diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 76be1286..6c58a848 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -21,6 +21,8 @@ func (err Error) Error() string { // Command is request commands as defined in RFC 1928 section 4. type Command = uint8 +const Version = 5 + // SOCKS request commands as defined in RFC 1928 section 4. const ( CmdConnect Command = 1 diff --git a/config/config.go b/config/config.go index 03216292..473e404c 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type General struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + MixedPort int `json:"mixed-port"` Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` @@ -97,6 +98,7 @@ type RawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` + MixedPort int `yaml:"mixed-port"` Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` BindAddress string `yaml:"bind-address"` @@ -217,6 +219,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { port := cfg.Port socksPort := cfg.SocksPort redirPort := cfg.RedirPort + mixedPort := cfg.MixedPort allowLan := cfg.AllowLan bindAddress := cfg.BindAddress externalController := cfg.ExternalController @@ -237,6 +240,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Port: port, SocksPort: socksPort, RedirPort: redirPort, + MixedPort: mixedPort, AllowLan: allowLan, BindAddress: bindAddress, Mode: mode, diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f073c1ce..ab848848 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -99,6 +99,7 @@ func GetGeneral() *config.General { Port: ports.Port, SocksPort: ports.SocksPort, RedirPort: ports.RedirPort, + MixedPort: ports.MixedPort, Authentication: authenticator, AllowLan: P.AllowLan(), BindAddress: P.BindAddress(), @@ -186,6 +187,10 @@ func updateGeneral(general *config.General) { if err := P.ReCreateRedir(general.RedirPort); err != nil { log.Errorln("Start Redir server error: %s", err.Error()) } + + if err := P.ReCreateMixed(general.MixedPort); err != nil { + log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) + } } func updateUsers(users []auth.AuthUser) { diff --git a/hub/route/configs.go b/hub/route/configs.go index dd97a840..c551d667 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -26,6 +26,7 @@ type configSchema struct { Port *int `json:"port"` SocksPort *int `json:"socks-port"` RedirPort *int `json:"redir-port"` + MixedPort *int `json:"mixed-port"` AllowLan *bool `json:"allow-lan"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` @@ -65,6 +66,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) + P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) if general.Mode != nil { tunnel.SetMode(*general.Mode) diff --git a/proxy/http/server.go b/proxy/http/server.go index de7e0fca..1791f62e 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) { } continue } - go handleConn(c, hl.cache) + go HandleConn(c, hl.cache) } }() @@ -69,7 +69,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache return } -func handleConn(conn net.Conn, cache *cache.Cache) { +func HandleConn(conn net.Conn, cache *cache.Cache) { br := bufio.NewReader(conn) request, err := http.ReadRequest(br) if err != nil || request.URL.Host == "" { diff --git a/proxy/listener.go b/proxy/listener.go index 1089e39d..5eda5426 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/mixed" "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" ) @@ -20,6 +21,8 @@ var ( httpListener *http.HttpListener redirListener *redir.RedirListener redirUDPListener *redir.RedirUDPListener + mixedListener *mixed.MixedListener + mixedUDPLister *socks.SockUDPListener ) type listener interface { @@ -31,6 +34,7 @@ type Ports struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + MixedPort int `json:"mixed-port"` } func AllowLan() bool { @@ -159,6 +163,52 @@ func ReCreateRedir(port int) error { return nil } +func ReCreateMixed(port int) error { + addr := genAddr(bindAddress, port, allowLan) + + shouldTCPIgnore := false + shouldUDPIgnore := false + + if mixedListener != nil { + if mixedListener.Address() != addr { + mixedListener.Close() + mixedListener = nil + } else { + shouldTCPIgnore = true + } + } + if mixedUDPLister != nil { + if mixedUDPLister.Address() != addr { + mixedUDPLister.Close() + mixedUDPLister = nil + } else { + shouldUDPIgnore = true + } + } + + if shouldTCPIgnore && shouldUDPIgnore { + return nil + } + + if portIsZero(addr) { + return nil + } + + var err error + mixedListener, err = mixed.NewMixedProxy(addr) + if err != nil { + return err + } + + mixedUDPLister, err = socks.NewSocksUDPProxy(addr) + if err != nil { + mixedListener.Close() + return err + } + + return nil +} + // GetPorts return the ports of proxy servers func GetPorts() *Ports { ports := &Ports{} @@ -181,6 +231,12 @@ func GetPorts() *Ports { ports.RedirPort = port } + if mixedListener != nil { + _, portStr, _ := net.SplitHostPort(mixedListener.Address()) + port, _ := strconv.Atoi(portStr) + ports.MixedPort = port + } + return ports } diff --git a/proxy/mixed/conn.go b/proxy/mixed/conn.go new file mode 100644 index 00000000..f009a006 --- /dev/null +++ b/proxy/mixed/conn.go @@ -0,0 +1,41 @@ +package mixed + +import ( + "bufio" + "net" +) + +type BufferedConn struct { + r *bufio.Reader + net.Conn +} + +func NewBufferedConn(c net.Conn) *BufferedConn { + return &BufferedConn{bufio.NewReader(c), c} +} + +// Reader returns the internal bufio.Reader. +func (c *BufferedConn) Reader() *bufio.Reader { + return c.r +} + +// Peek returns the next n bytes without advancing the reader. +func (c *BufferedConn) Peek(n int) ([]byte, error) { + return c.r.Peek(n) +} + +func (c *BufferedConn) Read(p []byte) (int, error) { + return c.r.Read(p) +} + +func (c *BufferedConn) ReadByte() (byte, error) { + return c.r.ReadByte() +} + +func (c *BufferedConn) UnreadByte() error { + return c.r.UnreadByte() +} + +func (c *BufferedConn) Buffered() int { + return c.r.Buffered() +} diff --git a/proxy/mixed/mixed.go b/proxy/mixed/mixed.go new file mode 100644 index 00000000..3328724e --- /dev/null +++ b/proxy/mixed/mixed.go @@ -0,0 +1,69 @@ +package mixed + +import ( + "net" + "time" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/log" + + "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/socks" +) + +type MixedListener struct { + net.Listener + address string + closed bool + cache *cache.Cache +} + +func NewMixedProxy(addr string) (*MixedListener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)} + go func() { + log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr) + + for { + c, err := ml.Accept() + if err != nil { + if ml.closed { + break + } + continue + } + go handleConn(c, ml.cache) + } + }() + + return ml, nil +} + +func (l *MixedListener) Close() { + l.closed = true + l.Listener.Close() +} + +func (l *MixedListener) Address() string { + return l.address +} + +func handleConn(conn net.Conn, cache *cache.Cache) { + bufConn := NewBufferedConn(conn) + head, err := bufConn.Peek(1) + if err != nil { + return + } + + if head[0] == socks5.Version { + socks.HandleSocks(bufConn) + return + } + + http.HandleConn(bufConn, cache) +} diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 50317177..0f6c4fb4 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -36,7 +36,7 @@ func NewSocksProxy(addr string) (*SockListener, error) { } continue } - go handleSocks(c) + go HandleSocks(c) } }() @@ -52,13 +52,15 @@ func (l *SockListener) Address() string { return l.address } -func handleSocks(conn net.Conn) { +func HandleSocks(conn net.Conn) { target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) if err != nil { conn.Close() return } - conn.(*net.TCPConn).SetKeepAlive(true) + if c, ok := conn.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } if command == socks5.CmdUDPAssociate { defer conn.Close() io.Copy(ioutil.Discard, conn)