diff --git a/.golangci.yml b/.golangci.yml index db02a3c9..012d08a1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,7 @@ linters: enable: - gofumpt - govet -# - gci + - gci - staticcheck - paralleltest diff --git a/adapter/outbound.go b/adapter/outbound.go index 1bfd8ba8..a45c27fd 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -7,6 +7,8 @@ import ( N "github.com/sagernet/sing/common/network" ) +// Note: for proxy protocols, outbound creates early connections by default. + type Outbound interface { Type() string Tag() string diff --git a/box.go b/box.go index 790649b3..bbfde850 100644 --- a/box.go +++ b/box.go @@ -124,6 +124,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) { tag = F.ToString(i) } out, err = outbound.New( + ctx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), outboundOptions) @@ -133,7 +134,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) { outbounds = append(outbounds, out) } err = router.Initialize(outbounds, func() adapter.Outbound { - out, oErr := outbound.New(router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"}) + out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"}) common.Must(oErr) outbounds = append(outbounds, out) return out diff --git a/common/debugio/log.go b/common/debugio/log.go new file mode 100644 index 00000000..77b13ac8 --- /dev/null +++ b/common/debugio/log.go @@ -0,0 +1,87 @@ +package debugio + +import ( + "net" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type LogConn struct { + N.ExtendedConn + logger log.Logger + prefix string +} + +func NewLogConn(conn net.Conn, logger log.Logger, prefix string) N.ExtendedConn { + return &LogConn{bufio.NewExtendedConn(conn), logger, prefix} +} + +func (c *LogConn) Read(p []byte) (n int, err error) { + n, err = c.ExtendedConn.Read(p) + if n > 0 { + c.logger.Debug(c.prefix, " read ", buf.EncodeHexString(p[:n])) + } + return +} + +func (c *LogConn) Write(p []byte) (n int, err error) { + c.logger.Debug(c.prefix, " write ", buf.EncodeHexString(p)) + return c.ExtendedConn.Write(p) +} + +func (c *LogConn) ReadBuffer(buffer *buf.Buffer) error { + err := c.ExtendedConn.ReadBuffer(buffer) + if err == nil { + c.logger.Debug(c.prefix, " read buffer ", buf.EncodeHexString(buffer.Bytes())) + } + return err +} + +func (c *LogConn) WriteBuffer(buffer *buf.Buffer) error { + c.logger.Debug(c.prefix, " write buffer ", buf.EncodeHexString(buffer.Bytes())) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *LogConn) Upstream() any { + return c.ExtendedConn +} + +type LogPacketConn struct { + N.NetPacketConn + logger log.Logger + prefix string +} + +func NewLogPacketConn(conn net.PacketConn, logger log.Logger, prefix string) N.NetPacketConn { + return &LogPacketConn{bufio.NewPacketConn(conn), logger, prefix} +} + +func (c *LogPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, addr, err = c.NetPacketConn.ReadFrom(p) + if n > 0 { + c.logger.Debug(c.prefix, " read from ", addr, " ", buf.EncodeHexString(p[:n])) + } + return +} + +func (c *LogPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + c.logger.Debug(c.prefix, " write to ", addr, " ", buf.EncodeHexString(p)) + return c.NetPacketConn.WriteTo(p, addr) +} + +func (c *LogPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + destination, err = c.NetPacketConn.ReadPacket(buffer) + if err == nil { + c.logger.Debug(c.prefix, " read packet from ", destination, " ", buf.EncodeHexString(buffer.Bytes())) + } + return +} + +func (c *LogPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + c.logger.Debug(c.prefix, " write packet to ", destination, " ", buf.EncodeHexString(buffer.Bytes())) + return c.NetPacketConn.WritePacket(buffer, destination) +} diff --git a/common/debugio/race.go b/common/debugio/race.go new file mode 100644 index 00000000..813a4326 --- /dev/null +++ b/common/debugio/race.go @@ -0,0 +1,48 @@ +package debugio + +import ( + "net" + "sync" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + N "github.com/sagernet/sing/common/network" +) + +type RaceConn struct { + N.ExtendedConn + readAccess sync.Mutex + writeAccess sync.Mutex +} + +func NewRaceConn(conn net.Conn) N.ExtendedConn { + return &RaceConn{ExtendedConn: bufio.NewExtendedConn(conn)} +} + +func (c *RaceConn) Read(p []byte) (n int, err error) { + c.readAccess.Lock() + defer c.readAccess.Unlock() + return c.ExtendedConn.Read(p) +} + +func (c *RaceConn) Write(p []byte) (n int, err error) { + c.writeAccess.Lock() + defer c.writeAccess.Unlock() + return c.ExtendedConn.Write(p) +} + +func (c *RaceConn) ReadBuffer(buffer *buf.Buffer) error { + c.readAccess.Lock() + defer c.readAccess.Unlock() + return c.ExtendedConn.ReadBuffer(buffer) +} + +func (c *RaceConn) WriteBuffer(buffer *buf.Buffer) error { + c.writeAccess.Lock() + defer c.writeAccess.Unlock() + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *RaceConn) Upstream() any { + return c.ExtendedConn +} diff --git a/common/dialer/default.go b/common/dialer/default.go index a068a105..6cb00139 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -12,6 +12,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" "github.com/database64128/tfo-go" ) @@ -124,7 +125,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return d.ListenConfig.ListenPacket(ctx, C.NetworkUDP, "") + return d.ListenConfig.ListenPacket(ctx, N.NetworkUDP, "") } func (d *DefaultDialer) Upstream() any { diff --git a/common/dialer/router.go b/common/dialer/router.go index 4f82c16c..1d558654 100644 --- a/common/dialer/router.go +++ b/common/dialer/router.go @@ -5,7 +5,6 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -23,7 +22,7 @@ func (d *RouterDialer) DialContext(ctx context.Context, network string, destinat } func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return d.router.DefaultOutbound(C.NetworkUDP).ListenPacket(ctx, destination) + return d.router.DefaultOutbound(N.NetworkUDP).ListenPacket(ctx, destination) } func (d *RouterDialer) Upstream() any { diff --git a/common/dialer/tls.go b/common/dialer/tls.go index 6423d78a..782bc0c2 100644 --- a/common/dialer/tls.go +++ b/common/dialer/tls.go @@ -112,7 +112,7 @@ func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOpt } func (d *TLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if network != C.NetworkTCP { + if network != N.NetworkTCP { return nil, os.ErrInvalid } conn, err := d.dialer.DialContext(ctx, network, destination) diff --git a/common/mux/client.go b/common/mux/client.go new file mode 100644 index 00000000..b39e1bbc --- /dev/null +++ b/common/mux/client.go @@ -0,0 +1,476 @@ +package mux + +import ( + "context" + "encoding/binary" + "io" + "net" + "sync" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/x/list" + + "github.com/hashicorp/yamux" +) + +var _ N.Dialer = (*Client)(nil) + +type Client struct { + access sync.Mutex + connections list.List[*yamux.Session] + ctx context.Context + dialer N.Dialer + maxConnections int + minStreams int + maxStreams int +} + +func NewClient(ctx context.Context, dialer N.Dialer, maxConnections int, minStreams int, maxStreams int) *Client { + return &Client{ + ctx: ctx, + dialer: dialer, + maxConnections: maxConnections, + minStreams: minStreams, + maxStreams: maxStreams, + } +} + +func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) N.Dialer { + if !options.Enabled { + return dialer + } + if options.MaxConnections == 0 && options.MaxStreams == 0 { + options.MinStreams = 8 + } + return NewClient(ctx, dialer, options.MaxConnections, options.MinStreams, options.MaxStreams) +} + +func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + stream, err := c.openStream() + if err != nil { + return nil, err + } + return &ClientConn{Conn: stream, destination: destination}, nil + case N.NetworkUDP: + stream, err := c.openStream() + if err != nil { + return nil, err + } + return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + stream, err := c.openStream() + if err != nil { + return nil, err + } + // return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil + return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil +} + +func (c *Client) openStream() (net.Conn, error) { + session, err := c.offer() + if err != nil { + return nil, err + } + conn, err := session.Open() + if err != nil { + return nil, err + } + return conn, nil +} + +func (c *Client) offer() (*yamux.Session, error) { + c.access.Lock() + defer c.access.Unlock() + + sessions := make([]*yamux.Session, 0, c.maxConnections) + for element := c.connections.Front(); element != nil; { + if element.Value.IsClosed() { + nextElement := element.Next() + c.connections.Remove(element) + element = nextElement + continue + } + sessions = append(sessions, element.Value) + element = element.Next() + } + sLen := len(sessions) + if sLen == 0 { + return c.offerNew() + } + // session := common.MinBy(sessions, yamux.Session.NumStreams) + session := common.MinBy(sessions, func(it *yamux.Session) int { + return it.NumStreams() + }) + numStreams := session.NumStreams() + if numStreams == 0 { + return session, nil + } + if c.maxConnections > 0 { + if sLen >= c.maxConnections || numStreams < c.minStreams { + return session, nil + } + } else { + if c.maxStreams > 0 && numStreams < c.maxStreams { + return session, nil + } + } + return c.offerNew() +} + +func (c *Client) offerNew() (*yamux.Session, error) { + conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination) + if err != nil { + return nil, err + } + session, err := yamux.Client(conn, newMuxConfig()) + if err != nil { + return nil, err + } + c.connections.PushBack(session) + return session, nil +} + +func (c *Client) Close() error { + c.access.Lock() + defer c.access.Unlock() + for _, session := range c.connections.Array() { + session.Close() + } + return nil +} + +type ClientConn struct { + net.Conn + destination M.Socksaddr + requestWrite bool + responseRead bool +} + +func (c *ClientConn) readResponse() error { + response, err := ReadResponse(c.Conn) + if err != nil { + return err + } + if response.Status == statusError { + return E.New("remote error: ", response.Message) + } + return nil +} + +func (c *ClientConn) Read(b []byte) (n int, err error) { + if !c.responseRead { + err = c.readResponse() + if err != nil { + return + } + c.responseRead = true + } + return c.Conn.Read(b) +} + +func (c *ClientConn) Write(b []byte) (n int, err error) { + if c.requestWrite { + return c.Conn.Write(b) + } + request := Request{ + Network: N.NetworkTCP, + Destination: c.destination, + } + _buffer := buf.StackNewSize(requestLen(request) + len(b)) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + EncodeRequest(request, buffer) + buffer.Write(b) + _, err = c.Conn.Write(buffer.Bytes()) + if err != nil { + return + } + c.requestWrite = true + return len(b), nil +} + +func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) { + if !c.requestWrite { + return bufio.ReadFrom0(c, r) + } + return bufio.Copy(c.Conn, r) +} + +func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) { + if !c.responseRead { + return bufio.WriteTo0(c, w) + } + return bufio.Copy(w, c.Conn) +} + +func (c *ClientConn) LocalAddr() net.Addr { + return c.Conn.LocalAddr() +} + +func (c *ClientConn) RemoteAddr() net.Addr { + return c.destination.TCPAddr() +} + +func (c *ClientConn) ReaderReplaceable() bool { + return c.responseRead +} + +func (c *ClientConn) WriterReplaceable() bool { + return c.requestWrite +} + +func (c *ClientConn) Upstream() any { + return c.Conn +} + +type ClientPacketConn struct { + N.ExtendedConn + destination M.Socksaddr + requestWrite bool + responseRead bool +} + +func (c *ClientPacketConn) readResponse() error { + response, err := ReadResponse(c.ExtendedConn) + if err != nil { + return err + } + if response.Status == statusError { + return E.New("remote error: ", response.Message) + } + return nil +} + +func (c *ClientPacketConn) Read(b []byte) (n int, err error) { + if !c.responseRead { + err = c.readResponse() + if err != nil { + return + } + c.responseRead = true + } + var length uint16 + err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) + if err != nil { + return + } + if cap(b) < int(length) { + return 0, io.ErrShortBuffer + } + return io.ReadFull(c.ExtendedConn, b[:length]) +} + +func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) { + request := Request{ + Network: N.NetworkUDP, + Destination: c.destination, + } + rLen := requestLen(request) + if len(payload) > 0 { + rLen += 2 + len(payload) + } + _buffer := buf.StackNewSize(rLen) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + EncodeRequest(request, buffer) + if len(payload) > 0 { + common.Must( + binary.Write(buffer, binary.BigEndian, uint16(len(payload))), + common.Error(buffer.Write(payload)), + ) + } + _, err = c.ExtendedConn.Write(buffer.Bytes()) + if err != nil { + return + } + c.requestWrite = true + return len(payload), nil +} + +func (c *ClientPacketConn) Write(b []byte) (n int, err error) { + if !c.requestWrite { + return c.writeRequest(b) + } + err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b))) + if err != nil { + return + } + return c.ExtendedConn.Write(b) +} + +func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error { + if !c.requestWrite { + defer buffer.Release() + return common.Error(c.writeRequest(buffer.Bytes())) + } + bLen := buffer.Len() + binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen)) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + return c.WriteBuffer(buffer) +} + +func (c *ClientPacketConn) LocalAddr() net.Addr { + return c.ExtendedConn.LocalAddr() +} + +func (c *ClientPacketConn) RemoteAddr() net.Addr { + return c.destination.UDPAddr() +} + +func (c *ClientPacketConn) Upstream() any { + return c.ExtendedConn +} + +var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil) + +type ClientPacketAddrConn struct { + N.ExtendedConn + destination M.Socksaddr + requestWrite bool + responseRead bool +} + +func (c *ClientPacketAddrConn) readResponse() error { + response, err := ReadResponse(c.ExtendedConn) + if err != nil { + return err + } + if response.Status == statusError { + return E.New("remote error: ", response.Message) + } + return nil +} + +func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + if !c.responseRead { + err = c.readResponse() + if err != nil { + return + } + c.responseRead = true + } + destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn) + if err != nil { + return + } + addr = destination.UDPAddr() + var length uint16 + err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) + if err != nil { + return + } + if cap(p) < int(length) { + return 0, nil, io.ErrShortBuffer + } + n, err = io.ReadFull(c.ExtendedConn, p[:length]) + return +} + +func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) { + request := Request{ + Network: N.NetworkUDP, + Destination: c.destination, + PacketAddr: true, + } + rLen := requestLen(request) + if len(payload) > 0 { + rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload) + } + _buffer := buf.StackNewSize(rLen) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + EncodeRequest(request, buffer) + if len(payload) > 0 { + common.Must( + M.SocksaddrSerializer.WriteAddrPort(buffer, destination), + binary.Write(buffer, binary.BigEndian, uint16(len(payload))), + common.Error(buffer.Write(payload)), + ) + } + _, err = c.ExtendedConn.Write(buffer.Bytes()) + if err != nil { + return + } + c.requestWrite = true + return len(payload), nil +} + +func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + if !c.requestWrite { + return c.writeRequest(p, M.SocksaddrFromNet(addr)) + } + err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr)) + if err != nil { + return + } + err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p))) + if err != nil { + return + } + return c.ExtendedConn.Write(p) +} + +func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + if !c.responseRead { + err = c.readResponse() + if err != nil { + return + } + c.responseRead = true + } + destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn) + if err != nil { + return + } + var length uint16 + err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) + if err != nil { + return + } + if buffer.FreeLen() < int(length) { + return destination, io.ErrShortBuffer + } + _, err = io.ReadFull(c.ExtendedConn, buffer.Extend(int(length))) + return +} + +func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + if !c.requestWrite { + defer buffer.Release() + return common.Error(c.writeRequest(buffer.Bytes(), destination)) + } + bLen := buffer.Len() + header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2)) + common.Must( + M.SocksaddrSerializer.WriteAddrPort(header, destination), + binary.Write(header, binary.BigEndian, uint16(bLen)), + ) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *ClientPacketAddrConn) LocalAddr() net.Addr { + return c.ExtendedConn.LocalAddr() +} + +func (c *ClientPacketAddrConn) Upstream() any { + return c.ExtendedConn +} diff --git a/common/mux/protocol.go b/common/mux/protocol.go new file mode 100644 index 00000000..00df1823 --- /dev/null +++ b/common/mux/protocol.go @@ -0,0 +1,119 @@ +package mux + +import ( + "encoding/binary" + "io" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/rw" + + "github.com/hashicorp/yamux" +) + +var Destination = M.Socksaddr{ + Fqdn: "sp.mux.sing-box.arpa", + Port: 444, +} + +func newMuxConfig() *yamux.Config { + config := yamux.DefaultConfig() + config.LogOutput = io.Discard + config.StreamCloseTimeout = C.TCPTimeout + config.StreamOpenTimeout = C.TCPTimeout + return config +} + +const ( + version0 = 0 + flagUDP = 1 + flagAddr = 2 + statusSuccess = 0 + statusError = 1 +) + +type Request struct { + Network string + Destination M.Socksaddr + PacketAddr bool +} + +func ReadRequest(reader io.Reader) (*Request, error) { + version, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + if version != version0 { + return nil, E.New("unsupported version: ", version) + } + var flags uint16 + err = binary.Read(reader, binary.BigEndian, &flags) + if err != nil { + return nil, err + } + destination, err := M.SocksaddrSerializer.ReadAddrPort(reader) + if err != nil { + return nil, err + } + var network string + var udpAddr bool + if flags&flagUDP == 0 { + network = N.NetworkTCP + } else { + network = N.NetworkUDP + udpAddr = flags&flagAddr != 0 + } + return &Request{network, destination, udpAddr}, nil +} + +func requestLen(request Request) int { + var rLen int + rLen += 1 // version + rLen += 2 // flags + rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination) + return rLen +} + +func EncodeRequest(request Request, buffer *buf.Buffer) { + destination := request.Destination + var flags uint16 + if request.Network == N.NetworkUDP { + flags |= flagUDP + } + if request.PacketAddr { + flags |= flagAddr + if !destination.IsValid() { + destination = Destination + } + } + common.Must( + buffer.WriteByte(version0), + binary.Write(buffer, binary.BigEndian, flags), + M.SocksaddrSerializer.WriteAddrPort(buffer, destination), + ) +} + +type Response struct { + Status uint8 + Message string +} + +func ReadResponse(reader io.Reader) (*Response, error) { + var response Response + status, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + response.Status = status + if status == statusError { + response.Message, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + } + return &response, nil +} diff --git a/common/mux/service.go b/common/mux/service.go new file mode 100644 index 00000000..21b6d1c3 --- /dev/null +++ b/common/mux/service.go @@ -0,0 +1,210 @@ +package mux + +import ( + "context" + "encoding/binary" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/rw" + + "github.com/hashicorp/yamux" +) + +func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error { + session, err := yamux.Server(conn, newMuxConfig()) + if err != nil { + return err + } + for { + stream, err := session.Accept() + if err != nil { + return err + } + request, err := ReadRequest(stream) + if err != nil { + return err + } + metadata.Destination = request.Destination + if request.Network == N.NetworkTCP { + go func() { + logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination) + hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata) + // hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata) + if hErr != nil { + errorHandler.NewError(ctx, hErr) + } + }() + } else { + go func() { + var packetConn N.PacketConn + if !request.PacketAddr { + logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination) + packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination} + } else { + logger.InfoContext(ctx, "inbound multiplex packet connection") + packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)} + } + hErr := router.RoutePacketConnection(ctx, packetConn, metadata) + if hErr != nil { + errorHandler.NewError(ctx, hErr) + } + }() + } + } +} + +var _ N.HandshakeConn = (*ServerConn)(nil) + +type ServerConn struct { + N.ExtendedConn + responseWrite bool +} + +func (c *ServerConn) HandshakeFailure(err error) error { + errMessage := err.Error() + _buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage)) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + common.Must( + buffer.WriteByte(statusError), + rw.WriteVString(_buffer, errMessage), + ) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *ServerConn) Write(b []byte) (n int, err error) { + if c.responseWrite { + return c.ExtendedConn.Write(b) + } + _buffer := buf.StackNewSize(1 + len(b)) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + common.Must( + buffer.WriteByte(statusSuccess), + common.Error(buffer.Write(b)), + ) + _, err = c.ExtendedConn.Write(buffer.Bytes()) + if err != nil { + return + } + c.responseWrite = true + return len(b), nil +} + +func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error { + if c.responseWrite { + return c.ExtendedConn.WriteBuffer(buffer) + } + buffer.ExtendHeader(1)[0] = statusSuccess + c.responseWrite = true + return c.ExtendedConn.WriteBuffer(buffer) +} + +var ( + _ N.HandshakeConn = (*ServerPacketConn)(nil) + _ N.PacketConn = (*ServerPacketConn)(nil) +) + +type ServerPacketConn struct { + N.ExtendedConn + destination M.Socksaddr + responseWrite bool +} + +func (c *ServerPacketConn) HandshakeFailure(err error) error { + errMessage := err.Error() + _buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage)) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + common.Must( + buffer.WriteByte(statusError), + rw.WriteVString(_buffer, errMessage), + ) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + var length uint16 + err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) + if err != nil { + return + } + _, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) + if err != nil { + return + } + destination = c.destination + return +} + +func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + pLen := buffer.Len() + common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen))) + if !c.responseWrite { + buffer.ExtendHeader(1)[0] = statusSuccess + c.responseWrite = true + } + return c.ExtendedConn.WriteBuffer(buffer) +} + +var ( + _ N.HandshakeConn = (*ServerPacketAddrConn)(nil) + _ N.PacketConn = (*ServerPacketAddrConn)(nil) +) + +type ServerPacketAddrConn struct { + N.ExtendedConn + responseWrite bool +} + +func (c *ServerPacketAddrConn) HandshakeFailure(err error) error { + errMessage := err.Error() + _buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage)) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + common.Must( + buffer.WriteByte(statusError), + rw.WriteVString(_buffer, errMessage), + ) + return c.ExtendedConn.WriteBuffer(buffer) +} + +func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn) + if err != nil { + return + } + var length uint16 + err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) + if err != nil { + return + } + _, err = buffer.ReadFullFrom(c.ExtendedConn, int(length)) + if err != nil { + return + } + return +} + +func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + pLen := buffer.Len() + common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen))) + common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination)) + if !c.responseWrite { + buffer.ExtendHeader(1)[0] = statusSuccess + c.responseWrite = true + } + return c.ExtendedConn.WriteBuffer(buffer) +} diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 2988debf..d8b85ae7 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -8,8 +8,8 @@ import ( "syscall" "unsafe" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + N "github.com/sagernet/sing/common/network" "golang.org/x/sys/unix" ) @@ -33,9 +33,9 @@ func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, sr func findProcessName(network string, ip netip.Addr, port int) (string, error) { var spath string switch network { - case C.NetworkTCP: + case N.NetworkTCP: spath = "net.inet.tcp.pcblist_n" - case C.NetworkUDP: + case N.NetworkUDP: spath = "net.inet.udp.pcblist_n" default: return "", os.ErrInvalid @@ -55,7 +55,7 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) { // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) itemSize := 384 - if network == C.NetworkTCP { + if network == N.NetworkTCP { // rup8(sizeof(xtcpcb_n)) itemSize += 208 } diff --git a/common/process/searcher_linux_shared.go b/common/process/searcher_linux_shared.go index 3ca49362..a00d5075 100644 --- a/common/process/searcher_linux_shared.go +++ b/common/process/searcher_linux_shared.go @@ -15,10 +15,10 @@ import ( "unicode" "unsafe" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" ) // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 @@ -52,9 +52,9 @@ func resolveSocketByNetlink0(network string, ip netip.Addr, srcPort int) (inode var protocol byte switch network { - case C.NetworkTCP: + case N.NetworkTCP: protocol = syscall.IPPROTO_TCP - case C.NetworkUDP: + case N.NetworkUDP: protocol = syscall.IPPROTO_UDP default: return 0, 0, os.ErrInvalid diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index ae3c3a7f..118578ba 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -8,9 +8,9 @@ import ( "syscall" "unsafe" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" "golang.org/x/sys/windows" ) @@ -86,10 +86,10 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) var class int var fn uintptr switch network { - case C.NetworkTCP: + case N.NetworkTCP: fn = procGetExtendedTcpTable.Addr() class = tcpTablePidConn - case C.NetworkUDP: + case N.NetworkUDP: fn = procGetExtendedUdpTable.Addr() class = udpTablePid default: @@ -101,7 +101,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) return "", err } - s := newSearcher(family == windows.AF_INET, network == C.NetworkTCP) + s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP) pid, err := s.Search(buf, ip, uint16(srcPort)) if err != nil { diff --git a/constant/network.go b/constant/network.go deleted file mode 100644 index f673da29..00000000 --- a/constant/network.go +++ /dev/null @@ -1,6 +0,0 @@ -package constant - -const ( - NetworkTCP = "tcp" - NetworkUDP = "udp" -) diff --git a/constant/timeout.go b/constant/timeout.go index d3a2db6e..96d8ce32 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,13 +3,11 @@ package constant import "time" const ( - TCPTimeout = 5 * time.Second - TCPKeepAlivePeriod = 30 * time.Second - ReadPayloadTimeout = 300 * time.Millisecond - URLTestTimeout = TCPTimeout - DefaultURLTestInterval = 1 * time.Minute - DNSTimeout = 10 * time.Second - QUICTimeout = 30 * time.Second - STUNTimeout = 15 * time.Second - UDPTimeout = 5 * time.Minute + TCPTimeout = 5 * time.Second + TCPKeepAlivePeriod = 30 * time.Second + ReadPayloadTimeout = 300 * time.Millisecond + DNSTimeout = 10 * time.Second + QUICTimeout = 30 * time.Second + STUNTimeout = 15 * time.Second + UDPTimeout = 5 * time.Minute ) diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index c4d1df93..ad2b3efa 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -15,6 +15,7 @@ import ( "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -82,7 +83,7 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { } info.Put("type", clashType) info.Put("name", detour.Tag()) - info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP)) + info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP)) delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour)) if delayHistory != nil { info.Put("history", []*urltest.History{delayHistory}) @@ -114,7 +115,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite allProxies = append(allProxies, detour.Tag()) } - defaultTag := router.DefaultOutbound(C.NetworkTCP).Tag() + defaultTag := router.DefaultOutbound(N.NetworkTCP).Tag() if defaultTag == "" { defaultTag = allProxies[0] } diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index d7d29cb1..a918908c 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -6,7 +6,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" @@ -86,7 +85,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad var chain []string var next string if rule == nil { - next = router.DefaultOutbound(C.NetworkTCP).Tag() + next = router.DefaultOutbound(N.NetworkTCP).Tag() } else { next = rule.Outbound() } @@ -173,7 +172,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route var chain []string var next string if rule == nil { - next = router.DefaultOutbound(C.NetworkUDP).Tag() + next = router.DefaultOutbound(N.NetworkUDP).Tag() } else { next = rule.Outbound() } diff --git a/go.mod b/go.mod index 4d1092cf..574190b2 100644 --- a/go.mod +++ b/go.mod @@ -7,25 +7,27 @@ require ( github.com/fsnotify/fsnotify v1.5.4 github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/cors v1.2.1 - github.com/go-chi/render v1.0.1 + github.com/go-chi/render v1.0.2 github.com/gofrs/uuid v4.2.0+incompatible github.com/gorilla/websocket v1.5.0 + github.com/hashicorp/yamux v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/maxminddb-golang v1.9.0 - github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e - github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5 - github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b + github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512 + github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1 + github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80 github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01 github.com/sagernet/sing-vmess v0.0.0-20220726034841-4dae776653e5 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa - golang.org/x/net v0.0.0-20220725212005-46097bf591d3 - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f + golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 ) require ( + github.com/ajg/form v1.5.1 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect diff --git a/go.sum b/go.sum index 31ea0601..23e3e5b4 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -35,8 +37,8 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= -github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= +github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -80,6 +82,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -143,12 +147,12 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e h1:5lfrAc+vSv0iW6eHGNLyHC+a/k6BDGJvYxYxwB/68Kk= -github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= -github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5 h1:l6ztUAFVhWhY0XOq7ISbwVBE4YLWMxfIN6HptgaOl4I= -github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5/go.mod h1:KL+8wZG3gqHLm+nvNI3ZNaPzCMA4T7KIwsGp7ix9a34= -github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b h1:6wJoJaroW3WCGjHGu7XPOSLEKP9Loi3Ox4+7A1kRTsQ= -github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b/go.mod h1:mH6wE4b5FZp1Q/meATe4tjiPjvQO9E7Lr0FBBwFYp4I= +github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512 h1:dCWDE55LpZu//W02FccNbGObZFlv1N2NS0yUdf2i4Mc= +github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1 h1:Gv9ow1IF98Qdxs+X8unPHJG4iwuEWoq0PE/jvlIqgqY= +github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1/go.mod h1:LQJDT4IpqyWI6NugkSSqxTcFfxxNBp94n+fXtHFMboQ= +github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80 h1:gpCPZyZJQVn6ZTBCJ/XaYbPi6j43TdyTty/MI5bXhbE= +github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80/go.mod h1:mH6wE4b5FZp1Q/meATe4tjiPjvQO9E7Lr0FBBwFYp4I= github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01 h1:tNJn7T87sgQyA8gpEvC6LbusV4lkhZU8oi4mRujOhM8= github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01/go.mod h1:bYHamPB16GFGt34ayYt56Pb7aN64RPY0+uuFPBSbj0U= github.com/sagernet/sing-vmess v0.0.0-20220726034841-4dae776653e5 h1:TNguWTPF6gxX/gR02hY3LGviUn6LGlDPofE6lpSJWeo= @@ -238,8 +242,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -275,8 +279,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/inbound/default.go b/inbound/default.go index 83d0c45d..151f5ac6 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -62,13 +62,13 @@ func (a *myInboundAdapter) Tag() string { func (a *myInboundAdapter) Start() error { bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort) - if common.Contains(a.network, C.NetworkTCP) { + if common.Contains(a.network, N.NetworkTCP) { var tcpListener *net.TCPListener var err error if !a.listenOptions.TCPFastOpen { - tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(C.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) + tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) } else { - tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr(C.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) + tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) } if err != nil { return err @@ -77,8 +77,8 @@ func (a *myInboundAdapter) Start() error { go a.loopTCPIn() a.logger.Info("tcp server started at ", tcpListener.Addr()) } - if common.Contains(a.network, C.NetworkUDP) { - udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(C.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr()) + if common.Contains(a.network, N.NetworkUDP) { + udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr()) if err != nil { return err } @@ -162,7 +162,7 @@ func (a *myInboundAdapter) loopTCPIn() { metadata.SniffEnabled = a.listenOptions.SniffEnabled metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy) - metadata.Network = C.NetworkTCP + metadata.Network = N.NetworkTCP metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()) a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) hErr := a.connHandler.NewConnection(ctx, conn, metadata) @@ -196,7 +196,7 @@ func (a *myInboundAdapter) loopUDPIn() { metadata.SniffEnabled = a.listenOptions.SniffEnabled metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy) - metadata.Network = C.NetworkUDP + metadata.Network = N.NetworkUDP metadata.Source = M.SocksaddrFromNetIP(addr) err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) if err != nil { @@ -228,7 +228,7 @@ func (a *myInboundAdapter) loopUDPOOBIn() { metadata.SniffEnabled = a.listenOptions.SniffEnabled metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy) - metadata.Network = C.NetworkUDP + metadata.Network = N.NetworkUDP metadata.Source = M.SocksaddrFromNetIP(addr) err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) if err != nil { @@ -254,7 +254,7 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() { metadata.SniffEnabled = a.listenOptions.SniffEnabled metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy) - metadata.Network = C.NetworkUDP + metadata.Network = N.NetworkUDP metadata.Source = M.SocksaddrFromNetIP(addr) err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) if err != nil { @@ -282,7 +282,7 @@ func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { metadata.SniffEnabled = a.listenOptions.SniffEnabled metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination metadata.DomainStrategy = dns.DomainStrategy(a.listenOptions.DomainStrategy) - metadata.Network = C.NetworkUDP + metadata.Network = N.NetworkUDP metadata.Source = M.SocksaddrFromNetIP(addr) err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata) if err != nil { @@ -334,7 +334,7 @@ func NewError(logger log.ContextLogger, ctx context.Context, err error) { func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { defer buffer.Release() if destination.IsFqdn() { - udpAddr, err := net.ResolveUDPAddr(C.NetworkUDP, destination.String()) + udpAddr, err := net.ResolveUDPAddr(N.NetworkUDP, destination.String()) if err != nil { return err } diff --git a/inbound/http.go b/inbound/http.go index fc8838a3..c54f3cf0 100644 --- a/inbound/http.go +++ b/inbound/http.go @@ -29,7 +29,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge inbound := &HTTP{ myInboundAdapter: myInboundAdapter{ protocol: C.TypeHTTP, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, ctx: ctx, router: router, logger: logger, diff --git a/inbound/mixed.go b/inbound/mixed.go index d81559ed..df991ffb 100644 --- a/inbound/mixed.go +++ b/inbound/mixed.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/socks" @@ -31,7 +32,7 @@ func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogg inbound := &Mixed{ myInboundAdapter{ protocol: C.TypeMixed, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, ctx: ctx, router: router, logger: logger, diff --git a/inbound/redirect.go b/inbound/redirect.go index 59a1084b..4c7cf1d5 100644 --- a/inbound/redirect.go +++ b/inbound/redirect.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" ) type Redirect struct { @@ -21,7 +22,7 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL redirect := &Redirect{ myInboundAdapter{ protocol: C.TypeRedirect, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, ctx: ctx, router: router, logger: logger, diff --git a/inbound/socks.go b/inbound/socks.go index 452d064e..06046d00 100644 --- a/inbound/socks.go +++ b/inbound/socks.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" ) @@ -24,7 +25,7 @@ func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogg inbound := &Socks{ myInboundAdapter{ protocol: C.TypeSocks, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, ctx: ctx, router: router, logger: logger, diff --git a/inbound/tun.go b/inbound/tun.go index 60c07ece..643bf5bd 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -111,7 +111,7 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Network = C.NetworkTCP + metadata.Network = N.NetworkTCP metadata.Source = upstreamMetadata.Source metadata.Destination = upstreamMetadata.Destination metadata.SniffEnabled = t.inboundOptions.SniffEnabled @@ -134,7 +134,7 @@ func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstre var metadata adapter.InboundContext metadata.Inbound = t.tag metadata.InboundType = C.TypeTun - metadata.Network = C.NetworkUDP + metadata.Network = N.NetworkUDP metadata.Source = upstreamMetadata.Source metadata.Destination = upstreamMetadata.Destination metadata.SniffEnabled = t.inboundOptions.SniffEnabled diff --git a/inbound/vmess.go b/inbound/vmess.go index aa97b933..9651ccbf 100644 --- a/inbound/vmess.go +++ b/inbound/vmess.go @@ -30,7 +30,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg inbound := &VMess{ myInboundAdapter: myInboundAdapter{ protocol: C.TypeVMess, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, ctx: ctx, router: router, logger: logger, diff --git a/log/export.go b/log/export.go index 0373794f..565ae7a7 100644 --- a/log/export.go +++ b/log/export.go @@ -12,6 +12,10 @@ func init() { std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr).Logger() } +func StdLogger() ContextLogger { + return std +} + func Trace(args ...any) { std.Trace(args...) } diff --git a/option/outbound.go b/option/outbound.go index 2a664a08..843f4be3 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -98,3 +98,10 @@ type ServerOptions struct { func (o ServerOptions) Build() M.Socksaddr { return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) } + +type MultiplexOptions struct { + Enabled bool `json:"enabled,omitempty"` + MaxConnections int `json:"max_connections,omitempty"` + MinStreams int `json:"min_streams,omitempty"` + MaxStreams int `json:"max_streams,omitempty"` +} diff --git a/option/shadowsocks.go b/option/shadowsocks.go index 0e39dbdd..606bf85e 100644 --- a/option/shadowsocks.go +++ b/option/shadowsocks.go @@ -24,7 +24,8 @@ type ShadowsocksDestination struct { type ShadowsocksOutboundOptions struct { OutboundDialerOptions ServerOptions - Method string `json:"method"` - Password string `json:"password"` - Network NetworkList `json:"network,omitempty"` + Method string `json:"method"` + Password string `json:"password"` + Network NetworkList `json:"network,omitempty"` + Multiplex *MultiplexOptions `json:"multiplex,omitempty"` } diff --git a/option/types.go b/option/types.go index d5b8c997..ee48d76c 100644 --- a/option/types.go +++ b/option/types.go @@ -6,9 +6,9 @@ import ( "time" "github.com/sagernet/sing-box/common/json" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" ) type ListenAddress netip.Addr @@ -50,7 +50,7 @@ func (v *NetworkList) UnmarshalJSON(content []byte) error { } for _, networkName := range networkList { switch networkName { - case C.NetworkTCP, C.NetworkUDP: + case N.NetworkTCP, N.NetworkUDP: break default: return E.New("unknown network: " + networkName) @@ -62,7 +62,7 @@ func (v *NetworkList) UnmarshalJSON(content []byte) error { func (v NetworkList) Build() []string { if v == "" { - return []string{C.NetworkTCP, C.NetworkUDP} + return []string{N.NetworkTCP, N.NetworkUDP} } return strings.Split(string(v), "\n") } diff --git a/outbound/block.go b/outbound/block.go index 1717f983..e73c4422 100644 --- a/outbound/block.go +++ b/outbound/block.go @@ -22,7 +22,7 @@ func NewBlock(logger log.ContextLogger, tag string) *Block { return &Block{ myOutboundAdapter{ protocol: C.TypeBlock, - network: []string{C.NetworkTCP, C.NetworkUDP}, + network: []string{N.NetworkTCP, N.NetworkUDP}, logger: logger, tag: tag, }, diff --git a/outbound/builder.go b/outbound/builder.go index bfb4c8c9..2751aef3 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -1,6 +1,8 @@ package outbound import ( + "context" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -8,7 +10,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" ) -func New(router adapter.Router, logger log.ContextLogger, options option.Outbound) (adapter.Outbound, error) { +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Outbound) (adapter.Outbound, error) { if options.Type == "" { return nil, E.New("missing outbound type") } @@ -24,7 +26,7 @@ func New(router adapter.Router, logger log.ContextLogger, options option.Outboun case C.TypeHTTP: return NewHTTP(router, logger, options.Tag, options.HTTPOptions) case C.TypeShadowsocks: - return NewShadowsocks(router, logger, options.Tag, options.ShadowsocksOptions) + return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) case C.TypeVMess: return NewVMess(router, logger, options.Tag, options.VMessOptions) case C.TypeSelector: diff --git a/outbound/default.go b/outbound/default.go index c87a011a..46967f98 100644 --- a/outbound/default.go +++ b/outbound/default.go @@ -42,12 +42,12 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) + outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } else { - outConn, err = this.DialContext(ctx, C.NetworkTCP, metadata.Destination) + outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } if err != nil { - return err + return N.HandshakeFailure(conn, err) } return bufio.CopyConn(ctx, conn, outConn) } @@ -57,12 +57,12 @@ func NewEarlyConnection(ctx context.Context, this N.Dialer, conn net.Conn, metad var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - outConn, err = N.DialSerial(ctx, this, C.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) + outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } else { - outConn, err = this.DialContext(ctx, C.NetworkTCP, metadata.Destination) + outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } if err != nil { - return err + return N.HandshakeFailure(conn, err) } return CopyEarlyConn(ctx, conn, outConn) } @@ -77,7 +77,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, outConn, err = this.ListenPacket(ctx, metadata.Destination) } if err != nil { - return err + return N.HandshakeFailure(conn, err) } if metadata.Protocol != "" { switch metadata.Protocol { @@ -120,7 +120,7 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro } _, err = serverConn.Write(payload.Bytes()) if err != nil { - return E.Cause(err, "client handshake") + return N.HandshakeFailure(conn, err) } runtime.KeepAlive(_payload) return bufio.CopyConn(ctx, conn, serverConn) diff --git a/outbound/direct.go b/outbound/direct.go index d0aaac00..c95de9d7 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -26,7 +26,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti outbound := &Direct{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeDirect, - network: []string{C.NetworkTCP, C.NetworkUDP}, + network: []string{N.NetworkTCP, N.NetworkUDP}, router: router, logger: logger, tag: tag, @@ -61,9 +61,9 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. destination.Port = h.overrideDestination.Port } switch network { - case C.NetworkTCP: + case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) - case C.NetworkUDP: + case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } return h.dialer.DialContext(ctx, network, destination) diff --git a/outbound/dns.go b/outbound/dns.go index f398d240..d4cf4174 100644 --- a/outbound/dns.go +++ b/outbound/dns.go @@ -30,7 +30,7 @@ func NewDNS(router adapter.Router, logger log.ContextLogger, tag string) *DNS { return &DNS{ myOutboundAdapter{ protocol: C.TypeDNS, - network: []string{C.NetworkTCP, C.NetworkUDP}, + network: []string{N.NetworkTCP, N.NetworkUDP}, router: router, logger: logger, tag: tag, diff --git a/outbound/http.go b/outbound/http.go index eca3acea..a570fdc2 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -31,7 +31,7 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option return &HTTP{ myOutboundAdapter{ protocol: C.TypeHTTP, - network: []string{C.NetworkTCP}, + network: []string{N.NetworkTCP}, router: router, logger: logger, tag: tag, diff --git a/outbound/selector.go b/outbound/selector.go index 24968500..7ebc5869 100644 --- a/outbound/selector.go +++ b/outbound/selector.go @@ -46,7 +46,7 @@ func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, op func (s *Selector) Network() []string { if s.selected == nil { - return []string{C.NetworkTCP, C.NetworkUDP} + return []string{N.NetworkTCP, N.NetworkUDP} } return s.selected.Network() } diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 582b5d84..31f83b38 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -6,12 +6,15 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowimpl" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -20,64 +23,39 @@ var _ adapter.Outbound = (*Shadowsocks)(nil) type Shadowsocks struct { myOutboundAdapter - dialer N.Dialer - method shadowsocks.Method - serverAddr M.Socksaddr + dialer N.Dialer + method shadowsocks.Method + serverAddr M.Socksaddr + multiplexDialer N.Dialer } -func NewShadowsocks(router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { method, err := shadowimpl.FetchMethod(options.Method, options.Password) if err != nil { return nil, err } - return &Shadowsocks{ - myOutboundAdapter{ + outbound := &Shadowsocks{ + myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeShadowsocks, network: options.Network.Build(), router: router, logger: logger, tag: tag, }, - dialer.NewOutbound(router, options.OutboundDialerOptions), - method, - options.ServerOptions.Build(), - }, nil + dialer: dialer.NewOutbound(router, options.OutboundDialerOptions), + method: method, + serverAddr: options.ServerOptions.Build(), + } + outbound.multiplexDialer = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.Multiplex)) + return outbound, nil } func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - switch network { - case C.NetworkTCP: - h.logger.InfoContext(ctx, "outbound connection to ", destination) - outConn, err := h.dialer.DialContext(ctx, C.NetworkTCP, h.serverAddr) - if err != nil { - return nil, err - } - return h.method.DialEarlyConn(outConn, destination), nil - case C.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - outConn, err := h.dialer.DialContext(ctx, C.NetworkUDP, h.serverAddr) - if err != nil { - return nil, err - } - return &bufio.BindPacketConn{PacketConn: h.method.DialPacketConn(outConn), Addr: destination}, nil - default: - panic("unknown network " + network) - } + return h.multiplexDialer.DialContext(ctx, network, destination) } func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - ctx, metadata := adapter.AppendContext(ctx) - metadata.Outbound = h.tag - metadata.Destination = destination - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - outConn, err := h.dialer.DialContext(ctx, "udp", h.serverAddr) - if err != nil { - return nil, err - } - return h.method.DialPacketConn(outConn), nil + return h.multiplexDialer.ListenPacket(ctx, destination) } func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { @@ -87,3 +65,43 @@ func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return NewPacketConnection(ctx, h, conn, metadata) } + +var _ N.Dialer = (*shadowsocksDialer)(nil) + +type shadowsocksDialer Shadowsocks + +func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.AppendContext(ctx) + metadata.Outbound = h.tag + metadata.Destination = destination + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) + if err != nil { + return nil, err + } + return h.method.DialEarlyConn(outConn, destination), nil + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) + if err != nil { + return nil, err + } + return &bufio.BindPacketConn{PacketConn: h.method.DialPacketConn(outConn), Addr: destination}, nil + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } +} + +func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + ctx, metadata := adapter.AppendContext(ctx) + metadata.Outbound = h.tag + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) + if err != nil { + return nil, err + } + return h.method.DialPacketConn(outConn), nil +} diff --git a/outbound/socks.go b/outbound/socks.go index 034aa230..c115366c 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -9,6 +9,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/protocol/socks" @@ -49,13 +50,13 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination - switch network { - case C.NetworkTCP: + switch N.NetworkName(network) { + case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) - case C.NetworkUDP: + case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) default: - panic("unknown network " + network) + return nil, E.Extend(N.ErrUnknownNetwork, network) } return h.client.DialContext(ctx, network, destination) } diff --git a/outbound/vmess.go b/outbound/vmess.go index 258ab4b1..845f9de0 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -58,28 +59,28 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination - switch network { - case C.NetworkTCP: + switch N.NetworkName(network) { + case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) - outConn, err := h.dialer.DialContext(ctx, C.NetworkTCP, h.serverAddr) + outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) if err != nil { return nil, err } return h.client.DialEarlyConn(outConn, destination), nil - case C.NetworkUDP: + case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - outConn, err := h.dialer.DialContext(ctx, C.NetworkTCP, h.serverAddr) + outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) if err != nil { return nil, err } return h.client.DialEarlyPacketConn(outConn, destination), nil default: - panic("unknown network " + network) + return nil, E.Extend(N.ErrUnknownNetwork, network) } } func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - conn, err := h.DialContext(ctx, C.NetworkUDP, destination) + conn, err := h.DialContext(ctx, N.NetworkUDP, destination) if err != nil { return nil, err } diff --git a/route/router.go b/route/router.go index 409603db..a660b68e 100644 --- a/route/router.go +++ b/route/router.go @@ -19,6 +19,7 @@ import ( "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" + "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/warning" @@ -278,17 +279,17 @@ func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() if !loaded { return E.New("default detour not found: ", r.defaultDetour) } - if common.Contains(detour.Network(), C.NetworkTCP) { + if common.Contains(detour.Network(), N.NetworkTCP) { defaultOutboundForConnection = detour } - if common.Contains(detour.Network(), C.NetworkUDP) { + if common.Contains(detour.Network(), N.NetworkUDP) { defaultOutboundForPacketConnection = detour } } var index, packetIndex int if defaultOutboundForConnection == nil { for i, detour := range outbounds { - if common.Contains(detour.Network(), C.NetworkTCP) { + if common.Contains(detour.Network(), N.NetworkTCP) { index = i defaultOutboundForConnection = detour break @@ -297,7 +298,7 @@ func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() } if defaultOutboundForPacketConnection == nil { for i, detour := range outbounds { - if common.Contains(detour.Network(), C.NetworkUDP) { + if common.Contains(detour.Network(), N.NetworkUDP) { packetIndex = i defaultOutboundForPacketConnection = detour break @@ -478,7 +479,7 @@ func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { } func (r *Router) DefaultOutbound(network string) adapter.Outbound { - if network == C.NetworkTCP { + if network == N.NetworkTCP { return r.defaultOutboundForConnection } else { return r.defaultOutboundForPacketConnection @@ -486,6 +487,10 @@ func (r *Router) DefaultOutbound(network string) adapter.Outbound { } func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + if metadata.Destination.Fqdn == mux.Destination.Fqdn { + r.logger.InfoContext(ctx, "inbound multiplex connection") + return mux.NewConnection(ctx, r, r, r.logger, conn, metadata) + } if metadata.SniffEnabled { _buffer := buf.StackNew() defer common.KeepAlive(_buffer) @@ -517,7 +522,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") } matchedRule, detour := r.match(ctx, &metadata, r.defaultOutboundForConnection) - if !common.Contains(detour.Network(), C.NetworkTCP) { + if !common.Contains(detour.Network(), N.NetworkTCP) { conn.Close() return E.New("missing supported outbound, closing connection") } @@ -564,7 +569,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") } matchedRule, detour := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection) - if !common.Contains(detour.Network(), C.NetworkUDP) { + if !common.Contains(detour.Network(), N.NetworkUDP) { conn.Close() return E.New("missing supported outbound, closing packet connection") } @@ -927,5 +932,10 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { } func (r *Router) NewError(ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + r.logger.TraceContext(ctx, "connection closed: ", err) + return + } r.logger.ErrorContext(ctx, err) } diff --git a/route/rule.go b/route/rule.go index 85c12c04..1d6d7d98 100644 --- a/route/rule.go +++ b/route/rule.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" ) func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) { @@ -73,7 +74,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt } if options.Network != "" { switch options.Network { - case C.NetworkTCP, C.NetworkUDP: + case N.NetworkTCP, N.NetworkUDP: item := NewNetworkItem(options.Network) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) diff --git a/route/rule_dns.go b/route/rule_dns.go index 3c00eefb..6dd81c10 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" ) func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) { @@ -59,7 +60,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options } if options.Network != "" { switch options.Network { - case C.NetworkTCP, C.NetworkUDP: + case N.NetworkTCP, N.NetworkUDP: item := NewNetworkItem(options.Network) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) diff --git a/test/go.mod b/test/go.mod index 72d24492..1376263f 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,14 +10,16 @@ require ( github.com/docker/docker v20.10.17+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e + github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512 + github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.0 - golang.org/x/net v0.0.0-20220725212005-46097bf591d3 + golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 ) require ( github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/ajg/form v1.5.1 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/database64128/tfo-go v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -26,11 +28,12 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-chi/chi/v5 v5.0.7 // indirect github.com/go-chi/cors v1.2.1 // indirect - github.com/go-chi/render v1.0.1 // indirect + github.com/go-chi/render v1.0.2 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect @@ -49,8 +52,7 @@ require ( github.com/oschwald/maxminddb-golang v1.9.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5 // indirect - github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b // indirect + github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1 // indirect github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01 // indirect github.com/sagernet/sing-vmess v0.0.0-20220726034841-4dae776653e5 // indirect github.com/sirupsen/logrus v1.8.1 // indirect @@ -59,7 +61,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/mod v0.5.1 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.1.9 // indirect diff --git a/test/go.sum b/test/go.sum index c0340ffa..bf84feba 100644 --- a/test/go.sum +++ b/test/go.sum @@ -12,6 +12,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -48,8 +50,8 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= -github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= +github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -96,6 +98,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -168,12 +172,12 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e h1:5lfrAc+vSv0iW6eHGNLyHC+a/k6BDGJvYxYxwB/68Kk= -github.com/sagernet/sing v0.0.0-20220726034811-bc109486f14e/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= -github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5 h1:l6ztUAFVhWhY0XOq7ISbwVBE4YLWMxfIN6HptgaOl4I= -github.com/sagernet/sing-dns v0.0.0-20220726044716-2b8c696b09f5/go.mod h1:KL+8wZG3gqHLm+nvNI3ZNaPzCMA4T7KIwsGp7ix9a34= -github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b h1:6wJoJaroW3WCGjHGu7XPOSLEKP9Loi3Ox4+7A1kRTsQ= -github.com/sagernet/sing-shadowsocks v0.0.0-20220726034922-ebbaadcae06b/go.mod h1:mH6wE4b5FZp1Q/meATe4tjiPjvQO9E7Lr0FBBwFYp4I= +github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512 h1:dCWDE55LpZu//W02FccNbGObZFlv1N2NS0yUdf2i4Mc= +github.com/sagernet/sing v0.0.0-20220729120910-4376f188c512/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1 h1:Gv9ow1IF98Qdxs+X8unPHJG4iwuEWoq0PE/jvlIqgqY= +github.com/sagernet/sing-dns v0.0.0-20220729120941-109c0a7aabb1/go.mod h1:LQJDT4IpqyWI6NugkSSqxTcFfxxNBp94n+fXtHFMboQ= +github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80 h1:gpCPZyZJQVn6ZTBCJ/XaYbPi6j43TdyTty/MI5bXhbE= +github.com/sagernet/sing-shadowsocks v0.0.0-20220729155919-91d2780bfc80/go.mod h1:mH6wE4b5FZp1Q/meATe4tjiPjvQO9E7Lr0FBBwFYp4I= github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01 h1:tNJn7T87sgQyA8gpEvC6LbusV4lkhZU8oi4mRujOhM8= github.com/sagernet/sing-tun v0.0.0-20220726111504-b4bded886e01/go.mod h1:bYHamPB16GFGt34ayYt56Pb7aN64RPY0+uuFPBSbj0U= github.com/sagernet/sing-vmess v0.0.0-20220726034841-4dae776653e5 h1:TNguWTPF6gxX/gR02hY3LGviUn6LGlDPofE6lpSJWeo= @@ -268,8 +272,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -310,8 +314,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/test/mux_test.go b/test/mux_test.go new file mode 100644 index 00000000..7076448f --- /dev/null +++ b/test/mux_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" +) + +func TestShadowsocksMux(t *testing.T) { + method := shadowaead_2022.List[0] + password := mkBase64(t, 16) + startInstance(t, option.Options{ + Log: &option.LogOptions{ + Level: "debug", + }, + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeShadowsocks, + ShadowsocksOptions: option.ShadowsocksInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Method: method, + Password: password, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeShadowsocks, + Tag: "ss-out", + ShadowsocksOptions: option.ShadowsocksOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Method: method, + Password: password, + Multiplex: &option.MultiplexOptions{ + Enabled: true, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "ss-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +}