Browse Source

Add udp over stream support for TUIC

世界 2 years ago
parent
commit
f46732bc0e
4 changed files with 66 additions and 11 deletions
  1. 14 0
      docs/configuration/outbound/tuic.md
  2. 10 0
      docs/configuration/outbound/tuic.zh.md
  3. 1 0
      option/tuic.go
  4. 41 11
      outbound/tuic.go

+ 14 - 0
docs/configuration/outbound/tuic.md

@@ -11,6 +11,7 @@
   "password": "hello",
   "password": "hello",
   "congestion_control": "cubic",
   "congestion_control": "cubic",
   "udp_relay_mode": "native",
   "udp_relay_mode": "native",
+  "udp_over_stream": false,
   "zero_rtt_handshake": false,
   "zero_rtt_handshake": false,
   "heartbeat": "10s",
   "heartbeat": "10s",
   "network": "tcp",
   "network": "tcp",
@@ -67,6 +68,19 @@ UDP packet relay mode
 
 
 `native` is used by default.
 `native` is used by default.
 
 
+Conflict with `udp_over_stream`.
+
+#### udp_over_stream
+
+This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC
+stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
+another program compatible with the protocol as a server.
+
+This mode has no positive effect in a proper UDP proxy scenario and should only be applied to relay streaming UDP
+traffic (basically QUIC streams).
+
+Conflict with `udp_relay_mode`.
+
 #### network
 #### network
 
 
 Enabled network
 Enabled network

+ 10 - 0
docs/configuration/outbound/tuic.zh.md

@@ -11,6 +11,7 @@
   "password": "hello",
   "password": "hello",
   "congestion_control": "cubic",
   "congestion_control": "cubic",
   "udp_relay_mode": "native",
   "udp_relay_mode": "native",
+  "udp_over_stream": false,
   "zero_rtt_handshake": false,
   "zero_rtt_handshake": false,
   "heartbeat": "10s",
   "heartbeat": "10s",
   "network": "tcp",
   "network": "tcp",
@@ -65,6 +66,15 @@ UDP 包中继模式
 | native | 原生 UDP                       |
 | native | 原生 UDP                       |
 | quic   | 使用 QUIC 流的无损 UDP 中继,引入了额外的开销 |
 | quic   | 使用 QUIC 流的无损 UDP 中继,引入了额外的开销 |
 
 
+与 `udp_over_stream` 冲突。
+
+#### udp_over_stream
+
+这是 TUIC 的 [UDP over TCP 协议](/configuration/shared/udp-over-tcp) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。
+
+此模式在正确的 UDP 代理场景中没有任何积极作用,仅适用于中继流式 UDP 流量(基本上是 QUIC 流)。
+
+与 `udp_relay_mode` 冲突。
 
 
 #### zero_rtt_handshake
 #### zero_rtt_handshake
 
 

+ 1 - 0
option/tuic.go

@@ -23,6 +23,7 @@ type TUICOutboundOptions struct {
 	Password          string              `json:"password,omitempty"`
 	Password          string              `json:"password,omitempty"`
 	CongestionControl string              `json:"congestion_control,omitempty"`
 	CongestionControl string              `json:"congestion_control,omitempty"`
 	UDPRelayMode      string              `json:"udp_relay_mode,omitempty"`
 	UDPRelayMode      string              `json:"udp_relay_mode,omitempty"`
+	UDPOverStream     bool                `json:"udp_over_stream,omitempty"`
 	ZeroRTTHandshake  bool                `json:"zero_rtt_handshake,omitempty"`
 	ZeroRTTHandshake  bool                `json:"zero_rtt_handshake,omitempty"`
 	Heartbeat         Duration            `json:"heartbeat,omitempty"`
 	Heartbeat         Duration            `json:"heartbeat,omitempty"`
 	Network           NetworkList         `json:"network,omitempty"`
 	Network           NetworkList         `json:"network,omitempty"`

+ 41 - 11
outbound/tuic.go

@@ -20,6 +20,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/uot"
 
 
 	"github.com/gofrs/uuid/v5"
 	"github.com/gofrs/uuid/v5"
 )
 )
@@ -31,7 +32,8 @@ var (
 
 
 type TUIC struct {
 type TUIC struct {
 	myOutboundAdapter
 	myOutboundAdapter
-	client *tuic.Client
+	client    *tuic.Client
+	udpStream bool
 }
 }
 
 
 func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) {
 func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) {
@@ -51,11 +53,14 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
 	if err != nil {
 	if err != nil {
 		return nil, E.Cause(err, "invalid uuid")
 		return nil, E.Cause(err, "invalid uuid")
 	}
 	}
-	var udpStream bool
+	var tuicUDPStream bool
+	if options.UDPOverStream && options.UDPRelayMode != "" {
+		return nil, E.New("udp_over_stream is conflict with udp_relay_mode")
+	}
 	switch options.UDPRelayMode {
 	switch options.UDPRelayMode {
 	case "native":
 	case "native":
 	case "quic":
 	case "quic":
-		udpStream = true
+		tuicUDPStream = true
 	}
 	}
 	outboundDialer, err := dialer.New(router, options.DialerOptions)
 	outboundDialer, err := dialer.New(router, options.DialerOptions)
 	if err != nil {
 	if err != nil {
@@ -69,7 +74,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
 		UUID:              userUUID,
 		UUID:              userUUID,
 		Password:          options.Password,
 		Password:          options.Password,
 		CongestionControl: options.CongestionControl,
 		CongestionControl: options.CongestionControl,
-		UDPStream:         udpStream,
+		UDPStream:         tuicUDPStream,
 		ZeroRTTHandshake:  options.ZeroRTTHandshake,
 		ZeroRTTHandshake:  options.ZeroRTTHandshake,
 		Heartbeat:         time.Duration(options.Heartbeat),
 		Heartbeat:         time.Duration(options.Heartbeat),
 	})
 	})
@@ -85,7 +90,8 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
 			tag:          tag,
 			tag:          tag,
 			dependencies: withDialerDependency(options.DialerOptions),
 			dependencies: withDialerDependency(options.DialerOptions),
 		},
 		},
-		client: client,
+		client:    client,
+		udpStream: options.UDPOverStream,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -95,19 +101,43 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So
 		h.logger.InfoContext(ctx, "outbound connection to ", destination)
 		h.logger.InfoContext(ctx, "outbound connection to ", destination)
 		return h.client.DialConn(ctx, destination)
 		return h.client.DialConn(ctx, destination)
 	case N.NetworkUDP:
 	case N.NetworkUDP:
-		conn, err := h.ListenPacket(ctx, destination)
-		if err != nil {
-			return nil, err
+		if h.udpStream {
+			h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
+			streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
+			if err != nil {
+				return nil, err
+			}
+			return uot.NewLazyConn(streamConn, uot.Request{
+				IsConnect:   true,
+				Destination: destination,
+			}), nil
+		} else {
+			conn, err := h.ListenPacket(ctx, destination)
+			if err != nil {
+				return nil, err
+			}
+			return bufio.NewBindPacketConn(conn, destination), nil
 		}
 		}
-		return bufio.NewBindPacketConn(conn, destination), nil
 	default:
 	default:
 		return nil, E.New("unsupported network: ", network)
 		return nil, E.New("unsupported network: ", network)
 	}
 	}
 }
 }
 
 
 func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
 func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
-	h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
-	return h.client.ListenPacket(ctx)
+	if h.udpStream {
+		h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
+		streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version))
+		if err != nil {
+			return nil, err
+		}
+		return uot.NewLazyConn(streamConn, uot.Request{
+			IsConnect:   false,
+			Destination: destination,
+		}), nil
+	} else {
+		h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
+		return h.client.ListenPacket(ctx)
+	}
 }
 }
 
 
 func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {