diff --git a/constant/listener.go b/constant/listener.go index 07782a9e..08a590bd 100644 --- a/constant/listener.go +++ b/constant/listener.go @@ -1,7 +1,15 @@ package constant +import "net" + type Listener interface { RawAddress() string Address() string Close() error } + +type AdvanceListener interface { + Close() + Config() string + HandleConn(conn net.Conn, in chan<- ConnContext) +} diff --git a/listener/listener.go b/listener/listener.go index c9e95226..880f56d4 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -41,7 +41,7 @@ var ( mixedListener *mixed.Listener mixedUDPLister *socks.UDPListener tunLister *sing_tun.Listener - shadowSocksListener *sing_shadowsocks.Listener + shadowSocksListener C.AdvanceListener vmessListener *sing_vmess.Listener tcpTunListener *tunnel.Listener udpTunListener *tunnel.UdpListener diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go new file mode 100644 index 00000000..d767b8ab --- /dev/null +++ b/listener/shadowsocks/tcp.go @@ -0,0 +1,105 @@ +package shadowsocks + +import ( + "net" + "strings" + + "github.com/Dreamacro/clash/adapter/inbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/shadowsocks/core" + "github.com/Dreamacro/clash/transport/socks5" +) + +type Listener struct { + closed bool + config string + listeners []net.Listener + udpListeners []*UDPListener + pickCipher core.Cipher +} + +var _listener *Listener + +func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) { + addr, cipher, password, err := parseSSURL(config) + if err != nil { + return nil, err + } + + pickCipher, err := core.PickCipher(cipher, nil, password) + if err != nil { + return nil, err + } + + sl := &Listener{false, config, nil, nil, pickCipher} + _listener = sl + + for _, addr := range strings.Split(addr, ",") { + addr := addr + + //UDP + ul, err := NewUDP(addr, pickCipher, udpIn) + if err != nil { + return nil, err + } + sl.udpListeners = append(sl.udpListeners, ul) + + //TCP + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + sl.listeners = append(sl.listeners, l) + + go func() { + log.Infoln("ShadowSocks proxy listening at: %s", l.Addr().String()) + for { + c, err := l.Accept() + if err != nil { + if sl.closed { + break + } + continue + } + _ = c.(*net.TCPConn).SetKeepAlive(true) + go sl.HandleConn(c, tcpIn) + } + }() + } + + return sl, nil +} + +func (l *Listener) Close() { + l.closed = true + for _, lis := range l.listeners { + _ = lis.Close() + } + for _, lis := range l.udpListeners { + _ = lis.Close() + } +} + +func (l *Listener) Config() string { + return l.config +} + +func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) { + conn = l.pickCipher.StreamConn(conn) + + target, err := socks5.ReadAddr(conn, make([]byte, socks5.MaxAddrLen)) + if err != nil { + _ = conn.Close() + return + } + in <- inbound.NewSocket(target, conn, C.SHADOWSOCKS) +} + +func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext) bool { + if _listener != nil && _listener.pickCipher != nil { + go _listener.HandleConn(conn, in) + return true + } + return false +} diff --git a/listener/shadowsocks/udp.go b/listener/shadowsocks/udp.go new file mode 100644 index 00000000..efb29e41 --- /dev/null +++ b/listener/shadowsocks/udp.go @@ -0,0 +1,76 @@ +package shadowsocks + +import ( + "net" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/common/sockopt" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/transport/shadowsocks/core" + "github.com/Dreamacro/clash/transport/socks5" +) + +type UDPListener struct { + packetConn net.PacketConn + closed bool +} + +func NewUDP(addr string, pickCipher core.Cipher, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { + l, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) + if err != nil { + log.Warnln("Failed to Reuse UDP Address: %s", err) + } + + sl := &UDPListener{l, false} + conn := pickCipher.PacketConn(l) + go func() { + for { + buf := pool.Get(pool.RelayBufferSize) + n, remoteAddr, err := conn.ReadFrom(buf) + if err != nil { + pool.Put(buf) + if sl.closed { + break + } + continue + } + handleSocksUDP(conn, in, buf[:n], remoteAddr) + } + }() + + return sl, nil +} + +func (l *UDPListener) Close() error { + l.closed = true + return l.packetConn.Close() +} + +func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { + tgtAddr := socks5.SplitAddr(buf) + if tgtAddr == nil { + // Unresolved UDP packet, return buffer to the pool + pool.Put(buf) + return + } + target := socks5.ParseAddr(tgtAddr.String()) + payload := buf[len(tgtAddr):] + + packet := &packet{ + pc: pc, + rAddr: addr, + payload: payload, + bufRef: buf, + } + select { + case in <- inbound.NewPacket(target, packet, C.SHADOWSOCKS): + default: + } +} diff --git a/listener/shadowsocks/utils.go b/listener/shadowsocks/utils.go new file mode 100644 index 00000000..f1710c94 --- /dev/null +++ b/listener/shadowsocks/utils.go @@ -0,0 +1,59 @@ +package shadowsocks + +import ( + "bytes" + "errors" + "net" + "net/url" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/socks5" +) + +type packet struct { + pc net.PacketConn + rAddr net.Addr + payload []byte + bufRef []byte +} + +func (c *packet) Data() []byte { + return c.payload +} + +// WriteBack wirtes UDP packet with source(ip, port) = `addr` +func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { + if addr == nil { + err = errors.New("address is invalid") + return + } + packet := bytes.Join([][]byte{socks5.ParseAddrToSocksAddr(addr), b}, []byte{}) + return c.pc.WriteTo(packet, c.rAddr) +} + +// LocalAddr returns the source IP/Port of UDP Packet +func (c *packet) LocalAddr() net.Addr { + return c.rAddr +} + +func (c *packet) Drop() { + pool.Put(c.bufRef) +} + +func (c *packet) InAddr() net.Addr { + return c.pc.LocalAddr() +} + +func parseSSURL(s string) (addr, cipher, password string, err error) { + u, err := url.Parse(s) + if err != nil { + return + } + + addr = u.Host + if u.User != nil { + cipher = u.User.Username() + password, _ = u.User.Password() + } + return +} diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index cc2c840e..7bb9bb54 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -10,6 +10,7 @@ import ( "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/sockopt" C "github.com/Dreamacro/clash/constant" + embedSS "github.com/Dreamacro/clash/listener/shadowsocks" "github.com/Dreamacro/clash/listener/sing" "github.com/Dreamacro/clash/log" @@ -32,7 +33,7 @@ type Listener struct { var _listener *Listener -func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) { +func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (C.AdvanceListener, error) { addr, cipher, password, err := parseSSURL(config) if err != nil { return nil, err @@ -56,6 +57,7 @@ func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.Packet sl.service, err = shadowaead_2022.NewServiceWithPassword(cipher, password, udpTimeout, h) default: err = fmt.Errorf("shadowsocks: unsupported method: %s", cipher) + return embedSS.New(config, tcpIn, udpIn) } if err != nil { return nil, err @@ -117,7 +119,7 @@ func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.Packet } _ = c.(*net.TCPConn).SetKeepAlive(true) - go sl.HandleConn(c) + go sl.HandleConn(c, tcpIn) } }() } @@ -139,7 +141,7 @@ func (l *Listener) Config() string { return l.config } -func (l *Listener) HandleConn(conn net.Conn) { +func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) { err := l.service.NewConnection(context.TODO(), conn, metadata.Metadata{ Protocol: "shadowsocks", Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()), @@ -150,12 +152,12 @@ func (l *Listener) HandleConn(conn net.Conn) { } } -func HandleShadowSocks(conn net.Conn) bool { +func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext) bool { if _listener != nil && _listener.service != nil { - go _listener.HandleConn(conn) + go _listener.HandleConn(conn, in) return true } - return false + return embedSS.HandleShadowSocks(conn, in) } func parseSSURL(s string) (addr, cipher, password string, err error) { diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index c8d0a2a1..26b12893 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -72,7 +72,7 @@ func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.Packet } _ = c.(*net.TCPConn).SetKeepAlive(true) - go sl.HandleConn(c) + go sl.HandleConn(c, tcpIn) } }() } @@ -92,7 +92,7 @@ func (l *Listener) Config() string { return l.config } -func (l *Listener) HandleConn(conn net.Conn) { +func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) { err := l.service.NewConnection(context.TODO(), conn, metadata.Metadata{ Protocol: "vmess", Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()), @@ -103,9 +103,9 @@ func (l *Listener) HandleConn(conn net.Conn) { } } -func HandleVmess(conn net.Conn) bool { +func HandleVmess(conn net.Conn, in chan<- C.ConnContext) bool { if _listener != nil && _listener.service != nil { - go _listener.HandleConn(conn) + go _listener.HandleConn(conn, in) return true } return false