diff --git a/cmd/sower/main.go b/cmd/sower/main.go index 6a57fe3..eb4ff77 100644 --- a/cmd/sower/main.go +++ b/cmd/sower/main.go @@ -26,9 +26,9 @@ var ( conf = struct { Remote struct { - Type string `default:"sower" required:"true" usage:"remote proxy protocol, sower/trojan"` - Addr string `required:"true" usage:"remote proxy address, eg: proxy.com"` - Password string `required:"true" usage:"remote proxy password"` + Type string `default:"sower" required:"true" usage:"remote proxy protocol, optional: sower/trojan/socks5"` + Addr string `required:"true" usage:"remote proxy address, eg: proxy.com/127.0.0.1:7890"` + Password string `usage:"remote proxy password"` } DNS struct { diff --git a/cmd/sower/proxy.go b/cmd/sower/proxy.go index 2c96f6b..6ddc595 100644 --- a/cmd/sower/proxy.go +++ b/cmd/sower/proxy.go @@ -12,22 +12,37 @@ import ( "github.com/wweir/sower/pkg/teeconn" "github.com/wweir/sower/router" "github.com/wweir/sower/transport" + "github.com/wweir/sower/transport/socks5" "github.com/wweir/sower/transport/sower" "github.com/wweir/sower/transport/trojan" "github.com/wweir/sower/util" ) func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn { - var ( - proxyAddr = net.JoinHostPort(proxyHost, "443") - tlsCfg = &tls.Config{} - proxy transport.Transport - ) + var proxy transport.Transport + var dialFn func() (net.Conn, error) + switch conf.Remote.Type { case "sower": proxy = sower.New(conf.Remote.Password) + tlsCfg := &tls.Config{} + dialFn = func() (net.Conn, error) { + return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg) + } + case "trojan": proxy = trojan.New(conf.Remote.Password) + tlsCfg := &tls.Config{} + dialFn = func() (net.Conn, error) { + return tls.Dial("tcp", net.JoinHostPort(proxyHost, "443"), tlsCfg) + } + + case "socks5": + proxy = socks5.New() + dialFn = func() (net.Conn, error) { + return net.Dial("tcp", proxyHost) + } + default: log.Fatal(). Str("type", conf.Remote.Type). @@ -39,16 +54,17 @@ func GenProxyDial(proxyType, proxyHost, proxyPassword string) router.ProxyDialFn return nil, errors.Errorf("invalid addr(%s:%d)", host, port) } - c, err := tls.Dial("tcp", proxyAddr, tlsCfg) + conn, err := dialFn() if err != nil { return nil, err } - if err := proxy.Wrap(c, host, port); err != nil { + if err := proxy.Wrap(conn, host, port); err != nil { + conn.Close() return nil, err } - return c, nil + return conn, nil } } @@ -82,7 +98,7 @@ func ServeHTTP(ln net.Listener, r *router.Router) { teeconn.Stop().Reread() util.Relay(teeconn, rc) - log.Info(). + log.Debug(). Str("host", req.Host). Dur("spend", time.Since(start)). Msg("serve http") @@ -119,8 +135,29 @@ func ServeHTTPS(ln net.Listener, r *router.Router) { teeconn.Stop().Reread() util.Relay(teeconn, rc) - log.Info(). + log.Debug(). Str("host", domain). Dur("spend", time.Since(start)). Msg("serve http") } + +func ServeSocks5(ln net.Listener, r *router.Router) { + conn, err := ln.Accept() + if err != nil { + log.Fatal().Err(err). + Msg("serve socks5") + } + go ServeSocks5(ln, r) + defer conn.Close() + + addr, err := socks5.New().Unwrap(conn) + if err != nil { + log.Warn().Err(err). + Str("addr", addr.String()). + Msg("parse socks5 target") + return + } + + host, port := addr.(*socks5.AddrHead).Addr() + r.RouteHandle(conn, host, port) +} diff --git a/cmd/sowerd/main.go b/cmd/sowerd/main.go index d6b9da4..102bac3 100644 --- a/cmd/sowerd/main.go +++ b/cmd/sowerd/main.go @@ -109,7 +109,7 @@ func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *troj defer teeconn.Close() teeconn.Reread() - if addr := sower.Unwrap(teeconn); addr != nil { + if addr, err := sower.Unwrap(teeconn); err == nil { teeconn.Stop() dur, err := util.RelayTo(teeconn, addr.String()) @@ -121,7 +121,7 @@ func serve443(ln net.Listener, fakeSite string, sower *sower.Sower, trojan *troj } teeconn.Reread() - if addr := trojan.Unwrap(teeconn); addr != nil { + if addr, err := trojan.Unwrap(teeconn); err == nil { teeconn.Stop() dur, err := util.RelayTo(teeconn, addr.String()) diff --git a/go.mod b/go.mod index 64bc400..5807651 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.16 require ( github.com/cristalhq/aconfig v0.16.2 - github.com/cristalhq/aconfig/aconfigdotenv v0.16.1 github.com/cristalhq/aconfig/aconfighcl v0.16.1 github.com/cristalhq/aconfig/aconfigtoml v0.16.1 github.com/cristalhq/aconfig/aconfigyaml v0.16.1 diff --git a/go.sum b/go.sum index 42147a0..d6f65aa 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cristalhq/aconfig v0.16.1/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= github.com/cristalhq/aconfig v0.16.2 h1:cN+y3rtHyK9ar7NRiY4E+USs07P2qnZnGeZfNzXoV2g= github.com/cristalhq/aconfig v0.16.2/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= -github.com/cristalhq/aconfig/aconfigdotenv v0.16.1 h1:nsybrhIghZ2y0hQAijDlqih7KYgs03UujQ+GcyButLc= -github.com/cristalhq/aconfig/aconfigdotenv v0.16.1/go.mod h1:VQMzt9eS0Zlxwsuf/zkJuk1oZLm72cjpAkEkMrpbOm0= github.com/cristalhq/aconfig/aconfighcl v0.16.1 h1:RiziovFKOwB6+H1LCD7DXxrFgoKDEJxxYLSsAOsj+vQ= github.com/cristalhq/aconfig/aconfighcl v0.16.1/go.mod h1:YvZyVGtty/7yIdyOPPl68u7ENaUMq6ybZAph+n3g4II= github.com/cristalhq/aconfig/aconfigtoml v0.16.1 h1:q9DJfJqdLvZj7vL3bOIR6QM0ySpA0i6X8CZ8FNob6nc= @@ -18,8 +16,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk= github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU= diff --git a/router/router.go b/router/router.go index 3ad8d54..2974181 100644 --- a/router/router.go +++ b/router/router.go @@ -24,11 +24,11 @@ type Router struct { accessCache *mem.Cache dns struct { + dns.Client fallbackDNS string serveIP net.IP - dns.Client - connCh chan *dns.Conn - cache *mem.Cache + connCh chan *dns.Conn + cache *mem.Cache } country struct { @@ -102,7 +102,7 @@ func (r *Router) RouteHandle(conn net.Conn, domain string, port uint16) (err err Str("domain", domain). Uint16("port", port). Dur("spend", time.Since(start)). - Msg("RouteHandle") + Msg("serve socks5") }() addr := net.JoinHostPort(domain, strconv.FormatUint(uint64(port), 10)) diff --git a/cmd/sower/socks5.go b/transport/socks5/rfc.go similarity index 53% rename from cmd/sower/socks5.go rename to transport/socks5/rfc.go index 25df5e4..82925dc 100644 --- a/cmd/sower/socks5.go +++ b/transport/socks5/rfc.go @@ -1,84 +1,21 @@ -package main +package socks5 import ( "encoding/binary" "io" "net" - - "github.com/rs/zerolog/log" - "github.com/wweir/sower/router" ) -func ServeSocks5(ln net.Listener, r *router.Router) { - conn, err := ln.Accept() - if err != nil { - log.Fatal().Err(err). - Msg("serve socks5") - } - go ServeSocks5(ln, r) - defer conn.Close() - - { - auth := new(socks5AuthReq) - if err := auth.Fulfill(conn); err != nil { - log.Error().Err(err). - Interface("request", auth). - Msg("socks5 auth") - return - } - - if err := binary.Write(conn, binary.BigEndian, socks5AuthResp); err != nil { - log.Error().Err(err). - Msg("socks5 auth") - return - } - } - - var addr addrType - { - head := new(socks5HeadReq) - if err := binary.Read(conn, binary.BigEndian, head); err != nil || !head.IsValid() { - return - } - switch head.ATYP { - case 0x01: // IPv4 - addr = &addrTypeIPv4{} - case 0x03: // domain name - addr = &addrTypeDomain{} - case 0x04: // IPv6 - addr = &addrTypeIPv6{} - default: - log.Error().Err(err). - Interface("head", head). - Msg("socks5 connect") - return - } - - if err := addr.Fulfill(conn); err != nil { - return - } - - if err := binary.Write(conn, binary.BigEndian, socks5HeadResp); err != nil { - log.Error().Err(err). - Msg("socks5 head") - return - } - } - - host, port := addr.Addr() - r.RouteHandle(conn, host, port) -} - -/******************* https://tools.ietf.org/html/rfc1928 *******************/ +// https://tools.ietf.org/html/rfc1928 // 1. client send auth request -type socks5AuthReq struct { +type authReq struct { VER byte NMETHODS uint8 METHODS []byte } -func (req *socks5AuthReq) Fulfill(r io.Reader) error { +func (req *authReq) Fulfill(r io.Reader) error { buf := make([]byte, 2) if n, err := r.Read(buf); err != nil || n != 2 { return err @@ -95,39 +32,37 @@ func (req *socks5AuthReq) Fulfill(r io.Reader) error { return nil } -func (r *socks5AuthReq) IsValid() bool { +func (r *authReq) IsValid() bool { return r.VER == 5 && r.METHODS[0] == 0 } // 2. server response auth request -var socks5AuthResp = struct { +type authResp struct { VER byte METHOD byte -}{VER: 5, METHOD: 0} +} // 3. client request with target address -type socks5HeadReq struct { +type reqHead struct { VER byte CMD byte RSV byte ATYP byte } -func (r *socks5HeadReq) IsValid() bool { +func (r *reqHead) IsValid() bool { return r.VER == 5 && r.CMD == 1 } // 4. server response with the address that assigned to connect to target address -var socks5HeadResp = struct { - VER byte - REP byte - RSV byte - ATYP byte - BIND struct { - ADDR [4]byte - PORT uint16 - } -}{VER: 5, REP: 0, RSV: 0, ATYP: 1} +type respHead struct { + VER byte + REP byte + RSV byte + ATYP byte + BND_ADDR [net.IPv4len]byte + BND_PORT uint16 +} type addrType interface { Fulfill(r io.Reader) error @@ -150,18 +85,6 @@ func (a *addrTypeIPv4) Addr() (string, uint16) { return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT } -type addrTypeIPv6 struct { - DST_ADDR [16]byte - DST_PORT uint16 -} - -func (a *addrTypeIPv6) Fulfill(r io.Reader) error { - return binary.Read(r, binary.BigEndian, &a) -} -func (a *addrTypeIPv6) Addr() (string, uint16) { - return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT -} - type addrTypeDomain struct { DST_ADDR_LEN uint8 DST_ADDR []byte @@ -188,3 +111,15 @@ func (a *addrTypeDomain) Fulfill(r io.Reader) error { func (a *addrTypeDomain) Addr() (string, uint16) { return string(a.DST_ADDR[:]), a.DST_PORT } + +type addrTypeIPv6 struct { + DST_ADDR [16]byte + DST_PORT uint16 +} + +func (a *addrTypeIPv6) Fulfill(r io.Reader) error { + return binary.Read(r, binary.BigEndian, &a) +} +func (a *addrTypeIPv6) Addr() (string, uint16) { + return net.IP(a.DST_ADDR[:]).String(), a.DST_PORT +} diff --git a/transport/socks5/socks5.go b/transport/socks5/socks5.go new file mode 100644 index 0000000..c7d8c7f --- /dev/null +++ b/transport/socks5/socks5.go @@ -0,0 +1,115 @@ +package socks5 + +import ( + "bytes" + "encoding/binary" + "net" + "strconv" + + "github.com/pkg/errors" +) + +type AddrHead struct { + addrType +} + +func (h *AddrHead) Network() string { return "tcp" } +func (h *AddrHead) String() string { + host, port := h.Addr() + return net.JoinHostPort(host, strconv.Itoa(int(port))) +} + +// Socks5 is a SOCKS5 proxy. It implements the teeconn.Conn interface. +// It is used to be a second relay of other proxy tools. +// user -> sower -socks5-> third-party proxy -> target +type Socks5 struct { +} + +func New() *Socks5 { + return &Socks5{} +} + +var noAuthResp = authResp{VER: 5, METHOD: 0} +var succHeadResp = respHead{VER: 5, REP: 0, RSV: 0, ATYP: 1} + +func (s *Socks5) Unwrap(conn net.Conn) (net.Addr, error) { + { + auth := new(authReq) + if err := auth.Fulfill(conn); err != nil && !auth.IsValid() { + return nil, errors.Wrap(err, "read auth") + } + + if err := binary.Write(conn, binary.BigEndian, noAuthResp); err != nil { + return nil, errors.Wrap(err, "write auth") + } + } + + var addr addrType + { + head := new(reqHead) + if err := binary.Read(conn, binary.BigEndian, head); err != nil || !head.IsValid() { + return nil, errors.Wrap(err, "read head") + } + switch head.ATYP { + case 0x01: // IPv4 + addr = &addrTypeIPv4{} + case 0x03: // domain name + addr = &addrTypeDomain{} + case 0x04: // IPv6 + addr = &addrTypeIPv6{} + default: + return nil, errors.New("invalid ATYP") + } + + if err := addr.Fulfill(conn); err != nil { + return nil, errors.Wrap(err, "read target") + } + + if err := binary.Write(conn, binary.BigEndian, succHeadResp); err != nil { + return nil, errors.Wrap(err, "write head") + } + } + + return &AddrHead{ + addrType: addr, + }, nil +} + +var noAuthReq = struct { + VER byte + NMETHODS uint8 + METHODS byte +}{5, 1, 0} +var domainHead = reqHead{VER: 5, CMD: 1, RSV: 0, ATYP: 0x03} + +func (s *Socks5) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error { + { + if err := binary.Write(conn, binary.BigEndian, &noAuthReq); err != nil { + return errors.WithStack(err) + } + + resp := &authResp{} + if err := binary.Read(conn, binary.BigEndian, resp); err != nil { + return errors.WithStack(err) + } + } + + { + buf := bytes.NewBuffer(make([]byte, 0, binary.Size(domainHead)+1+len(tgtHost)+2)) + _ = binary.Write(buf, binary.BigEndian, domainHead) + buf.WriteByte(uint8(len(tgtHost))) + buf.WriteString(tgtHost) + buf.Write([]byte{byte(tgtPort >> 8), byte(tgtPort)}) + + if _, err := conn.Write(buf.Bytes()); err != nil { + return errors.WithStack(err) + } + + head := respHead{} + if err := binary.Read(conn, binary.BigEndian, &head); err != nil { + return errors.WithStack(err) + } + } + + return nil +} diff --git a/transport/sower/sower.go b/transport/sower/sower.go index cd393f6..4b5839f 100644 --- a/transport/sower/sower.go +++ b/transport/sower/sower.go @@ -7,7 +7,7 @@ import ( "net" "strconv" - "github.com/wweir/sower/pkg/teeconn" + "github.com/pkg/errors" ) // https://en.wikipedia.org/wiki/Domain_Name_System @@ -41,10 +41,10 @@ func New(password string) *Sower { } } -func (s *Sower) Unwrap(conn *teeconn.Conn) net.Addr { +func (s *Sower) Unwrap(conn net.Conn) (net.Addr, error) { buf := make([]byte, headSize) if n, err := conn.Read(buf); err != nil || n != headSize { - return nil + return nil, errors.Wrap(err, "read head") } h := &Head{} @@ -52,14 +52,14 @@ func (s *Sower) Unwrap(conn *teeconn.Conn) net.Addr { switch h.Cmd { case 0x80: default: - return nil + return nil, errors.Errorf("invalid command: %d", h.Cmd) } if h.Checksum != sumChecksum(h.TgtAddr, s.password) { - return nil + return nil, errors.New("auth fail") } - return h + return h, nil } func (s *Sower) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error { diff --git a/transport/transport.go b/transport/transport.go index e466041..82f5f09 100644 --- a/transport/transport.go +++ b/transport/transport.go @@ -2,11 +2,9 @@ package transport import ( "net" - - "github.com/wweir/sower/pkg/teeconn" ) type Transport interface { - Unwrap(conn *teeconn.Conn) net.Addr + Unwrap(conn net.Conn) (net.Addr, error) Wrap(conn net.Conn, tgtHost string, tgtPort uint16) error } diff --git a/transport/transport_test.go b/transport/transport_test.go index de7206c..44d17eb 100644 --- a/transport/transport_test.go +++ b/transport/transport_test.go @@ -15,7 +15,7 @@ func init() { log.Logger = log.Logger.With().Caller().Logger() } -func testPipe(tran Transport) net.Addr { +func testPipe(tran Transport) (net.Addr, error) { r, w := net.Pipe() defer r.Close() @@ -28,11 +28,11 @@ func testPipe(tran Transport) net.Addr { } func Test_Transports(t *testing.T) { - if addr := testPipe(newSower()); addr == nil || strings.TrimSpace(addr.String()) != "sower:443" { + if addr, err := testPipe(newSower()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" { t.Errorf("test sower, unexpected address: %s", addr) } - if addr := testPipe(newTrojan()); addr == nil || strings.TrimSpace(addr.String()) != "sower:443" { + if addr, err := testPipe(newTrojan()); err != nil || strings.TrimSpace(addr.String()) != "sower:443" { t.Errorf("test trojan, unexpected address: %s", addr) } } diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 517b089..a9c1d04 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -10,7 +10,6 @@ import ( "strconv" "github.com/pkg/errors" - "github.com/wweir/sower/pkg/teeconn" ) // +-----------------------+---------+----------------+---------+----------+ @@ -110,47 +109,41 @@ func New(password string) *Trojan { return t } -func (t *Trojan) Unwrap(conn *teeconn.Conn) net.Addr { +func (t *Trojan) Unwrap(conn net.Conn) (net.Addr, error) { buf := make([]byte, headLen) // do not use io.ReadFull to avoid hang if n, err := conn.Read(buf); err != nil || n != headLen { - return nil + return nil, errors.Wrap(err, "read head") } head := &staticHead{} if err := binary.Read(bytes.NewBuffer(buf), binary.BigEndian, head); err != nil { - return nil + return nil, errors.Wrap(err, "read head") } if !bytes.Equal(head.Passwd[:], []byte(t.headPasswd)) { - return nil + return nil, errors.New("auth fail") } head.CMD, head.ATYP = buf[58], buf[59] switch head.ATYP { case 0x01: //ipv4 addr := &ipv4Addr{} - if err := binary.Read(conn, binary.BigEndian, addr); err != nil { - return nil - } - return addr + err := binary.Read(conn, binary.BigEndian, addr) + return addr, errors.Wrap(err, "read addr") case 0x04: //ipv6 addr := &ipv6Addr{} - if err := binary.Read(conn, binary.BigEndian, addr); err != nil { - return nil - } - return addr + err := binary.Read(conn, binary.BigEndian, addr) + return addr, errors.Wrap(err, "read addr") case 0x03: // domain addr := &domain{} - if err := addr.Fulfill(conn); err != nil { - return nil - } - return addr + err := addr.Fulfill(conn) + return addr, errors.Wrap(err, "read addr") default: - return nil + return nil, errors.New("invalid ATYP") } }