UDP-over-TCP (UoT)
Source: common/uot/router.go
Overview
UoT (UDP-over-TCP) tunnels UDP traffic through TCP connections. It intercepts connections destined for magic sentinel addresses and converts them into packet-based connections using github.com/sagernet/sing/common/uot.
Magic Addresses
Two sentinel addresses signal UoT connections:
uot.MagicAddress-- Current UoT protocol with request headeruot.LegacyMagicAddress-- Legacy UoT without request header
Router
The Router wraps an existing ConnectionRouterEx and intercepts connections by destination FQDN:
type Router struct {
router adapter.ConnectionRouterEx
logger logger.ContextLogger
}
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
return &Router{router, logger}
}Connection Handling (Ex variant)
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn,
metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
switch metadata.Destination.Fqdn {
case uot.MagicAddress:
request, err := uot.ReadRequest(conn)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
if request.IsConnect {
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
} else {
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
}
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
return
case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)
return
}
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}UoT Request Header
For the current protocol (uot.MagicAddress), a request header is read from the connection:
- Destination: The actual UDP destination address
- IsConnect: Boolean flag indicating connect mode vs regular mode
In connect mode, the connection behaves like a connected UDP socket to a single destination. In regular mode, each packet carries its own destination address.
Legacy Protocol
The legacy protocol (uot.LegacyMagicAddress) has no request header. The destination is set to 0.0.0.0 (IPv4 unspecified), and an empty Request{} is used.
Passthrough
Connections not matching either magic address are passed through to the underlying router unchanged:
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)Packet Connection Conversion
uot.NewConn(conn, request) wraps the TCP connection as a N.PacketConn. The UoT protocol frames individual UDP packets within the TCP stream, handling:
- Packet length framing
- Per-packet destination addressing (non-connect mode)
- Bidirectional packet streaming
The resulting packet connection is then routed through RoutePacketConnectionEx for standard UDP processing.