diff --git a/Makefile b/Makefile index 9e79558..f6c61b7 100644 --- a/Makefile +++ b/Makefile @@ -24,15 +24,20 @@ kill: sudo pkill -9 sower || true client: build kill - sudo $(PWD)/sower + sudo $(PWD)/sower -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 e01c84e..6f814cc 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -4,16 +4,26 @@ import ( "net" "github.com/golang/glog" + "github.com/wweir/sower/proxy/parser" "github.com/wweir/sower/proxy/shadow" + "github.com/wweir/sower/proxy/socks5" "github.com/wweir/sower/proxy/transport" ) func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) { - connCh := listenLocal(listenIP, []string{"80", "443"}) + conn80 := listenLocal(listenIP, "80") + conn443 := listenLocal(listenIP, "443") + var isHttp bool + var conn net.Conn glog.Infoln("Client started.") for { - conn := <-connCh + select { + case conn = <-conn80: + isHttp = true + case conn = <-conn443: + isHttp = false + } resolveAddr(&server) glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server) @@ -25,41 +35,64 @@ func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, passwo continue } - if isSocks5 { - if rc, conn, err = buildSocks5Conn(rc, conn); err != nil { + switch { + case isSocks5 && isHttp: + c, host, port, err := parser.ParseHttpAddr(conn) + if err != nil { + c.Close() + rc.Close() + glog.Errorln(err) + continue + } + + conn = c + rc = socks5.ToSocks5(rc, host, port) + + case isSocks5 && !isHttp: + c, host, err := parser.ParseHttpsHost(conn) + if err != nil { + c.Close() + rc.Close() glog.Errorln(err) continue } - } else { + + conn = c + rc = socks5.ToSocks5(rc, host, "443") + + case !isSocks5 && isHttp: rc = shadow.Shadow(rc, cipher, password) + rc = parser.NewHttpConn(rc) + + case !isSocks5 && !isHttp: + rc = shadow.Shadow(rc, cipher, password) + rc = parser.NewHttpsConn(rc, "443") } go relay(conn, rc) } } -func listenLocal(listenIP string, ports []string) <-chan net.Conn { +func listenLocal(listenIP string, port string) <-chan net.Conn { connCh := make(chan net.Conn, 10) - for i := range ports { - go func(port string) { - ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port)) + go func() { + ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port)) + if err != nil { + glog.Fatalln(err) + } + + for { + conn, err := ln.Accept() if err != nil { - glog.Fatalln(err) + glog.Errorln("accept", listenIP+port, "fail:", err) + continue } - for { - conn, err := ln.Accept() - if err != nil { - glog.Errorln("accept", listenIP+port, "fail:", err) - continue - } - - conn.(*net.TCPConn).SetKeepAlive(true) - connCh <- conn - } - }(ports[i]) - } + conn.(*net.TCPConn).SetKeepAlive(true) + connCh <- conn + } + }() - glog.Infoln("listening ports:", ports) + glog.Infoln("listening port:", port) return connCh } diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 84a6331..627e88d 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -10,7 +10,9 @@ import ( "time" "github.com/golang/glog" + "github.com/wweir/sower/proxy/parser" "github.com/wweir/sower/proxy/shadow" + "github.com/wweir/sower/proxy/socks5" "github.com/wweir/sower/proxy/transport" ) @@ -34,7 +36,7 @@ func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, pas glog.Fatalln(srv.ListenAndServe()) } -func httpProxy(w http.ResponseWriter, req *http.Request, +func httpProxy(w http.ResponseWriter, r *http.Request, tran transport.Transport, isSocks5 bool, server, cipher, password string) { roundTripper := &http.Transport{ @@ -55,11 +57,13 @@ func httpProxy(w http.ResponseWriter, req *http.Request, if err != nil { return nil, err } - return shadow.Shadow(conn, cipher, password), nil + + conn = shadow.Shadow(conn, cipher, password) + return parser.NewHttpConn(conn), nil } } - resp, err := roundTripper.RoundTrip(req) + resp, err := roundTripper.RoundTrip(r) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) glog.Errorln("serve https proxy, get remote data:", err) @@ -88,6 +92,7 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, conn.(*net.TCPConn).SetKeepAlive(true) if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) conn.Close() glog.Errorln("serve https proxy, write data fail:", err) return @@ -96,19 +101,26 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, // remote conn rc, err := tran.Dial(server) if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) conn.Close() + glog.Errorln("serve https proxy, dial remote fail:", err) + return + } + + host, port, err := net.SplitHostPort(r.Host) + if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) + conn.Close() glog.Errorln("serve https proxy, dial remote fail:", err) return } if isSocks5 { - if rc, conn, err = buildSocks5Conn(rc, conn); err != nil { - glog.Errorln(err) - return - } + rc = socks5.ToSocks5(rc, host, port) + } else { rc = shadow.Shadow(rc, cipher, password) + rc = parser.NewHttpsConn(rc, port) } relay(rc, conn) diff --git a/proxy/parser/doc.go b/proxy/parser/doc.go new file mode 100644 index 0000000..bf127df --- /dev/null +++ b/proxy/parser/doc.go @@ -0,0 +1,12 @@ +// Package parser transter conn to be a parser conn +// +// init request payload: +// (1) + (2))(+Overhead) + (size+Overhead) +// data definition: +// 0x00(any): [size](1) + [addr:port] + content +// 0x01(http): content +// 0x02(https): [port](2) + content +// +// init response payload: +// ([status code](2) + (2))(+Overhead) + (size+Overhead) +package parser diff --git a/proxy/parser/parse.go b/proxy/parser/parse.go new file mode 100644 index 0000000..c8e8d52 --- /dev/null +++ b/proxy/parser/parse.go @@ -0,0 +1,163 @@ +package parser + +import ( + "bufio" + "io" + "net" + "net/http" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/wweir/sower/util" +) + +const ( + OTHER byte = iota + HTTP + HTTPS +) + +// Write Addr +type conn struct { + typ byte + domain string + port string + init bool + net.Conn +} + +func NewOtherConn(c net.Conn, domain, port string) net.Conn { + return &conn{ + typ: OTHER, + domain: domain, + port: port, + init: true, + Conn: c, + } +} +func NewHttpConn(c net.Conn) net.Conn { + return &conn{ + typ: HTTP, + init: true, + Conn: c, + } +} +func NewHttpsConn(c net.Conn, port string) net.Conn { + return &conn{ + typ: HTTPS, + port: port, + init: true, + Conn: c, + } +} + +func (c *conn) Write(b []byte) (n int, err error) { + if c.init { + var pkg []byte + var prefixLen int + switch c.typ { + case OTHER: + // type + domain + ':' + port + data + prefixLen = 1 + len(c.domain) + 1 + len(c.port) + pkg = make([]byte, 0, prefixLen+len(b)) + pkg = append(pkg, OTHER) + pkg = append(pkg, byte(len(c.domain)+1+len(c.port))) + pkg = append(pkg, []byte(c.domain+":"+c.port)...) + + case HTTP: + // type + data + prefixLen = 1 + pkg = make([]byte, 0, prefixLen+len(b)) + pkg = append(pkg, HTTP) + + case HTTPS: + // type + port + data + prefixLen = 1 + 2 + pkg = make([]byte, 0, prefixLen+len(b)) + pkg = append(pkg, HTTPS) + port, _ := strconv.Atoi(c.port) + pkg = append(pkg, byte(port>>8), byte(port)) + } + + c.init = false + n, err := c.Conn.Write(append(pkg, b...)) + // n should larger than prefix length, if not, err is not nil + return n - prefixLen, err + } + + return c.Conn.Write(b) +} + +// Read Addr +func ParseAddr(conn net.Conn) (net.Conn, string, string, error) { + buf := make([]byte, 1) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + + switch buf[0] { + case OTHER: + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + buf = make([]byte, int(buf[0])) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + + addr := string(buf) + if idx := strings.LastIndex(addr, ":"); idx != -1 { + return conn, addr[:idx], addr[idx+1:], nil + } + return conn, "", "", errors.New("invalid payload") + + case HTTP: + return ParseHttpAddr(conn) + + case HTTPS: + buf = make([]byte, 2) + if _, err := io.ReadFull(conn, buf); err != nil { + return conn, "", "", err + } + port := strconv.Itoa(int(buf[0])<<8 + int(buf[1])) + + conn, domain, err := ParseHttpsHost(conn) + return conn, domain, port, err + + default: + return conn, "", "", errors.Errorf("not supported type (%v)", buf[0]) + } +} + +func ParseHttpAddr(conn net.Conn) (net.Conn, string, string, error) { + teeConn := &util.TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() + + b := bufio.NewReader(teeConn) + resp, err := http.ReadRequest(b) + if err != nil { + return teeConn, "", "", err + } + + if idx := strings.LastIndex(resp.Host, ":"); idx != -1 { + return teeConn, resp.Host[:idx], resp.Host[idx+1:], nil + } + return teeConn, resp.Host, "80", nil +} + +func ParseHttpsHost(conn net.Conn) (net.Conn, string, error) { + teeConn := &util.TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() + + domain, _, err := extractSNI(teeConn) + if err != nil { + return teeConn, "", err + } else if domain == "" { + return teeConn, "", errors.New("ClientHello did not present an SNI extension") + } + + return teeConn, domain, nil +} diff --git a/proxy/parser/parse_addr.go b/proxy/parser/parse_addr.go deleted file mode 100644 index d098083..0000000 --- a/proxy/parser/parse_addr.go +++ /dev/null @@ -1,48 +0,0 @@ -package parser - -import ( - "bufio" - "fmt" - "io" - "net" - "net/http" - "strings" - - "github.com/wweir/sower/util" -) - -func ParseAddr(conn net.Conn) (teeConn *util.TeeConn, addr string, err error) { - teeConn = &util.TeeConn{Conn: conn} - teeConn.StartOrReset() - defer teeConn.Stop() - - buf := make([]byte, 1) - if n, err := teeConn.Read(buf); err != nil { - return teeConn, "", fmt.Errorf("Read conn fail: %v, readed: %v", err, buf[:n]) - } - - teeConn.StartOrReset() - - // https - if buf[0] == 0x16 { // SSL handleshake - host, _, err := extractSNI(io.Reader(teeConn)) - if err != nil { - return teeConn, "", err - } - if strings.Contains(host, ":") { - return teeConn, host, nil - } - return teeConn, host + ":443", nil - } - - // http - b := bufio.NewReader(teeConn) - resp, err := http.ReadRequest(b) - if err != nil { - return teeConn, "", err - } - if strings.Contains(resp.Host, ":") { - return teeConn, resp.Host, nil - } - return teeConn, resp.Host + ":80", nil -} diff --git a/proxy/parser/parse_test.go b/proxy/parser/parse_test.go new file mode 100644 index 0000000..add722a --- /dev/null +++ b/proxy/parser/parse_test.go @@ -0,0 +1,97 @@ +package parser + +import ( + "bufio" + "bytes" + "io/ioutil" + "net" + "net/http" + "testing" + + "github.com/wweir/sower/proxy/shadow" + "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) + } +} + +func TestParseAddr4(t *testing.T) { + c1, c2 := net.Pipe() + + go func() { + c1 = shadow.Shadow(c1, "AES_128_GCM", "12345678") + c1 = NewHttpConn(c1) + req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3})) + req.Write(c1) + }() + + c2 = shadow.Shadow(c2, "AES_128_GCM", "12345678") + 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) + } +} diff --git a/proxy/server.go b/proxy/server.go index badd09e..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,23 +25,21 @@ 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) { - conn, addr, err := parser.ParseAddr(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() glog.Warningln(err) return } - glog.V(1).Infof("new conn from %s to %s", conn.RemoteAddr(), addr) + glog.V(1).Infof("new conn from %s to %s:%s", conn.RemoteAddr(), host, port) - rc, err := net.Dial("tcp", addr) + rc, err := net.Dial("tcp", net.JoinHostPort(host, port)) if err != nil { conn.Close() glog.Warningln(err) 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/doc.go b/proxy/shadow/doc.go index d4d56e2..9bc17fe 100644 --- a/proxy/shadow/doc.go +++ b/proxy/shadow/doc.go @@ -1,4 +1,5 @@ // Package shadow transter conn to be a crypto conn // support aead mode only -// payload: [size](2+Overhead bytes) + [content](size+Overhead bytes) +// data payload: +// (2+Overhead) + (size+Overhead) package shadow diff --git a/proxy/shadow/shadow.go b/proxy/shadow/shadow.go index 7c2be13..510da42 100644 --- a/proxy/shadow/shadow.go +++ b/proxy/shadow/shadow.go @@ -71,7 +71,7 @@ func (c *conn) Write(b []byte) (n int, err error) { } // BigEndian - c.writeBuf[0], c.writeBuf[1] = byte((dataSize&0xFF00)>>8), byte(dataSize&0xFF) + c.writeBuf[0], c.writeBuf[1] = byte(dataSize>>8), byte(dataSize) c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil) c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil) 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/proxy/socks5/socks5.go b/proxy/socks5/socks5.go index 610f79a..ac0bc67 100644 --- a/proxy/socks5/socks5.go +++ b/proxy/socks5/socks5.go @@ -4,19 +4,15 @@ import ( "encoding/binary" "io" "net" + "strconv" "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) - } + num, _ := strconv.Atoi(port) + bytes := []byte{byte(num >> 8), byte(num)} + return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: bytes} } type conn struct { diff --git a/proxy/util.go b/proxy/util.go index fc5116b..2210bb2 100644 --- a/proxy/util.go +++ b/proxy/util.go @@ -8,8 +8,6 @@ import ( "time" "github.com/golang/glog" - "github.com/wweir/sower/proxy/parser" - "github.com/wweir/sower/proxy/socks5" ) // race safe @@ -20,30 +18,13 @@ func resolveAddr(server *string) { if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil { glog.Errorln(err) } else { + glog.Infof("remote server (%s)=>(%s)", *server, addr) *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) 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 {