Update UoT protocol

This commit is contained in:
世界 2023-03-17 12:24:29 +08:00
parent a3a5185b15
commit 43f31b40ba
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
16 changed files with 186 additions and 118 deletions

View file

@ -12,7 +12,7 @@
"plugin": "", "plugin": "",
"plugin_opts": "", "plugin_opts": "",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false | {},
"multiplex": {}, "multiplex": {},
... // Dial Fields ... // Dial Fields
@ -87,7 +87,9 @@ Both is enabled by default.
#### udp_over_tcp #### udp_over_tcp
Enable the UDP over TCP protocol. UDP over TCP configuration.
See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details.
Conflict with `multiplex`. Conflict with `multiplex`.

View file

@ -12,7 +12,7 @@
"plugin": "", "plugin": "",
"plugin_opts": "", "plugin_opts": "",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false | {},
"multiplex": {}, "multiplex": {},
... // 拨号字段 ... // 拨号字段
@ -87,7 +87,9 @@ Shadowsocks SIP003 插件参数。
#### udp_over_tcp #### udp_over_tcp
启用 UDP over TCP 协议。 UDP over TCP 配置。
参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。
`multiplex` 冲突。 `multiplex` 冲突。

View file

@ -13,7 +13,7 @@
"username": "sekai", "username": "sekai",
"password": "admin", "password": "admin",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false | {},
... // Dial Fields ... // Dial Fields
} }
@ -57,7 +57,9 @@ Both is enabled by default.
#### udp_over_tcp #### udp_over_tcp
Enable the UDP over TCP protocol. UDP over TCP protocol settings.
See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details.
### Dial Fields ### Dial Fields

View file

@ -13,7 +13,7 @@
"username": "sekai", "username": "sekai",
"password": "admin", "password": "admin",
"network": "udp", "network": "udp",
"udp_over_tcp": false, "udp_over_tcp": false | {},
... // 拨号字段 ... // 拨号字段
} }
@ -57,7 +57,9 @@ SOCKS5 密码。
#### udp_over_tcp #### udp_over_tcp
启用 UDP over TCP 协议。 UDP over TCP 配置。
参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。
### 拨号字段 ### 拨号字段

View file

@ -0,0 +1,81 @@
# UDP over TCP
!!! warning ""
It's a proprietary protocol created by SagerNet, not part of shadowsocks.
The UDP over TCP protocol is used to transmit UDP packets in TCP.
### Structure
```json
{
"enabled": true,
"version": 2
}
```
!!! info ""
The structure can be replaced with a boolean value when the version is not specified.
### Fields
#### enabled
Enable the UDP over TCP protocol.
#### version
The protocol version, `1` or `2`.
2 is used by default.
### Application support
| Project | UoT v1 | UoT v2 |
|--------------|----------------------|------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Xray-core | v1.5.7 (2022/06/05) | / |
| Clash.Meta | v1.12.0 (2022/07/02) | / |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
### Protocol details
#### Protocol version 1
The client requests the magic address to the upper layer proxy protocol to indicate the request: `sp.udp-over-tcp.arpa`
#### Stream format
| ATYP | address | port | length | data |
|------|----------|-------|--------|----------|
| u8 | variable | u16be | u16be | variable |
*ATYP / address / port*: Uses the SOCKS address format.
#### Protocol version 2
Protocol version 2 uses a new magic address: `sp.v2.udp-over-tcp.arpa`
##### Request format
| isConnect | ATYP | address | port |
|-----------|------|----------|-------|
| u8 | u8 | variable | u16be |
**version**: Fixed to 2.
**isConnect**: Set to 1 to indicates that the stream uses the connect format, 0 to disable.
**ATYP / address / port**: Request destination, uses the SOCKS address format.
##### Connect stream format
| length | data |
|--------|----------|
| u16be | variable |
##### Non-connect stream format
As the same as the stream format in protocol version 1.

View file

@ -1,5 +1,9 @@
# Shadowsocks # Shadowsocks
!!! warning ""
For censorship bypass usage in China, we recommend using UDP over TCP and disabling UDP on the server.
## Single User ## Single User
#### Server #### Server
@ -11,6 +15,7 @@
"type": "shadowsocks", "type": "shadowsocks",
"listen": "::", "listen": "::",
"listen_port": 8080, "listen_port": 8080,
"network": "tcp",
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==" "password": "8JCsPssfgS8tiRwiMlhARg=="
} }
@ -35,7 +40,8 @@
"server": "127.0.0.1", "server": "127.0.0.1",
"server_port": 8080, "server_port": 8080,
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==" "password": "8JCsPssfgS8tiRwiMlhARg==",
"udp_over_tcp": true
} }
] ]
} }

View file

@ -24,6 +24,7 @@
"type": "shadowsocks", "type": "shadowsocks",
"tag": "shadowsocks-in", "tag": "shadowsocks-in",
"listen": "127.0.0.1", "listen": "127.0.0.1",
"network": "tcp",
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==" "password": "8JCsPssfgS8tiRwiMlhARg=="
} }
@ -46,6 +47,7 @@
"max_connections": 4, "max_connections": 4,
"min_streams": 4 "min_streams": 4
} }
// or "udp_over_tcp": true
}, },
{ {
"type": "shadowtls", "type": "shadowtls",

2
go.mod
View file

@ -25,7 +25,7 @@ require (
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8
github.com/sagernet/sing v0.1.9-0.20230315163130-ed73785ecc78 github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6
github.com/sagernet/sing-dns v0.1.4 github.com/sagernet/sing-dns v0.1.4
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
github.com/sagernet/sing-shadowtls v0.1.0 github.com/sagernet/sing-shadowtls v0.1.0

4
go.sum
View file

@ -111,8 +111,8 @@ github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 h1:4M3+0/kqvJuTsi
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/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.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.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.1.9-0.20230315163130-ed73785ecc78 h1:SO7TITxjoKyQFBVR0MJhTji9msxEXcv5p60imPrEyY4= github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6 h1:h1wGLPBJLjujj9kYSbLiP1Tt6+IQnZ7Ok7jQd4u3xxk=
github.com/sagernet/sing v0.1.9-0.20230315163130-ed73785ecc78/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw= github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0= github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk= github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0= github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=

View file

@ -64,6 +64,7 @@ nav:
- TLS: configuration/shared/tls.md - TLS: configuration/shared/tls.md
- Multiplex: configuration/shared/multiplex.md - Multiplex: configuration/shared/multiplex.md
- V2Ray Transport: configuration/shared/v2ray-transport.md - V2Ray Transport: configuration/shared/v2ray-transport.md
- UDP over TCP: configuration/shared/udp-over-tcp.md
- Inbound: - Inbound:
- configuration/inbound/index.md - configuration/inbound/index.md
- Direct: configuration/inbound/direct.md - Direct: configuration/inbound/direct.md

View file

@ -28,7 +28,6 @@ type ShadowsocksOutboundOptions struct {
Plugin string `json:"plugin,omitempty"` Plugin string `json:"plugin,omitempty"`
PluginOptions string `json:"plugin_opts,omitempty"` PluginOptions string `json:"plugin_opts,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
UoT bool `json:"udp_over_tcp,omitempty"` UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
UoTVersion int `json:"udp_over_tcp_version,omitempty"`
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
} }

View file

@ -21,8 +21,7 @@ type SocksOutboundOptions struct {
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
UoT bool `json:"udp_over_tcp,omitempty"` UDPOverTCPOptions *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
UoTVersion int `json:"udp_over_tcp_version,omitempty"`
} }
type HTTPOutboundOptions struct { type HTTPOutboundOptions struct {

30
option/udp_over_tcp.go Normal file
View file

@ -0,0 +1,30 @@
package option
import (
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing/common/uot"
)
type _UDPOverTCPOptions struct {
Enabled bool `json:"enabled,omitempty"`
Version uint8 `json:"version,omitempty"`
}
type UDPOverTCPOptions _UDPOverTCPOptions
func (o UDPOverTCPOptions) MarshalJSON() ([]byte, error) {
switch o.Version {
case 0, uot.Version:
return json.Marshal(o.Enabled)
default:
return json.Marshal(_UDPOverTCPOptions(o))
}
}
func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, &o.Enabled)
if err == nil {
return nil
}
return json.Unmarshal(bytes, (*_UDPOverTCPOptions)(o))
}

View file

@ -29,8 +29,7 @@ type Shadowsocks struct {
method shadowsocks.Method method shadowsocks.Method
serverAddr M.Socksaddr serverAddr M.Socksaddr
plugin sip003.Plugin plugin sip003.Plugin
uot bool uotClient *uot.Client
uotVersion int
multiplexDialer N.Dialer multiplexDialer N.Dialer
} }
@ -50,7 +49,6 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
dialer: dialer.New(router, options.DialerOptions), dialer: dialer.New(router, options.DialerOptions),
method: method, method: method,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
uot: options.UoT,
} }
if options.Plugin != "" { if options.Plugin != "" {
outbound.plugin, err = sip003.CreatePlugin(options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr) outbound.plugin, err = sip003.CreatePlugin(options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
@ -58,19 +56,18 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
return nil, err return nil, err
} }
} }
if !options.UoT { uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
if !uotOptions.Enabled {
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions)) outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
switch options.UoTVersion { if uotOptions.Enabled {
case uot.LegacyVersion: outbound.uotClient = &uot.Client{
outbound.uotVersion = uot.LegacyVersion Dialer: (*shadowsocksDialer)(outbound),
case 0, uot.Version: Version: uotOptions.Version,
outbound.uotVersion = uot.Version }
default:
return nil, E.New("unknown udp over tcp protocol version ", options.UoTVersion)
} }
return outbound, nil return outbound, nil
} }
@ -84,26 +81,13 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati
case N.NetworkTCP: case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination) h.logger.InfoContext(ctx, "outbound connection to ", destination)
case N.NetworkUDP: case N.NetworkUDP:
if h.uot { if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) h.logger.InfoContext(ctx, "outbound UoT connect packet connection to ", destination)
var uotDestination M.Socksaddr return h.uotClient.DialContext(ctx, network, destination)
if h.uotVersion == uot.Version {
uotDestination.Fqdn = uot.MagicAddress
} else { } else {
uotDestination.Fqdn = uot.LegacyMagicAddress
}
tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, uotDestination)
if err != nil {
return nil, err
}
if h.uotVersion == uot.Version {
return uot.NewLazyConn(tcpConn, uot.Request{IsConnect: true, Destination: destination}), nil
} else {
return uot.NewConn(tcpConn, false, destination), nil
}
}
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
} }
}
return (*shadowsocksDialer)(h).DialContext(ctx, network, destination) return (*shadowsocksDialer)(h).DialContext(ctx, network, destination)
} else { } else {
switch N.NetworkName(network) { switch N.NetworkName(network) {
@ -121,23 +105,11 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr)
metadata.Outbound = h.tag metadata.Outbound = h.tag
metadata.Destination = destination metadata.Destination = destination
if h.multiplexDialer == nil { if h.multiplexDialer == nil {
if h.uot { if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
var uotDestination M.Socksaddr return h.uotClient.ListenPacket(ctx, destination)
if h.uotVersion == uot.Version {
uotDestination.Fqdn = uot.MagicAddress
} else { } else {
uotDestination.Fqdn = uot.LegacyMagicAddress h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, uotDestination)
if err != nil {
return nil, err
}
if h.uotVersion == uot.Version {
return uot.NewLazyConn(tcpConn, uot.Request{Destination: destination}), nil
} else {
return uot.NewConn(tcpConn, false, destination), nil
}
} }
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*shadowsocksDialer)(h).ListenPacket(ctx, destination) return (*shadowsocksDialer)(h).ListenPacket(ctx, destination)

View file

@ -9,6 +9,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
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"
@ -22,8 +23,7 @@ type Socks struct {
myOutboundAdapter myOutboundAdapter
client *socks.Client client *socks.Client
resolve bool resolve bool
uot bool uotClient *uot.Client
uotVersion int
} }
func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) {
@ -47,15 +47,13 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
}, },
client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password), client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password),
resolve: version == socks.Version4, resolve: version == socks.Version4,
uot: options.UoT,
} }
switch options.UoTVersion { uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
case uot.LegacyVersion: if uotOptions.Enabled {
outbound.uotVersion = uot.LegacyVersion outbound.uotClient = &uot.Client{
case 0, uot.Version: Dialer: outbound.client,
outbound.uotVersion = uot.Version Version: uotOptions.Version,
default: }
return nil, E.New("unknown udp over tcp protocol version ", options.UoTVersion)
} }
return outbound, nil return outbound, nil
} }
@ -68,23 +66,9 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S
case N.NetworkTCP: case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination) h.logger.InfoContext(ctx, "outbound connection to ", destination)
case N.NetworkUDP: case N.NetworkUDP:
if h.uot { if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) h.logger.InfoContext(ctx, "outbound UoT connect packet connection to ", destination)
var uotDestination M.Socksaddr return h.uotClient.DialContext(ctx, network, destination)
if h.uotVersion == uot.Version {
uotDestination.Fqdn = uot.MagicAddress
} else {
uotDestination.Fqdn = uot.LegacyMagicAddress
}
tcpConn, err := h.client.DialContext(ctx, N.NetworkTCP, uotDestination)
if err != nil {
return nil, err
}
if h.uotVersion == uot.Version {
return uot.NewLazyConn(tcpConn, uot.Request{IsConnect: true, Destination: destination}), nil
} else {
return uot.NewConn(tcpConn, false, destination), nil
}
} }
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
default: default:
@ -104,23 +88,9 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
ctx, metadata := adapter.AppendContext(ctx) ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.tag
metadata.Destination = destination metadata.Destination = destination
if h.uot { if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
var uotDestination M.Socksaddr return h.uotClient.ListenPacket(ctx, destination)
if h.uotVersion == uot.Version {
uotDestination.Fqdn = uot.MagicAddress
} else {
uotDestination.Fqdn = uot.LegacyMagicAddress
}
tcpConn, err := h.client.DialContext(ctx, N.NetworkTCP, uotDestination)
if err != nil {
return nil, err
}
if h.uotVersion == uot.Version {
return uot.NewLazyConn(tcpConn, uot.Request{Destination: destination}), nil
} else {
return uot.NewConn(tcpConn, false, destination), nil
}
} }
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.ListenPacket(ctx, destination) return h.client.ListenPacket(ctx, destination)

View file

@ -589,12 +589,12 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
} }
metadata.Domain = metadata.Destination.Fqdn metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination metadata.Destination = request.Destination
return r.RoutePacketConnection(ctx, uot.NewConn(conn, request.IsConnect, metadata.Destination), metadata) return r.RoutePacketConnection(ctx, uot.NewConn(conn, *request), metadata)
case uot.LegacyMagicAddress: case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection") r.logger.InfoContext(ctx, "inbound legacy UoT connection")
metadata.Domain = metadata.Destination.Fqdn metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
return r.RoutePacketConnection(ctx, uot.NewConn(conn, false, metadata.Destination), metadata) return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
} }
if metadata.InboundOptions.SniffEnabled { if metadata.InboundOptions.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()