瀏覽代碼

Add back UoT support

世界 3 年之前
父節點
當前提交
517a89fa9c

+ 1 - 1
common/dialer/resolve.go

@@ -51,7 +51,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
 }
 
 func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
-	if !destination.IsFqdn() {
+	if !destination.IsFqdn() || destination.Fqdn == "" {
 		return d.dialer.ListenPacket(ctx, destination)
 	}
 	ctx, metadata := adapter.AppendContext(ctx)

+ 6 - 2
docs/changelog.md

@@ -1,9 +1,13 @@
-#### 2022/08/09
+#### 2022/08/11
+
+* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds.
+
+#### 2022/08/10
 
 * Add full-featured [Naive](/configuration/inbound/naive) inbound
 * Fix default dns server option [#9] by iKirby
 
-#### 2022/08/08
+#### 2022/08/09
 
 No changelog before.
 

+ 7 - 0
docs/configuration/outbound/shadowsocks.md

@@ -14,6 +14,7 @@
       "method": "2022-blake3-aes-128-gcm",
       "password": "8JCsPssfgS8tiRwiMlhARg==",
       "network": "udp",
+      "udp_over_tcp": false,
       "multiplex": {},
       
       "detour": "upstream-out",
@@ -85,6 +86,12 @@ One of `tcp` `udp`.
 
 Both is enabled by default.
 
+#### udp_over_tcp
+
+Enable UDP over TCP protocol.
+
+Conflict with `multiplex`.
+
 #### multiplex
 
 Multiplex configuration, see [Multiplex structure](/configuration/shared/multiplex).

+ 1 - 0
option/shadowsocks.go

@@ -27,5 +27,6 @@ type ShadowsocksOutboundOptions struct {
 	Method           string            `json:"method"`
 	Password         string            `json:"password"`
 	Network          NetworkList       `json:"network,omitempty"`
+	UoT              bool              `json:"udp_over_tcp,omitempty"`
 	MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
 }

+ 30 - 3
outbound/shadowsocks.go

@@ -17,6 +17,7 @@ import (
 	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/uot"
 )
 
 var _ adapter.Outbound = (*Shadowsocks)(nil)
@@ -26,6 +27,7 @@ type Shadowsocks struct {
 	dialer          N.Dialer
 	method          shadowsocks.Method
 	serverAddr      M.Socksaddr
+	uot             bool
 	multiplexDialer N.Dialer
 }
 
@@ -45,10 +47,13 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
 		dialer:     dialer.NewOutbound(router, options.OutboundDialerOptions),
 		method:     method,
 		serverAddr: options.ServerOptions.Build(),
+		uot:        options.UoT,
 	}
-	outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
-	if err != nil {
-		return nil, err
+	if !options.UoT {
+		outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
+		if err != nil {
+			return nil, err
+		}
 	}
 	return outbound, nil
 }
@@ -59,6 +64,17 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati
 		case N.NetworkTCP:
 			h.logger.InfoContext(ctx, "outbound connection to ", destination)
 		case N.NetworkUDP:
+			if h.uot {
+				h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
+				tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, M.Socksaddr{
+					Fqdn: uot.UOTMagicAddress,
+					Port: destination.Port,
+				})
+				if err != nil {
+					return nil, err
+				}
+				return uot.NewClientConn(tcpConn), nil
+			}
 			h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
 		}
 		return (*shadowsocksDialer)(h).DialContext(ctx, network, destination)
@@ -75,6 +91,17 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati
 
 func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
 	if h.multiplexDialer == nil {
+		if h.uot {
+			h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
+			tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, M.Socksaddr{
+				Fqdn: uot.UOTMagicAddress,
+				Port: destination.Port,
+			})
+			if err != nil {
+				return nil, err
+			}
+			return uot.NewClientConn(tcpConn), nil
+		}
 		h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
 		return (*shadowsocksDialer)(h).ListenPacket(ctx, destination)
 	} else {

+ 11 - 5
route/router.go

@@ -35,6 +35,7 @@ import (
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 	"github.com/sagernet/sing/common/rw"
+	"github.com/sagernet/sing/common/uot"
 )
 
 var warnDefaultInterfaceOnUnsupportedPlatform = warning.New(
@@ -492,9 +493,15 @@ 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 {
+	switch metadata.Destination.Fqdn {
+	case mux.Destination.Fqdn:
 		r.logger.InfoContext(ctx, "inbound multiplex connection")
 		return mux.NewConnection(ctx, r, r, r.logger, conn, metadata)
+	case uot.UOTMagicAddress:
+		r.logger.InfoContext(ctx, "inbound UoT connection")
+		metadata.Network = N.NetworkUDP
+		metadata.Destination = M.Socksaddr{}
+		return r.RoutePacketConnection(ctx, uot.NewClientConn(conn), metadata)
 	}
 	if metadata.SniffEnabled {
 		buffer := buf.NewPacket()
@@ -543,13 +550,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 	if metadata.SniffEnabled {
 		buffer := buf.NewPacket()
 		buffer.FullReset()
-		_, err := conn.ReadPacket(buffer)
+		destination, err := conn.ReadPacket(buffer)
 		if err != nil {
 			buffer.Release()
 			return err
 		}
 		sniffMetadata, err := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
-		originDestination := metadata.Destination
 		if err == nil {
 			metadata.Protocol = sniffMetadata.Protocol
 			metadata.Domain = sniffMetadata.Domain
@@ -562,9 +568,9 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 				r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
 			}
 		}
-		conn = bufio.NewCachedPacketConn(conn, buffer, originDestination)
+		conn = bufio.NewCachedPacketConn(conn, buffer, destination)
 	}
-	if metadata.Destination.IsFqdn() && metadata.DomainStrategy != dns.DomainStrategyAsIS {
+	if metadata.Destination.IsFqdn() && metadata.Destination.Fqdn != uot.UOTMagicAddress && metadata.DomainStrategy != dns.DomainStrategyAsIS {
 		addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, metadata.DomainStrategy)
 		if err != nil {
 			return err

+ 63 - 0
test/shadowsocks_test.go

@@ -8,6 +8,7 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
 	F "github.com/sagernet/sing/common/format"
 
 	"github.com/stretchr/testify/require"
@@ -193,6 +194,68 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
 	testSuit(t, clientPort, testPort)
 }
 
+func TestShadowsocksUoT(t *testing.T) {
+	method := shadowaead_2022.List[0]
+	password := mkBase64(t, 16)
+	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.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,
+					UoT:      true,
+				},
+			},
+		},
+		Route: &option.RouteOptions{
+			Rules: []option.Rule{
+				{
+					DefaultOptions: option.DefaultRule{
+						Inbound:  []string{"mixed-in"},
+						Outbound: "ss-out",
+					},
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
+}
+
 func mkBase64(t *testing.T, length int) string {
 	psk := make([]byte, length)
 	_, err := rand.Read(psk)