mirror of
https://github.com/wweir/sower.git
synced 2026-10-19 16:14:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f0ca4ee7 | ||
|
|
98c3934762 | ||
|
|
f001f7dab7 | ||
|
|
36ef329b68 | ||
|
|
64778f68ac | ||
|
|
53748cfa85 | ||
|
|
d35115b7b3 | ||
|
|
559c5fde6b | ||
|
|
eb8080df18 | ||
|
|
45ca063a7e | ||
|
|
6add7afd19 | ||
|
|
f13687506e | ||
|
|
199e4cf295 | ||
|
|
b3ad9f6657 | ||
|
|
f9e9fe366f | ||
|
|
103699e25e | ||
|
|
f80755eae5 |
+19
-4
@@ -18,18 +18,33 @@ jobs:
|
||||
- run:
|
||||
name: Build linux Binary
|
||||
command: |
|
||||
make build
|
||||
tar czvf artifacts/sower_linux_amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower-linux-amd64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=386 make build
|
||||
tar czvf artifacts/sower-linux-386.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm make build
|
||||
tar czvf artifacts/sower-linux-arm.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=arm64 make build
|
||||
tar czvf artifacts/sower-linux-arm64.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mips make build
|
||||
tar czvf artifacts/sower-linux-mips.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
GOOS=linux GOARCH=mipsle make build
|
||||
tar czvf artifacts/sower-linux-mipsle.tar.gz sower sower.toml sower-client.service sower-server.service
|
||||
|
||||
- run:
|
||||
name: Build macOS Binary
|
||||
command: |
|
||||
GOOS=darwin GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_darwin_amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
tar czvf artifacts/sower-darwin-amd64.tar.gz sower sower.toml cc.wweir.sower.plist
|
||||
|
||||
- run:
|
||||
name: Build Windows Binary
|
||||
command: |
|
||||
GOOS=windows GOARCH=amd64 make build
|
||||
tar czvf artifacts/sower_windows_amd64.tar.gz sower.exe sower.toml
|
||||
tar czvf artifacts/sower-windows-amd64.tar.gz sower.exe sower.toml
|
||||
GOOS=windows GOARCH=386 make build
|
||||
tar czvf artifacts/sower-windows-386.tar.gz sower.exe sower.toml
|
||||
|
||||
- run:
|
||||
name: Publish Release on GitHub
|
||||
command: |
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
export GO111MODULE=on
|
||||
SERVER:=127.0.0.1:5533
|
||||
|
||||
default: test build
|
||||
|
||||
generate:
|
||||
ifeq ("", "$(shell which stringer)")
|
||||
@echo installing generator
|
||||
go get -v golang.org/x/tools/cmd/stringer
|
||||
endif
|
||||
go generate ./...
|
||||
|
||||
test:
|
||||
go vet ./...
|
||||
go test ./...
|
||||
build:
|
||||
GO111MODULE=on go build -v -ldflags \
|
||||
go build -ldflags \
|
||||
"-X main.version=$(shell git describe --tags) \
|
||||
-X main.date=$(shell date +%Y-%m-%d)"
|
||||
image:
|
||||
@@ -27,6 +33,6 @@ run: build kill
|
||||
$(PWD)/sower -n TCP -v 1 &
|
||||
sudo $(PWD)/sower &
|
||||
@sleep 1
|
||||
curl 127.0.0.1
|
||||
curl localhost
|
||||
@sleep 1
|
||||
@sudo pkill -9 sower || true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# sower
|
||||
[](https://github.com/wweir/sower/releases)
|
||||
[](https://circleci.com/gh/wweir/sower)
|
||||
[](https://hub.docker.com/r/wweir/sower)
|
||||
[](https://github.com/wweir/sower/issues)
|
||||
[](https://github.com/wweir/sower/stargazers)
|
||||
[](LICENSE)
|
||||
@@ -25,13 +26,17 @@ After Deployed, please check your config file, it is placed in `/usr/local/etc/s
|
||||
Auto deploy script support Linux server side and masOS/Linux client side.
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -s https://raw.githubusercontent.com/wweir/sower/master/.circleci/install)"
|
||||
$ bash -c "$(curl -sL https://git.io/fhhdp)"
|
||||
```
|
||||
|
||||
Then modify the configuration file as needed and set `127.0.0.1` as your first domain name server.
|
||||
In most situation, you just need to modify `/etc/resolv.conf`.
|
||||
|
||||
If you wanna uninstall sower, change `install` into `uninstall` and rerun the command.
|
||||
If you wanna uninstall sower, run:
|
||||
|
||||
```shell
|
||||
$ bash -c "$(curl -sL https://git.io/fhjer)"
|
||||
```
|
||||
|
||||
### Manually deploy
|
||||
1. Download the precompiled file from https://github.com/wweir/sower/releases
|
||||
@@ -93,3 +98,5 @@ For more detail, see [透明代理 Sower 技术剖析](https://wweir.cc/post/%E9
|
||||
- [x] relay optimization
|
||||
- [x] deploy script for all normal platform
|
||||
- [x] dns rule intelligent suggestions
|
||||
- [x] use socks5 as upstream proxy
|
||||
- [ ] multi port http_proxy support
|
||||
+86
-65
@@ -7,9 +7,10 @@ import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/golang/glog"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/wweir/sower/util"
|
||||
@@ -28,6 +29,7 @@ var Conf = struct {
|
||||
|
||||
DNSServer string `toml:"dns_server"`
|
||||
ClientIP string `toml:"client_ip"`
|
||||
SuggestLevel string `toml:"suggest_level"`
|
||||
ClearDNSCache string `toml:"clear_dns_cache"`
|
||||
|
||||
BlockList []string `toml:"blocklist"`
|
||||
@@ -36,40 +38,6 @@ var Conf = struct {
|
||||
Verbose int `toml:"verbose"`
|
||||
}{}
|
||||
|
||||
// OnRefreash will be executed while init and write new config
|
||||
var OnRefreash = []func() error{
|
||||
func() (err error) {
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.ConfigFile = file
|
||||
|
||||
return flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() error {
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).Run()
|
||||
default:
|
||||
return exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
initArgs()
|
||||
|
||||
@@ -77,48 +45,101 @@ func init() {
|
||||
glog.Warningln("no config file has been load:", Conf.ConfigFile)
|
||||
return
|
||||
}
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Fatalln(err)
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Fatalln(action+":", err)
|
||||
}
|
||||
}
|
||||
|
||||
go addSuggestions()
|
||||
}
|
||||
|
||||
// mu keep synchronized add rule(write), do not care read while write
|
||||
var mu = &sync.Mutex{}
|
||||
|
||||
// AddSuggestion add new domain into suggest rules
|
||||
func AddSuggestion(domain string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Conf.Suggestions = append(Conf.Suggestions, domain)
|
||||
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
|
||||
|
||||
{ // safe write
|
||||
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
// refreshFns will be executed while init and write new config
|
||||
var refreshFns = []func() (string, error){
|
||||
func() (string, error) {
|
||||
action := "load config"
|
||||
f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
return action, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
f.Close()
|
||||
return
|
||||
//safe refresh config
|
||||
file := Conf.ConfigFile
|
||||
if err = toml.NewDecoder(f).Decode(&Conf); err != nil {
|
||||
return action, err
|
||||
}
|
||||
f.Close()
|
||||
Conf.ConfigFile = file
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
return action, flag.Set("v", strconv.Itoa(Conf.Verbose))
|
||||
},
|
||||
func() (string, error) {
|
||||
action := "clear dns cache"
|
||||
if Conf.ClearDNSCache != "" {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if out, err := exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
|
||||
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
}
|
||||
default:
|
||||
if out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput(); err != nil {
|
||||
return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
return action, nil
|
||||
},
|
||||
}
|
||||
|
||||
// AddRefreshFn add refreshh function for reload config
|
||||
func AddRefreshFn(init bool, fn func() (string, error)) error {
|
||||
if init {
|
||||
if _, err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// reload config
|
||||
for i := range OnRefreash {
|
||||
if err := OnRefreash[i](); err != nil {
|
||||
glog.Errorln(err)
|
||||
refreshFns = append(refreshFns, fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SuggestCh add domain into suggestios
|
||||
var SuggestCh = make(chan string)
|
||||
|
||||
// addSuggestions add new domain into suggest rules
|
||||
func addSuggestions() {
|
||||
for domain := range SuggestCh {
|
||||
Conf.Suggestions = append(Conf.Suggestions, domain)
|
||||
Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq()
|
||||
|
||||
{ // safe write
|
||||
f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil {
|
||||
glog.Errorln(err)
|
||||
f.Close()
|
||||
continue
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// reload config
|
||||
for i := range refreshFns {
|
||||
if action, err := refreshFns[i](); err != nil {
|
||||
glog.Errorln(action+":", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -8,19 +8,22 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/wweir/sower/dns"
|
||||
"github.com/wweir/sower/proxy/shadow"
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func initArgs() {
|
||||
flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type ("+strings.Join(transport.ListTransports(), "|")+")")
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)")
|
||||
flag.StringVar(&Conf.NetType, "n", "TCP", "net type (socks5 client only): "+strings.Join(transport.ListTransports(), ","))
|
||||
flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type: "+strings.Join(shadow.ListCiphers(), ","))
|
||||
flag.StringVar(&Conf.Password, "p", "12345678", "password")
|
||||
flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)")
|
||||
flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in CLIENT MODE if set)")
|
||||
flag.StringVar(&Conf.HTTPProxy, "H", "", "http proxy listen addr")
|
||||
flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server")
|
||||
flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP")
|
||||
flag.StringVar(&Conf.SuggestLevel, "S", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ","))
|
||||
|
||||
if !flag.Parsed() {
|
||||
flag.Set("logtostderr", "true")
|
||||
|
||||
+5
-3
@@ -1,12 +1,13 @@
|
||||
net_type="TCP"
|
||||
cipher="AES_128_GCM"
|
||||
net_type="TCP" # TCP, KCP, QUIC, SOCKS5(client only)
|
||||
cipher="AES_128_GCM" # AES_128_GCM, AES_192_GCM, AES_256_GCM, CHACHA20_IETF_POLY1305, XCHACHA20_IETF_POLY1305
|
||||
password="12345678"
|
||||
server_port="5533"
|
||||
# server_addr="remote-server:5533" # replce it to remote server
|
||||
http_proxy=":8080" # eg: 192.168.0.2:8080
|
||||
dns_server="223.5.5.5" # Keep empty for dynamic setting from net env
|
||||
dns_server="114.114.114.114" # Keep empty for dynamic setting from net env
|
||||
client_ip="127.0.0.1" # listen the IP, dns target is the IP
|
||||
# clear_dns_cache="pkill mDNSResponder" # Windows: "ipconfig /flushdnss"
|
||||
suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP
|
||||
blocklist=[
|
||||
"**.google.*", # google
|
||||
"**.goo.gl",
|
||||
@@ -31,6 +32,7 @@ blocklist=[
|
||||
"*.github.*",
|
||||
]
|
||||
whitelist=[
|
||||
"**.in-addr.arpa",
|
||||
"iamp.*.*",
|
||||
"iamp.*.*.*",
|
||||
"smtp.*.*",
|
||||
|
||||
+6
-2
@@ -29,11 +29,11 @@ func GetDefaultDNSServer() (string, error) {
|
||||
|
||||
var conn net.PacketConn
|
||||
if runtime.GOOS == "windows" {
|
||||
if conn, err = reuseport.ListenPacket("udp", iface.IP.String()+":68"); err != nil {
|
||||
if conn, err = reuseport.ListenPacket("udp4", iface.IP.String()+":68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
} else {
|
||||
if conn, err = reuseport.ListenPacket("udp", "0.0.0.0:68"); err != nil {
|
||||
if conn, err = reuseport.ListenPacket("udp4", "0.0.0.0:68"); err != nil {
|
||||
return "", errors.Wrap(err, "listen dhcp")
|
||||
}
|
||||
}
|
||||
@@ -52,5 +52,9 @@ func GetDefaultDNSServer() (string, error) {
|
||||
|
||||
pack = dhcp4.Packet(buf[:n])
|
||||
dnsBytes := pack.ParseOptions()[dhcp4.OptionDomainNameServer]
|
||||
if len(dnsBytes) < 4 {
|
||||
return "", errors.New("no DNS setting in upstream network device")
|
||||
}
|
||||
|
||||
return net.IPv4(dnsBytes[0], dnsBytes[1], dnsBytes[2], dnsBytes[3]).String(), nil
|
||||
}
|
||||
|
||||
+85
-40
@@ -1,6 +1,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -9,34 +10,21 @@ import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
mem "github.com/wweir/mem-go"
|
||||
"github.com/wweir/sower/conf"
|
||||
)
|
||||
|
||||
const colon = byte(':')
|
||||
|
||||
func StartDNS(dnsServer, listenIP string) {
|
||||
func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) {
|
||||
ip := net.ParseIP(listenIP)
|
||||
|
||||
suggest := &intelliSuggest{listenIP, time.Second}
|
||||
suggest := &intelliSuggest{suggestCh, parseSuggestLevel(suggestLevel), listenIP, time.Second}
|
||||
mem.DefaultCache = mem.New(time.Hour)
|
||||
|
||||
dhcpCh := make(chan struct{})
|
||||
if dnsServer != "" {
|
||||
dnsServer = net.JoinHostPort(dnsServer, "53")
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
<-dhcpCh
|
||||
host, err := GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
// atomic action
|
||||
dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
}()
|
||||
go dynamicSetUpstreamDNS(listenIP, &dnsServer, dhcpCh)
|
||||
dhcpCh <- struct{}{}
|
||||
}
|
||||
|
||||
@@ -54,7 +42,7 @@ func StartDNS(dnsServer, listenIP string) {
|
||||
|
||||
domain := r.Question[0].Name
|
||||
if idx := strings.IndexByte(domain, colon); idx > 0 {
|
||||
domain = domain[:idx]
|
||||
domain = domain[:idx] // trim port
|
||||
}
|
||||
|
||||
matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest)
|
||||
@@ -64,6 +52,37 @@ func StartDNS(dnsServer, listenIP string) {
|
||||
glog.Fatalln(server.ListenAndServe())
|
||||
}
|
||||
|
||||
func dynamicSetUpstreamDNS(listenIP string, dnsServer *string, dhcpCh <-chan struct{}) {
|
||||
addr, _ := dns.ReverseAddr(listenIP)
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: false,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: addr,
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
for {
|
||||
<-dhcpCh
|
||||
if _, err := dns.Exchange(msg, *dnsServer); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := GetDefaultDNSServer()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// atomic action
|
||||
*dnsServer = net.JoinHostPort(host, "53")
|
||||
glog.Infoln("set dns server to", host)
|
||||
}
|
||||
}
|
||||
func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string,
|
||||
dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) {
|
||||
|
||||
@@ -74,7 +93,22 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := dns.Exchange(r, dnsServer)
|
||||
if !inWriteList {
|
||||
go func() {
|
||||
ip, err := net.LookupIP(domain)
|
||||
if err != nil || len(ip) == 0 {
|
||||
glog.V(1).Infoln(ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
mem.Remember(suggest, addr{domain, ip[0].String()})
|
||||
}()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
msg, err := dns.ExchangeContext(ctx, r, dnsServer)
|
||||
if err != nil {
|
||||
if dhcpCh != nil {
|
||||
select {
|
||||
@@ -82,31 +116,39 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer
|
||||
default:
|
||||
}
|
||||
}
|
||||
glog.V(1).Infof("get dns of %s from %s fail: %s", domain, dnsServer, err)
|
||||
return
|
||||
} else if msg == nil { // expose any response except nil
|
||||
glog.V(1).Infof("get dns of %s fail: %s", domain, err)
|
||||
glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer)
|
||||
return
|
||||
}
|
||||
|
||||
if !inWriteList {
|
||||
// trigger suggest logic while dns server OK
|
||||
// avoid check while DNS setting not correct
|
||||
go mem.Remember(suggest, domain)
|
||||
}
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
type intelliSuggest struct {
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
suggestCh chan<- string
|
||||
suggestLevel suggestLevel
|
||||
listenIP string
|
||||
timeout time.Duration
|
||||
}
|
||||
type addr struct {
|
||||
domain string
|
||||
ip string
|
||||
}
|
||||
|
||||
func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) {
|
||||
func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) {
|
||||
iface, e = struct{}{}, nil
|
||||
if i.suggestLevel == DISABLE {
|
||||
return
|
||||
}
|
||||
|
||||
domain := key.(addr).domain
|
||||
ip := key.(addr).ip
|
||||
|
||||
// kill deadloop, for ugly wildcard setting dns setting
|
||||
addr := strings.TrimSuffix(domain.(string), ".")
|
||||
if len(strings.Split(addr, ".")) > 10 {
|
||||
domain = strings.TrimSuffix(domain, ".")
|
||||
if strings.Count(domain, ".") > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,9 +157,9 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
|
||||
viaAddr string
|
||||
port Port
|
||||
}{
|
||||
{addr, HTTP},
|
||||
{ip, HTTP},
|
||||
{i.listenIP, HTTP},
|
||||
{addr, HTTPS},
|
||||
{ip, HTTPS},
|
||||
{i.listenIP, HTTPS},
|
||||
}
|
||||
protos = [...]*int32{
|
||||
@@ -128,22 +170,25 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
|
||||
)
|
||||
for idx := range pings {
|
||||
go func(idx int) {
|
||||
if err := HTTPPing(pings[idx].viaAddr, addr, pings[idx].port, i.timeout); err != nil {
|
||||
if err := HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil {
|
||||
// local ping fail
|
||||
if pings[idx].viaAddr == addr {
|
||||
if pings[idx].viaAddr == ip {
|
||||
atomic.AddInt32(score, 1)
|
||||
glog.V(1).Infof("local ping %s fail", addr)
|
||||
glog.V(1).Infof("local ping %s fail", domain)
|
||||
} else {
|
||||
atomic.AddInt32(score, -1)
|
||||
glog.V(1).Infof("remote ping %s fail", addr)
|
||||
glog.V(1).Infof("remote ping %s fail", domain)
|
||||
}
|
||||
|
||||
// remote ping faster
|
||||
} else if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && pings[idx].viaAddr == i.listenIP {
|
||||
atomic.AddInt32(score, 1)
|
||||
glog.V(1).Infof("remote ping %s faster", addr)
|
||||
} else if pings[idx].viaAddr == i.listenIP {
|
||||
if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.suggestLevel == SPEEDUP {
|
||||
atomic.AddInt32(score, 1)
|
||||
}
|
||||
glog.V(1).Infof("remote ping %s faster", domain)
|
||||
|
||||
} else {
|
||||
atomic.CompareAndSwapInt32(protos[idx/2], 0, 2)
|
||||
return // score change trigger add suggestion
|
||||
}
|
||||
|
||||
@@ -160,8 +205,8 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error)
|
||||
// 2. all remote pings are faster
|
||||
if atomic.LoadInt32(score) >= int32(len(protos)) {
|
||||
old := atomic.SwapInt32(score, -1) // avoid readd the suggestion
|
||||
conf.AddSuggestion(addr)
|
||||
glog.Infof("suggested domain: %s with score: %d", addr, old)
|
||||
i.suggestCh <- domain
|
||||
glog.Infof("suggested domain: %s with score: %d", domain, old)
|
||||
}
|
||||
}(idx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Code generated by "stringer -type=suggestLevel util.go"; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _suggestLevel_name = "DISABLEBLOCKSPEEDUPlevelEnd"
|
||||
|
||||
var _suggestLevel_index = [...]uint8{0, 7, 12, 19, 27}
|
||||
|
||||
func (i suggestLevel) String() string {
|
||||
if i < 0 || i >= suggestLevel(len(_suggestLevel_index)-1) {
|
||||
return "suggestLevel(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _suggestLevel_name[_suggestLevel_index[i]:_suggestLevel_index[i+1]]
|
||||
}
|
||||
+36
-19
@@ -5,34 +5,22 @@ import (
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/util"
|
||||
)
|
||||
|
||||
var (
|
||||
blockList *util.Node
|
||||
whiteList *util.Node
|
||||
suggestList *util.Node
|
||||
whiteList *util.Node
|
||||
)
|
||||
|
||||
func init() {
|
||||
host, _, _ := net.SplitHostPort(conf.Conf.ServerAddr)
|
||||
|
||||
//first init
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
suggestList = loadRules("suggest", conf.Conf.Suggestions)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
// LoadRules init rules from config
|
||||
func LoadRules(blocklist, suggestions, whitelist []string, host string) {
|
||||
blockList = loadRules("block", blocklist)
|
||||
suggestList = loadRules("suggest", suggestions)
|
||||
whiteList = loadRules("white", whitelist)
|
||||
whiteList.Add(host)
|
||||
glog.V(1).Infoln("load config")
|
||||
|
||||
conf.OnRefreash = append(conf.OnRefreash, func() error {
|
||||
blockList = loadRules("block", conf.Conf.BlockList)
|
||||
suggestList = loadRules("suggest", conf.Conf.Suggestions)
|
||||
whiteList = loadRules("white", conf.Conf.WhiteList)
|
||||
whiteList.Add(host)
|
||||
glog.V(1).Infoln("reload config")
|
||||
return nil
|
||||
})
|
||||
glog.V(1).Infoln("reloaded config")
|
||||
}
|
||||
|
||||
func loadRules(name string, list []string) *util.Node {
|
||||
@@ -57,3 +45,32 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//go:generate stringer -type=suggestLevel $GOFILE
|
||||
type suggestLevel int32
|
||||
|
||||
const (
|
||||
DISABLE suggestLevel = iota
|
||||
BLOCK
|
||||
SPEEDUP
|
||||
levelEnd
|
||||
)
|
||||
|
||||
func ListSuggestLevels() []string {
|
||||
list := make([]string, 0, int(levelEnd))
|
||||
for i := suggestLevel(0); i < levelEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func parseSuggestLevel(level string) suggestLevel {
|
||||
for i := suggestLevel(0); i < levelEnd; i++ {
|
||||
if level == i.String() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
glog.Exitln("invalid suggest level: " + level)
|
||||
return levelEnd
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
||||
github.com/lucas-clemente/quic-go v0.10.1
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
||||
github.com/miekg/dns v1.1.5
|
||||
github.com/miekg/dns v1.1.6
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
@@ -23,7 +23,7 @@ require (
|
||||
github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f // indirect
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 // indirect
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e
|
||||
)
|
||||
|
||||
@@ -21,8 +21,8 @@ github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQk
|
||||
github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/miekg/dns v1.1.5 h1:7T0Xr4dLK+cpA3vIupAI3aDJCPRU4khl5O2J0LEAV+Y=
|
||||
github.com/miekg/dns v1.1.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.6 h1:jVwb4GDwD65q/gtItR/lIZHjNH93QfeGxZUkzJcW9mc=
|
||||
github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
@@ -42,10 +42,12 @@ github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJd
|
||||
github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE=
|
||||
github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f h1:CxcNDReoTQBlkHuyVUepQrMJTSa7q1+j65kVRv6jK3c=
|
||||
golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/conf"
|
||||
"github.com/wweir/sower/dns"
|
||||
@@ -11,24 +13,31 @@ import (
|
||||
var version, date string
|
||||
|
||||
func main() {
|
||||
conf := conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, conf)
|
||||
cfg := &conf.Conf
|
||||
glog.Infof("Starting sower(%s %s): %v", version, date, cfg)
|
||||
|
||||
tran, err := transport.GetTransport(conf.NetType)
|
||||
tran, err := transport.GetTransport(cfg.NetType)
|
||||
if err != nil {
|
||||
glog.Exitln(err)
|
||||
}
|
||||
|
||||
if conf.ServerAddr == "" {
|
||||
proxy.StartServer(tran, conf.ServerPort, conf.Cipher, conf.Password)
|
||||
if cfg.ServerAddr == "" {
|
||||
proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password)
|
||||
|
||||
} else {
|
||||
if conf.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(tran, conf.ServerAddr,
|
||||
conf.Cipher, conf.Password, conf.HTTPProxy)
|
||||
conf.AddRefreshFn(true, func() (string, error) {
|
||||
host, _, _ := net.SplitHostPort(cfg.ServerAddr)
|
||||
dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, host)
|
||||
return "load rules", nil
|
||||
})
|
||||
|
||||
isSocks5 := (cfg.NetType == "SOCKS5")
|
||||
|
||||
if cfg.HTTPProxy != "" {
|
||||
go proxy.StartHttpProxy(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy)
|
||||
}
|
||||
|
||||
go dns.StartDNS(conf.DNSServer, conf.ClientIP)
|
||||
proxy.StartClient(tran, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP)
|
||||
go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel)
|
||||
proxy.StartClient(tran, isSocks5, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.ClientIP)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -8,22 +8,14 @@ import (
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartClient(tran transport.Transport, server, cipher, password, listenIP string) {
|
||||
func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) {
|
||||
connCh := listenLocal(listenIP, []string{"80", "443"})
|
||||
resolved := false
|
||||
|
||||
glog.Infoln("Client started.")
|
||||
for {
|
||||
conn := <-connCh
|
||||
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
resolveAddr(&server)
|
||||
glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server)
|
||||
|
||||
rc, err := tran.Dial(server)
|
||||
@@ -32,7 +24,15 @@ func StartClient(tran transport.Transport, server, cipher, password, listenIP st
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
if isSocks5 {
|
||||
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
|
||||
glog.Errorln(err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
}
|
||||
|
||||
go relay(conn, rc)
|
||||
}
|
||||
|
||||
+30
-22
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
@@ -13,25 +14,16 @@ import (
|
||||
"github.com/wweir/sower/proxy/transport"
|
||||
)
|
||||
|
||||
func StartHttpProxy(tran transport.Transport, server, cipher, password, addr string) {
|
||||
resolved := false
|
||||
|
||||
func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, password, addr string) {
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
resolveAddr(&server)
|
||||
|
||||
if r.Method == http.MethodConnect {
|
||||
httpsProxy(w, r, tran, server, cipher, password)
|
||||
httpsProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
} else {
|
||||
httpProxy(w, r, tran, server, cipher, password)
|
||||
httpProxy(w, r, tran, isSocks5, server, cipher, password)
|
||||
}
|
||||
}),
|
||||
// Disable HTTP/2.
|
||||
@@ -43,20 +35,28 @@ func StartHttpProxy(tran transport.Transport, server, cipher, password, addr str
|
||||
}
|
||||
|
||||
func httpProxy(w http.ResponseWriter, req *http.Request,
|
||||
tran transport.Transport, server, cipher, password string) {
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
roundTripper := &http.Transport{
|
||||
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if isSocks5 {
|
||||
roundTripper.Proxy = func(*http.Request) (*url.URL, error) {
|
||||
return url.Parse("socks5://" + server)
|
||||
}
|
||||
|
||||
} else {
|
||||
roundTripper.DialContext = func(context.Context, string, string) (net.Conn, error) {
|
||||
conn, err := tran.Dial(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shadow.Shadow(conn, cipher, password), nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := roundTripper.RoundTrip(req)
|
||||
@@ -77,7 +77,7 @@ func httpProxy(w http.ResponseWriter, req *http.Request,
|
||||
}
|
||||
|
||||
func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
tran transport.Transport, server, cipher, password string) {
|
||||
tran transport.Transport, isSocks5 bool, server, cipher, password string) {
|
||||
|
||||
// local conn
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
@@ -101,7 +101,15 @@ func httpsProxy(w http.ResponseWriter, r *http.Request,
|
||||
glog.Errorln("serve https proxy, dial remote fail:", err)
|
||||
return
|
||||
}
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
|
||||
if isSocks5 {
|
||||
if rc, conn, err = buildSocks5Conn(rc, conn); err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
rc = shadow.Shadow(rc, cipher, password)
|
||||
}
|
||||
|
||||
relay(rc, conn)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,17 @@ const (
|
||||
AES_256_GCM
|
||||
CHACHA20_IETF_POLY1305
|
||||
XCHACHA20_IETF_POLY1305
|
||||
cipherEnd
|
||||
)
|
||||
|
||||
func ListCiphers() []string {
|
||||
list := make([]string, 0, int(cipherEnd))
|
||||
for i := cipherType(0); i < cipherEnd; i++ {
|
||||
list = append(list, i.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func pickCipher(cipherType, password string) (cipher.AEAD, error) {
|
||||
var blockSize int
|
||||
switch cipherType {
|
||||
|
||||
@@ -4,9 +4,9 @@ package shadow
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305"
|
||||
const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd"
|
||||
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78}
|
||||
var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78, 87}
|
||||
|
||||
func (i cipherType) String() string {
|
||||
if i < 0 || i >= cipherType(len(_cipherType_index)-1) {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ToSocks5(c net.Conn, domain, port string) net.Conn {
|
||||
switch port {
|
||||
case "80":
|
||||
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x00, 0x50}}
|
||||
case "443":
|
||||
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: []byte{0x01, 0xbb}}
|
||||
default:
|
||||
panic("invalid port: " + port)
|
||||
}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
init chan struct{}
|
||||
domain string
|
||||
port []byte
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
<-c.init
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-c.init:
|
||||
return c.Conn.Write(b)
|
||||
default:
|
||||
}
|
||||
|
||||
{
|
||||
req := &authReq{
|
||||
VER: 5,
|
||||
NMETHODS: 1,
|
||||
METHODS: [1]byte{0}, // NO AUTHENTICATION REQUIRED
|
||||
}
|
||||
if err := binary.Write(c.Conn, binary.BigEndian, req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
resp := &authResp{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
req := &request{
|
||||
req: req{
|
||||
VER: 5, // socks5
|
||||
CMD: 1, // CONNECT
|
||||
RSV: 0, // RESERVED
|
||||
ATYP: 3, // DOMAINNAME
|
||||
},
|
||||
DST_ADDR: append([]byte{byte(len(c.domain))}, []byte(c.domain)...),
|
||||
DST_PORT: c.port,
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Write(req.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
{
|
||||
resp := &response{}
|
||||
if err := binary.Read(c.Conn, binary.BigEndian, &(resp.resp)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch resp.REP {
|
||||
case 0x00:
|
||||
default:
|
||||
return 0, errors.Errorf("socks5 handshake fail, return code: %d", resp.REP)
|
||||
}
|
||||
|
||||
switch resp.ATYP {
|
||||
case 0x01: // IPv4
|
||||
resp.DST_ADDR = make([]byte, net.IPv4len)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case 0x03: // domain name
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR[1:1+int(resp.DST_ADDR[0])]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case 0x04:
|
||||
resp.DST_ADDR = make([]byte, net.IPv6len)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_ADDR); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.DST_PORT = make([]byte, 2)
|
||||
if _, err := io.ReadFull(c.Conn, resp.DST_PORT); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
close(c.init)
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
type authReq struct {
|
||||
VER byte
|
||||
NMETHODS byte
|
||||
METHODS [1]byte // 1 to 255, fix to no authentication
|
||||
}
|
||||
|
||||
type authResp struct {
|
||||
VER byte
|
||||
METHOD byte
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type req struct {
|
||||
VER byte
|
||||
CMD byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
|
||||
func (r *request) Bytes() []byte {
|
||||
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
|
||||
out = append(out, r.DST_ADDR...)
|
||||
return append(out, r.DST_PORT...)
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp
|
||||
DST_ADDR []byte // first byte is length
|
||||
DST_PORT []byte // two bytes
|
||||
}
|
||||
type resp struct {
|
||||
VER byte
|
||||
REP byte
|
||||
RSV byte
|
||||
ATYP byte
|
||||
}
|
||||
+10
-1
@@ -9,12 +9,17 @@ import (
|
||||
|
||||
type tcp struct {
|
||||
DialTimeout time.Duration
|
||||
isSocks5 bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["TCP"] = &tcp{
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
transports["SOCKS5"] = &tcp{
|
||||
DialTimeout: 5 * time.Second,
|
||||
isSocks5: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tcp) Dial(server string) (net.Conn, error) {
|
||||
@@ -27,7 +32,11 @@ func (t *tcp) Dial(server string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (*tcp) Listen(port string) (<-chan net.Conn, error) {
|
||||
func (t *tcp) Listen(port string) (<-chan net.Conn, error) {
|
||||
if t.isSocks5 {
|
||||
panic("not support run as socks5 server")
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -8,8 +8,42 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/wweir/sower/proxy/parser"
|
||||
"github.com/wweir/sower/proxy/socks5"
|
||||
)
|
||||
|
||||
// race safe
|
||||
var resolved = false
|
||||
|
||||
func resolveAddr(server *string) {
|
||||
if !resolved {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil {
|
||||
glog.Errorln(err)
|
||||
} else {
|
||||
*server = addr.String()
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildSocks5Conn(c, relay net.Conn) (net.Conn, net.Conn, error) {
|
||||
conn, addr, err := parser.ParseAddr(relay)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
relay.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
relay.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return socks5.ToSocks5(c, host, port), conn, nil
|
||||
}
|
||||
|
||||
func relay(conn1, conn2 net.Conn) {
|
||||
wg := &sync.WaitGroup{}
|
||||
exitFlag := new(int32)
|
||||
|
||||
Reference in New Issue
Block a user