From 50827bcff17eac7d4b09f79d97a9ff2da1eabe7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 24 Apr 2023 09:55:46 +0800 Subject: [PATCH] Add multiplexer for VLESS outbound --- option/vless.go | 1 + outbound/vless.go | 85 +++++++++++++++++++++++++++--------- test/go.mod | 3 +- test/go.sum | 6 ++- test/mux_test.go | 108 +++++++++++++++++++++++++++++++++------------- 5 files changed, 149 insertions(+), 54 deletions(-) diff --git a/option/vless.go b/option/vless.go index 2b6521b1..5547bd88 100644 --- a/option/vless.go +++ b/option/vless.go @@ -20,6 +20,7 @@ type VLESSOutboundOptions struct { Flow string `json:"flow,omitempty"` Network NetworkList `json:"network,omitempty"` TLS *OutboundTLSOptions `json:"tls,omitempty"` + Multiplex *MultiplexOptions `json:"multiplex,omitempty"` Transport *V2RayTransportOptions `json:"transport,omitempty"` PacketEncoding *string `json:"packet_encoding,omitempty"` } diff --git a/outbound/vless.go b/outbound/vless.go index adb84df0..dd194f7e 100644 --- a/outbound/vless.go +++ b/outbound/vless.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" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -24,13 +25,14 @@ var _ adapter.Outbound = (*VLESS)(nil) type VLESS struct { myOutboundAdapter - dialer N.Dialer - client *vless.Client - serverAddr M.Socksaddr - tlsConfig tls.Config - transport adapter.V2RayClientTransport - packetAddr bool - xudp bool + dialer N.Dialer + client *vless.Client + serverAddr M.Socksaddr + multiplexDialer *mux.Client + tlsConfig tls.Config + transport adapter.V2RayClientTransport + packetAddr bool + xudp bool } func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { @@ -75,10 +77,65 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg if err != nil { return nil, err } + outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } return outbound, nil } func (h *VLESS) 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 (*vlessDialer)(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 *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.multiplexDialer == nil { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + return (*vlessDialer)(h).ListenPacket(ctx, destination) + } else { + h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) + return h.multiplexDialer.ListenPacket(ctx, destination) + } +} + +func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewConnection(ctx, h, conn, metadata) +} + +func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, h, conn, metadata) +} + +func (h *VLESS) InterfaceUpdated() error { + if h.multiplexDialer != nil { + h.multiplexDialer.Reset() + } + return nil +} + +func (h *VLESS) Close() error { + return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) +} + +type vlessDialer VLESS + +func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination @@ -120,7 +177,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag @@ -154,15 +211,3 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. return h.client.DialEarlyPacketConn(conn, destination) } } - -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewPacketConnection(ctx, h, conn, metadata) -} - -func (h *VLESS) Close() error { - return common.Close(h.transport) -} diff --git a/test/go.mod b/test/go.mod index 812d572f..f53ef9e1 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.4 + github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207 github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.2 @@ -71,6 +71,7 @@ require ( github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc // indirect + github.com/sagernet/sing-mux v0.0.0-20230424015424-9b0d527c3bb0 // indirect github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b // indirect github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b // indirect github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 // indirect diff --git a/test/go.sum b/test/go.sum index 1568f7b2..93fefce3 100644 --- a/test/go.sum +++ b/test/go.sum @@ -126,10 +126,12 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo= -github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207 h1:+dDVjW20IT+e8maKryaDeRY2+RFmTFdrQeIzqE2WOss= +github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4= github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY= +github.com/sagernet/sing-mux v0.0.0-20230424015424-9b0d527c3bb0 h1:87jyxzTjq01VgEiUVSMNRKjCfsSfp/QwyUVT37eXY50= +github.com/sagernet/sing-mux v0.0.0-20230424015424-9b0d527c3bb0/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI= github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88= github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog= github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4= diff --git a/test/mux_test.go b/test/mux_test.go index aff082fd..a50fc199 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -4,7 +4,6 @@ import ( "net/netip" "testing" - "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" @@ -12,45 +11,32 @@ import ( "github.com/gofrs/uuid/v5" ) -var muxProtocols = []mux.Protocol{ - mux.ProtocolYAMux, - mux.ProtocolSMux, +var muxProtocols = []string{ + "smux", + "yamux", + "h2mux", } -func TestVMessSMux(t *testing.T) { - testVMessMux(t, option.MultiplexOptions{ - Enabled: true, - Protocol: mux.ProtocolSMux.String(), - }) -} - -func TestShadowsocksMux(t *testing.T) { +func TestMux(t *testing.T) { for _, protocol := range muxProtocols { - t.Run(protocol.String(), func(t *testing.T) { - testShadowsocksMux(t, option.MultiplexOptions{ + t.Run(protocol, func(t *testing.T) { + options := option.MultiplexOptions{ Enabled: true, - Protocol: protocol.String(), + Protocol: protocol, + } + t.Run("shadowsocks", func(t *testing.T) { + testShadowsocksMux(t, options) + }) + t.Run("vmess", func(t *testing.T) { + testVMessMux(t, options) + }) + t.Run("vless", func(t *testing.T) { + testVLESSMux(t, options) }) }) } } -func TestShadowsockH2Mux(t *testing.T) { - testShadowsocksMux(t, option.MultiplexOptions{ - Enabled: true, - Protocol: mux.ProtocolH2Mux.String(), - Padding: true, - }) -} - -func TestShadowsockSMuxPadding(t *testing.T) { - testShadowsocksMux(t, option.MultiplexOptions{ - Enabled: true, - Protocol: mux.ProtocolSMux.String(), - Padding: true, - }) -} - func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) @@ -170,3 +156,63 @@ func testVMessMux(t *testing.T, options option.MultiplexOptions) { }) testSuit(t, clientPort, testPort) } + +func testVLESSMux(t *testing.T, options option.MultiplexOptions) { + user, _ := uuid.NewV4() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + VLESSOptions: option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{ + { + UUID: user.String(), + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVLESS, + Tag: "vless-out", + VLESSOptions: option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: user.String(), + Multiplex: &options, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "vless-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +}