diff --git a/Makefile b/Makefile index 9e79558..5baa35f 100644 --- a/Makefile +++ b/Makefile @@ -24,15 +24,20 @@ kill: sudo pkill -9 sower || true client: build kill - sudo $(PWD)/sower + sudo $(PWD)/sower -f '' -s 127.0.0.1:5533 -H "127.0.0.1:8080" -server: build kill - $(PWD)/sower -n TCP -v 1 +server: build + $(PWD)/sower -f '' run: build kill - $(PWD)/sower -n TCP -v 1 & - sudo $(PWD)/sower & + $(PWD)/sower -f '' & + sudo $(PWD)/sower -f '' -s 127.0.0.1:5533 -H "127.0.0.1:8080" & + @sleep 1 - curl localhost + HTTP_PROXY=http://127.0.0.1:8080 curl http://baidu.com || true + @echo + HTTPS_PROXY=http://127.0.0.1:8080 curl https://baidu.com || true + @echo + @sleep 1 @sudo pkill -9 sower || true diff --git a/conf/conf_other.go b/conf/conf_other.go index 001cfd6..bbb402c 100644 --- a/conf/conf_other.go +++ b/conf/conf_other.go @@ -23,11 +23,11 @@ func initArgs() { 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(), ",")) + flag.StringVar(&Conf.SuggestLevel, "l", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ",")) flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version") if !flag.Parsed() { diff --git a/dns/dhcp_test.go b/dns/dhcp_test.go index 03adf0a..0b56b2b 100644 --- a/dns/dhcp_test.go +++ b/dns/dhcp_test.go @@ -1,9 +1,18 @@ package dns -import "testing" +import ( + "runtime" + "testing" +) func TestGetDefaultDNSServer(t *testing.T) { - t.Skip("skip for some enviroment not support dhcp and permission") + switch runtime.GOOS { + case "windows": + case "darwin": + default: + t.Skip("skip for some enviroment not support dhcp and permission set") + return + } if got, err := GetDefaultDNSServer(); err != nil { t.Errorf("GetDefaultDNSServer() return error: %s", err) diff --git a/dns/dns.go b/dns/dns.go index 49e6259..6009d8b 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -10,14 +10,15 @@ import ( "github.com/golang/glog" "github.com/miekg/dns" mem "github.com/wweir/mem-go" + "github.com/wweir/sower/util" ) const colon = byte(':') -func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) { +func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string) { ip := net.ParseIP(listenIP) - suggest := &intelliSuggest{suggestCh, parseSuggestLevel(suggestLevel), listenIP, time.Second} + suggest := &intelliSuggest{suggestCh, parseSuggestLevel(level), listenIP, time.Second} mem.DefaultCache = mem.New(time.Hour) dhcpCh := make(chan struct{}) @@ -93,18 +94,7 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer return } - 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()}) - }() - } - + go mem.Remember(suggest, domain) ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() @@ -127,40 +117,39 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer } type intelliSuggest struct { - suggestCh chan<- string - suggestLevel suggestLevel - listenIP string - timeout time.Duration -} -type addr struct { - domain string - ip string + suggestCh chan<- string + level level + listenIP string + timeout time.Duration } func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) { iface, e = struct{}{}, nil - if i.suggestLevel == DISABLE { + if i.level == DISABLE { return } - domain := key.(addr).domain - ip := key.(addr).ip - // kill deadloop, for ugly wildcard setting dns setting - domain = strings.TrimSuffix(domain, ".") + domain := strings.TrimSuffix(key.(string), ".") if strings.Count(domain, ".") > 10 { return } + ip, err := net.LookupIP(domain) + if err != nil || len(ip) == 0 { + glog.V(1).Infoln(domain, ip, err) + return + } + var ( pings = [...]struct { viaAddr string - port Port + port util.Port }{ - {ip, HTTP}, - {i.listenIP, HTTP}, - {ip, HTTPS}, - {i.listenIP, HTTPS}, + {ip[0].String(), util.HTTP}, + {i.listenIP, util.HTTP}, + {ip[0].String(), util.HTTPS}, + {i.listenIP, util.HTTPS}, } protos = [...]*int32{ new(int32), /*HTTP*/ @@ -170,19 +159,19 @@ func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) { ) for idx := range pings { go func(idx int) { - if err := HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil { + if err := util.HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil { // local ping fail - if pings[idx].viaAddr == ip { - atomic.AddInt32(score, 1) - glog.V(1).Infof("local ping %s fail", domain) - } else { + if pings[idx].viaAddr == i.listenIP { atomic.AddInt32(score, -1) glog.V(1).Infof("remote ping %s fail", domain) + } else { + atomic.AddInt32(score, 1) + glog.V(1).Infof("local ping %s fail", domain) } // remote ping faster } else if pings[idx].viaAddr == i.listenIP { - if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.suggestLevel == SPEEDUP { + if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.level == SPEEDUP { atomic.AddInt32(score, 1) } glog.V(1).Infof("remote ping %s faster", domain) diff --git a/dns/level_string.go b/dns/level_string.go new file mode 100644 index 0000000..ba6f934 --- /dev/null +++ b/dns/level_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=level util.go"; DO NOT EDIT. + +package dns + +import "strconv" + +const _level_name = "DISABLEBLOCKSPEEDUPlevelEnd" + +var _level_index = [...]uint8{0, 7, 12, 19, 27} + +func (i level) String() string { + if i < 0 || i >= level(len(_level_index)-1) { + return "level(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _level_name[_level_index[i]:_level_index[i+1]] +} diff --git a/dns/suggestlevel_string.go b/dns/suggestlevel_string.go deleted file mode 100644 index 3839562..0000000 --- a/dns/suggestlevel_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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]] -} diff --git a/dns/util.go b/dns/util.go index 47df51b..0fa0ae1 100644 --- a/dns/util.go +++ b/dns/util.go @@ -46,11 +46,11 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg { return m } -//go:generate stringer -type=suggestLevel $GOFILE -type suggestLevel int32 +//go:generate stringer -type=level $GOFILE +type level int32 const ( - DISABLE suggestLevel = iota + DISABLE level = iota BLOCK SPEEDUP levelEnd @@ -58,19 +58,19 @@ const ( func ListSuggestLevels() []string { list := make([]string, 0, int(levelEnd)) - for i := suggestLevel(0); i < levelEnd; i++ { + for i := level(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() { +func parseSuggestLevel(suggestLevel string) level { + for i := level(0); i < levelEnd; i++ { + if suggestLevel == i.String() { return i } } - glog.Exitln("invalid suggest level: " + level) + glog.Exitln("invalid suggest level: " + suggestLevel) return levelEnd } diff --git a/proxy/client.go b/proxy/client.go index cf500ab..6f814cc 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -62,11 +62,11 @@ func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, passwo case !isSocks5 && isHttp: rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpProtocol(rc) + rc = parser.NewHttpConn(rc) case !isSocks5 && !isHttp: rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpsProtocol(rc, "443") + rc = parser.NewHttpsConn(rc, "443") } go relay(conn, rc) diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 2f06fa0..627e88d 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -58,8 +58,8 @@ func httpProxy(w http.ResponseWriter, r *http.Request, return nil, err } - conn = parser.NewHttpProtocol(conn) - return shadow.Shadow(conn, cipher, password), nil + conn = shadow.Shadow(conn, cipher, password) + return parser.NewHttpConn(conn), nil } } @@ -120,7 +120,7 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, } else { rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpsProtocol(rc, port) + rc = parser.NewHttpsConn(rc, port) } relay(rc, conn) diff --git a/proxy/parser/parse_test.go b/proxy/parser/parse_test.go new file mode 100644 index 0000000..917b33c --- /dev/null +++ b/proxy/parser/parse_test.go @@ -0,0 +1,68 @@ +package parser + +import ( + "bufio" + "bytes" + "io/ioutil" + "net" + "net/http" + "testing" + + "github.com/wweir/sower/util" +) + +func TestParseAddr1(t *testing.T) { + c1, c2 := net.Pipe() + + go func() { + c1 = NewHttpConn(c1) + req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3})) + req.Write(c1) + }() + + c2, host, port, err := ParseAddr(c2) + + if err != nil || host != "wweir.cc" || port != "80" { + t.Error(err, host, port) + } + + req, err := http.ReadRequest(bufio.NewReader(c2)) + if err != nil { + t.Error(err) + } + + data, err := ioutil.ReadAll(req.Body) + if err != nil || len(data) != 3 || data[0] != 1 { + t.Error(err, data) + } +} + +func TestParseAddr2(t *testing.T) { + c1, c2 := net.Pipe() + + go func() { + c1 = NewHttpsConn(c1, "443") + c1.Write(util.HTTPS.PingMsg("wweir.cc")) + }() + + _, host, port, err := ParseAddr(c2) + + if err != nil || host != "wweir.cc" || port != "443" { + t.Error(err, host, port) + } +} + +func TestParseAddr3(t *testing.T) { + c1, c2 := net.Pipe() + + go func() { + c1 = NewOtherConn(c1, "wweir.cc", "1080") + c1.Write(util.HTTPS.PingMsg("wweir.cc")) + }() + + _, host, port, err := ParseAddr(c2) + + if err != nil || host != "wweir.cc" || port != "1080" { + t.Error(err, host, port) + } +} diff --git a/proxy/server.go b/proxy/server.go index b5a56b5..99da7a6 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -14,7 +14,7 @@ func StartServer(tran transport.Transport, port, cipher, password string) { if port == "" { glog.Fatalln("port must set") } - if !strings.Contains(port, ":") { + if !strings.HasPrefix(port, ":") { port = ":" + port } @@ -25,14 +25,12 @@ func StartServer(tran transport.Transport, port, cipher, password string) { glog.Infoln("Server started.") for { - conn := <-connCh - conn = shadow.Shadow(conn, cipher, password) - - go handle(conn) + go handle(<-connCh, cipher, password) } } -func handle(conn net.Conn) { +func handle(conn net.Conn, cipher, password string) { + conn = shadow.Shadow(conn, cipher, password) conn, host, port, err := parser.ParseAddr(conn) if err != nil { conn.Close() diff --git a/proxy/shadow/cipher.go b/proxy/shadow/cipher.go index a553d3a..c6af813 100644 --- a/proxy/shadow/cipher.go +++ b/proxy/shadow/cipher.go @@ -8,11 +8,11 @@ import ( "golang.org/x/crypto/chacha20poly1305" ) -//go:generate stringer -type=cipherType $GOFILE -type cipherType int +//go:generate stringer -type=typ $GOFILE +type typ int const ( - AES_128_GCM cipherType = iota + AES_128_GCM typ = iota AES_192_GCM AES_256_GCM CHACHA20_IETF_POLY1305 @@ -22,15 +22,15 @@ const ( func ListCiphers() []string { list := make([]string, 0, int(cipherEnd)) - for i := cipherType(0); i < cipherEnd; i++ { + for i := typ(0); i < cipherEnd; i++ { list = append(list, i.String()) } return list } -func pickCipher(cipherType, password string) (cipher.AEAD, error) { +func pickCipher(typ, password string) (cipher.AEAD, error) { var blockSize int - switch cipherType { + switch typ { case AES_128_GCM.String(): blockSize = 16 case AES_192_GCM.String(): @@ -44,7 +44,7 @@ func pickCipher(cipherType, password string) (cipher.AEAD, error) { return chacha20poly1305.NewX(genKey(password, 256)) default: - return nil, errors.New("do not support cipher type: " + cipherType) + return nil, errors.New("do not support cipher type: " + typ) } // aes gcm diff --git a/proxy/shadow/ciphertype_string.go b/proxy/shadow/ciphertype_string.go deleted file mode 100644 index c5bf544..0000000 --- a/proxy/shadow/ciphertype_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by "stringer -type=cipherType cipher.go"; DO NOT EDIT. - -package shadow - -import "strconv" - -const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd" - -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) { - return "cipherType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _cipherType_name[_cipherType_index[i]:_cipherType_index[i+1]] -} diff --git a/proxy/shadow/shadow_test.go b/proxy/shadow/shadow_test.go new file mode 100644 index 0000000..4ddb953 --- /dev/null +++ b/proxy/shadow/shadow_test.go @@ -0,0 +1,22 @@ +package shadow + +import ( + "net" + "testing" +) + +func TestShadow(t *testing.T) { + c1, c2 := net.Pipe() + + go func() { + conn := Shadow(c1, "AES_128_GCM", "12345678") + conn.Write([]byte{1, 2}) + }() + + conn := Shadow(c2, "AES_128_GCM", "12345678") + buf := make([]byte, 3) + n, _ := conn.Read(buf) + if n!=2|| buf[0] != 1 || buf[1] != 2 { + t.Error(buf) + } +} diff --git a/proxy/shadow/typ_string.go b/proxy/shadow/typ_string.go new file mode 100644 index 0000000..d6e344a --- /dev/null +++ b/proxy/shadow/typ_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=typ cipher.go"; DO NOT EDIT. + +package shadow + +import "strconv" + +const _typ_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd" + +var _typ_index = [...]uint8{0, 11, 22, 33, 55, 78, 87} + +func (i typ) String() string { + if i < 0 || i >= typ(len(_typ_index)-1) { + return "typ(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _typ_name[_typ_index[i]:_typ_index[i+1]] +} diff --git a/dns/http_ping.go b/util/http_ping.go similarity index 96% rename from dns/http_ping.go rename to util/http_ping.go index 7e5590d..5b8bf50 100644 --- a/dns/http_ping.go +++ b/util/http_ping.go @@ -1,4 +1,4 @@ -package dns +package util import ( "bytes" @@ -18,7 +18,7 @@ func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err err defer conn.Close() conn.SetDeadline(time.Now().Add(timeout)) - if _, err = conn.Write(port.pingMsg(domain)); err != nil { + if _, err = conn.Write(port.PingMsg(domain)); err != nil { return err } @@ -51,7 +51,7 @@ func (p Port) JoinAddr(addr string) string { } } -func (p Port) pingMsg(domain string) []byte { +func (p Port) PingMsg(domain string) []byte { switch p { case HTTP: return []byte("TRACE / HTTP/1.1\r\nHost: " + domain + "\r\n\r\n") diff --git a/util/pick_iface_test.go b/util/pick_iface_test.go index 00b81d6..b81b6f2 100644 --- a/util/pick_iface_test.go +++ b/util/pick_iface_test.go @@ -1,11 +1,18 @@ package util import ( + "runtime" "testing" ) func TestPickInterface(t *testing.T) { - t.Skip("skip for some enviroment not have net interface") + switch runtime.GOOS { + case "windows": + case "darwin": + default: + t.Skip("skip for some enviroment not have net interface") + return + } got, err := PickInterface() if err != nil {