From 64dbac813837bbadfaeec1a6e0d064875a123e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 4 Aug 2022 10:38:20 +0800 Subject: [PATCH] Add multiplexer for vmess --- common/mux/protocol.go | 2 +- docs/configuration/outbound/vmess.md | 7 ++- go.mod | 4 +- go.sum | 8 +-- option/shadowsocks.go | 8 +-- option/vmess.go | 1 + outbound/builder.go | 2 +- outbound/shadowsocks.go | 2 +- outbound/vmess.go | 86 +++++++++++++++++++-------- test/go.mod | 4 +- test/go.sum | 8 +-- test/mux_test.go | 89 ++++++++++++++++++++++++++-- 12 files changed, 171 insertions(+), 50 deletions(-) diff --git a/common/mux/protocol.go b/common/mux/protocol.go index 80ce2898..dcda3f07 100644 --- a/common/mux/protocol.go +++ b/common/mux/protocol.go @@ -109,7 +109,7 @@ func ReadRequest(reader io.Reader) (*Request, error) { if err != nil { return nil, err } - if protocol > byte(ProtocolSMux) { + if protocol > byte(ProtocolYAMux) { return nil, E.New("unsupported protocol: ", protocol) } return &Request{Protocol: Protocol(protocol)}, nil diff --git a/docs/configuration/outbound/vmess.md b/docs/configuration/outbound/vmess.md index 786c9cf9..1596fcc1 100644 --- a/docs/configuration/outbound/vmess.md +++ b/docs/configuration/outbound/vmess.md @@ -16,7 +16,8 @@ "authenticated_length": true, "network": "tcp", "tls": {}, - + "multiplex": {}, + "detour": "upstream-out", "bind_interface": "en0", "routing_mark": 1234, @@ -92,6 +93,10 @@ Both is enabled by default. TLS configuration, see [TLS outbound structure](/configuration/shared/tls/#outbound-structure). +#### multiplex + +Multiplex configuration, see [Multiplex structure](/configuration/shared/multiplex). + ### Dial Fields #### detour diff --git a/go.mod b/go.mod index 0b683761..4539532b 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( 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-20220803094243-d9ca259bec6a + github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050 github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed - github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9 + github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 github.com/xtaci/smux v1.5.16 diff --git a/go.sum b/go.sum index 7ebd437b..00e459b6 100644 --- a/go.sum +++ b/go.sum @@ -149,16 +149,16 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/sing v0.0.0-20220803094243-d9ca259bec6a h1:CHxuamnZEAxXoep7ycGSuMOiKzsRYuYa0ucnOCdUT9U= -github.com/sagernet/sing v0.0.0-20220803094243-d9ca259bec6a/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= +github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050 h1:Q6PV3tlP7htm9jgebFryXT4HFvEHvb5QHEcuWrf4E2o= +github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 h1:jxt2PYixIkK2i7nUGW3f+PzJagEZcbNyQddBWGuqNnw= github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM= github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 h1:RYvOc69eSNMN0dwVugrDts41Nn7Ar/C/n/fvytvFcp4= github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1/go.mod h1:NqZjiXszgVCMQ4gVDa2V+drhS8NMfGqUqDF86EacEFc= github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed h1:28qeqeuHLZEkzdcZjYwcCn8y4ckyKimaP+L4P25dqUo= github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed/go.mod h1:jNlPidQzZYkpmpQJ+sDN2YGrPsL4QImoqBpuauId9po= -github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9 h1:x+r8P5MKyQWGN3tcI5dmOM1Aei1mlWua2ciMBGz0/oM= -github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9/go.mod h1:ETvczg3TQzGa8zg0lYXj8cYTJr4+OFUmtQer9/c/cLU= +github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 h1:C8sc2MYiNx0O7uQ0nieJWq5qYeIHj20XHFWPlcgoQeY= +github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2/go.mod h1:bNXBqSWYaG3ePl6u0xQY5zneE+ZKa3683ZpuE8S1M1w= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= diff --git a/option/shadowsocks.go b/option/shadowsocks.go index 606bf85e..4caecc59 100644 --- a/option/shadowsocks.go +++ b/option/shadowsocks.go @@ -24,8 +24,8 @@ type ShadowsocksDestination struct { type ShadowsocksOutboundOptions struct { OutboundDialerOptions ServerOptions - Method string `json:"method"` - Password string `json:"password"` - Network NetworkList `json:"network,omitempty"` - Multiplex *MultiplexOptions `json:"multiplex,omitempty"` + Method string `json:"method"` + Password string `json:"password"` + Network NetworkList `json:"network,omitempty"` + MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` } diff --git a/option/vmess.go b/option/vmess.go index c1fe667d..ad3d6973 100644 --- a/option/vmess.go +++ b/option/vmess.go @@ -22,4 +22,5 @@ type VMessOutboundOptions struct { AuthenticatedLength bool `json:"authenticated_length,omitempty"` Network NetworkList `json:"network,omitempty"` TLSOptions *OutboundTLSOptions `json:"tls,omitempty"` + MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` } diff --git a/outbound/builder.go b/outbound/builder.go index 82b2eda9..e0632704 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -28,7 +28,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o case C.TypeShadowsocks: return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions) case C.TypeVMess: - return NewVMess(router, logger, options.Tag, options.VMessOptions) + return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions) case C.TypeSelector: return NewSelector(router, logger, options.Tag, options.SelectorOptions) default: diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index ba5f52e5..ec26d644 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -46,7 +46,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte method: method, serverAddr: options.ServerOptions.Build(), } - outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.Multiplex)) + outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions)) if err != nil { return nil, err } diff --git a/outbound/vmess.go b/outbound/vmess.go index 845f9de0..9c12eaf0 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -6,6 +6,7 @@ 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" @@ -20,12 +21,13 @@ var _ adapter.Outbound = (*VMess)(nil) type VMess struct { myOutboundAdapter - dialer N.Dialer - client *vmess.Client - serverAddr M.Socksaddr + dialer N.Dialer + client *vmess.Client + serverAddr M.Socksaddr + multiplexDialer N.Dialer } -func NewVMess(router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { +func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { var clientOptions []vmess.ClientOption if options.GlobalPadding { clientOptions = append(clientOptions, vmess.ClientWithGlobalPadding()) @@ -37,38 +39,80 @@ func NewVMess(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - detour, err := dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) - if err != nil { - return nil, err - } - return &VMess{ - myOutboundAdapter{ + inbound := &VMess{ + myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeVMess, network: options.Network.Build(), router: router, logger: logger, tag: tag, }, - detour, - client, - options.ServerOptions.Build(), - }, nil + client: client, + serverAddr: options.ServerOptions.Build(), + } + inbound.dialer, err = dialer.NewTLS(dialer.NewOutbound(router, options.OutboundDialerOptions), options.Server, common.PtrValueOrDefault(options.TLSOptions)) + if err != nil { + return nil, err + } + inbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(inbound), common.PtrValueOrDefault(options.MultiplexOptions)) + if err != nil { + return nil, err + } + return inbound, nil } func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if h.multiplexDialer == nil { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + } + return (*vmessDialer)(h).DialContext(ctx, network, destination) + } else { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound multiplex connection to ", destination) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) + } + return h.multiplexDialer.DialContext(ctx, network, destination) + } +} + +func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.multiplexDialer == nil { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + return (*vmessDialer)(h).ListenPacket(ctx, destination) + } else { + h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) + return h.multiplexDialer.ListenPacket(ctx, destination) + } +} + +func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewEarlyConnection(ctx, h, conn, metadata) +} + +func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, h, conn, metadata) +} + +type vmessDialer VMess + +func (h *vmessDialer) 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.client.DialEarlyConn(outConn, destination), nil case N.NetworkUDP: - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) outConn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) if err != nil { return nil, err @@ -79,18 +123,10 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { conn, err := h.DialContext(ctx, N.NetworkUDP, destination) if err != nil { return nil, err } return conn.(vmess.PacketConn), nil } - -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewEarlyConnection(ctx, h, conn, metadata) -} - -func (h *VMess) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} diff --git a/test/go.mod b/test/go.mod index 0015e00c..affbe9d7 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ 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-20220803094243-d9ca259bec6a + github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.0 @@ -55,7 +55,7 @@ require ( github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 // indirect github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 // indirect github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed // indirect - github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9 // indirect + github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/xtaci/smux v1.5.16 // indirect diff --git a/test/go.sum b/test/go.sum index 85fb2669..811e97fa 100644 --- a/test/go.sum +++ b/test/go.sum @@ -174,16 +174,16 @@ github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/sing v0.0.0-20220803094243-d9ca259bec6a h1:CHxuamnZEAxXoep7ycGSuMOiKzsRYuYa0ucnOCdUT9U= -github.com/sagernet/sing v0.0.0-20220803094243-d9ca259bec6a/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= +github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050 h1:Q6PV3tlP7htm9jgebFryXT4HFvEHvb5QHEcuWrf4E2o= +github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 h1:jxt2PYixIkK2i7nUGW3f+PzJagEZcbNyQddBWGuqNnw= github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM= github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 h1:RYvOc69eSNMN0dwVugrDts41Nn7Ar/C/n/fvytvFcp4= github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1/go.mod h1:NqZjiXszgVCMQ4gVDa2V+drhS8NMfGqUqDF86EacEFc= github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed h1:28qeqeuHLZEkzdcZjYwcCn8y4ckyKimaP+L4P25dqUo= github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed/go.mod h1:jNlPidQzZYkpmpQJ+sDN2YGrPsL4QImoqBpuauId9po= -github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9 h1:x+r8P5MKyQWGN3tcI5dmOM1Aei1mlWua2ciMBGz0/oM= -github.com/sagernet/sing-vmess v0.0.0-20220802053753-a38d3b22e6b9/go.mod h1:ETvczg3TQzGa8zg0lYXj8cYTJr4+OFUmtQer9/c/cLU= +github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 h1:C8sc2MYiNx0O7uQ0nieJWq5qYeIHj20XHFWPlcgoQeY= +github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2/go.mod h1:bNXBqSWYaG3ePl6u0xQY5zneE+ZKa3683ZpuE8S1M1w= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= diff --git a/test/mux_test.go b/test/mux_test.go index 6d03e15d..ff881d82 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -8,19 +8,31 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + + "github.com/gofrs/uuid" ) +var muxProtocols = []mux.Protocol{ + mux.ProtocolYAMux, + mux.ProtocolSMux, +} + func TestShadowsocksMux(t *testing.T) { - for _, protocol := range []mux.Protocol{ - mux.ProtocolYAMux, - mux.ProtocolSMux, - } { + for _, protocol := range muxProtocols { t.Run(protocol.String(), func(t *testing.T) { testShadowsocksMux(t, protocol.String()) }) } } +func TestVMessMux(t *testing.T) { + for _, protocol := range muxProtocols { + t.Run(protocol.String(), func(t *testing.T) { + testVMessMux(t, protocol.String()) + }) + } +} + func testShadowsocksMux(t *testing.T, protocol string) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) @@ -65,7 +77,7 @@ func testShadowsocksMux(t *testing.T, protocol string) { }, Method: method, Password: password, - Multiplex: &option.MultiplexOptions{ + MultiplexOptions: &option.MultiplexOptions{ Enabled: true, Protocol: protocol, }, @@ -85,3 +97,70 @@ func testShadowsocksMux(t *testing.T, protocol string) { }) testSuit(t, clientPort, testPort) } + +func testVMessMux(t *testing.T, protocol string) { + user, _ := uuid.NewV4() + startInstance(t, option.Options{ + Log: &option.LogOptions{ + Level: "trace", + }, + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVMess, + VMessOptions: option.VMessInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VMessUser{ + { + UUID: user.String(), + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVMess, + Tag: "vmess-out", + VMessOptions: option.VMessOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Security: "auto", + UUID: user.String(), + MultiplexOptions: &option.MultiplexOptions{ + Enabled: true, + Protocol: protocol, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "vmess-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +}