Add udp over stream support for TUIC

This commit is contained in:
世界 2023-08-20 11:28:30 +08:00
parent 5c5c25e3ad
commit f46732bc0e
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 66 additions and 11 deletions

View file

@ -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

View file

@ -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

View file

@ -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"`

View file

@ -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 h.udpStream {
if err != nil { h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
return nil, err 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) if h.udpStream {
return h.client.ListenPacket(ctx) 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 {