diff --git a/go.mod b/go.mod index afb8d041..cc609cfc 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 - github.com/sagernet/sing v0.1.9-0.20230313033500-448948d26d1a + github.com/sagernet/sing v0.1.9-0.20230315063014-2731df16725b github.com/sagernet/sing-dns v0.1.4 github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 github.com/sagernet/sing-shadowtls v0.1.0 @@ -51,7 +51,7 @@ require ( gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c ) -//replace github.com/sagernet/sing-tun => ../sing-tun +//replace github.com/sagernet/sing => ../sing require ( github.com/ajg/form v1.5.1 // indirect diff --git a/go.sum b/go.sum index 91b120c0..806a7829 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,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/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.9-0.20230313033500-448948d26d1a h1:JgPPxKLiqA95Z0oTp9FyYUfij8xsjS2rBWtpQ41zFTo= -github.com/sagernet/sing v0.1.9-0.20230313033500-448948d26d1a/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw= +github.com/sagernet/sing v0.1.9-0.20230315063014-2731df16725b h1:1iKGftQ59+shDSx2RaLaxXJcMK/B+IU9WqUPwyBW+E0= +github.com/sagernet/sing v0.1.9-0.20230315063014-2731df16725b/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/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk= github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0= diff --git a/option/shadowsocks.go b/option/shadowsocks.go index 56b528b2..d12e9626 100644 --- a/option/shadowsocks.go +++ b/option/shadowsocks.go @@ -29,5 +29,6 @@ type ShadowsocksOutboundOptions struct { PluginOptions string `json:"plugin_opts,omitempty"` Network NetworkList `json:"network,omitempty"` UoT bool `json:"udp_over_tcp,omitempty"` + UoTVersion int `json:"udp_over_tcp_version,omitempty"` MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` } diff --git a/option/simple.go b/option/simple.go index 32645fbe..f48dc51e 100644 --- a/option/simple.go +++ b/option/simple.go @@ -17,11 +17,12 @@ type HTTPMixedInboundOptions struct { type SocksOutboundOptions struct { DialerOptions ServerOptions - Version string `json:"version,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Network NetworkList `json:"network,omitempty"` - UoT bool `json:"udp_over_tcp,omitempty"` + Version string `json:"version,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Network NetworkList `json:"network,omitempty"` + UoT bool `json:"udp_over_tcp,omitempty"` + UoTVersion int `json:"udp_over_tcp_version,omitempty"` } type HTTPOutboundOptions struct { diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 2831d4b1..a037db1d 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -30,6 +30,7 @@ type Shadowsocks struct { serverAddr M.Socksaddr plugin sip003.Plugin uot bool + uotVersion int multiplexDialer N.Dialer } @@ -63,6 +64,14 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return nil, err } } + switch options.UoTVersion { + case uot.LegacyVersion: + outbound.uotVersion = uot.LegacyVersion + case 0, uot.Version: + outbound.uotVersion = uot.Version + default: + return nil, E.New("unknown udp over tcp protocol version ", options.UoTVersion) + } return outbound, nil } @@ -77,14 +86,21 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati 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, - }) + var uotDestination M.Socksaddr + if h.uotVersion == uot.Version { + uotDestination.Fqdn = uot.MagicAddress + } else { + uotDestination.Fqdn = uot.LegacyMagicAddress + } + tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, uotDestination) if err != nil { return nil, err } - return uot.NewClientConn(tcpConn), nil + 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) } @@ -107,14 +123,21 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) 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, - }) + var uotDestination M.Socksaddr + if h.uotVersion == uot.Version { + uotDestination.Fqdn = uot.MagicAddress + } else { + uotDestination.Fqdn = uot.LegacyMagicAddress + } + tcpConn, err := (*shadowsocksDialer)(h).DialContext(ctx, N.NetworkTCP, uotDestination) if err != nil { return nil, err } - return uot.NewClientConn(tcpConn), nil + 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) return (*shadowsocksDialer)(h).ListenPacket(ctx, destination) diff --git a/outbound/socks.go b/outbound/socks.go index 50b1f7c1..0f336d32 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -20,13 +20,13 @@ var _ adapter.Outbound = (*Socks)(nil) type Socks struct { myOutboundAdapter - client *socks.Client - resolve bool - uot bool + client *socks.Client + resolve bool + uot bool + uotVersion int } func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { - detour := dialer.New(router, options.DialerOptions) var version socks.Version var err error if options.Version != "" { @@ -37,18 +37,27 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - return &Socks{ - myOutboundAdapter{ + outbound := &Socks{ + myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeSocks, network: options.Network.Build(), router: router, logger: logger, tag: tag, }, - socks.NewClient(detour, options.ServerOptions.Build(), version, options.Username, options.Password), - version == socks.Version4, - options.UoT, - }, nil + client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password), + resolve: version == socks.Version4, + uot: options.UoT, + } + switch options.UoTVersion { + case uot.LegacyVersion: + outbound.uotVersion = uot.LegacyVersion + case 0, uot.Version: + outbound.uotVersion = uot.Version + default: + return nil, E.New("unknown udp over tcp protocol version ", options.UoTVersion) + } + return outbound, nil } func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { @@ -61,14 +70,21 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S case N.NetworkUDP: if h.uot { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) - tcpConn, err := h.client.DialContext(ctx, N.NetworkTCP, M.Socksaddr{ - Fqdn: uot.UOTMagicAddress, - Port: destination.Port, - }) + var uotDestination M.Socksaddr + 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 } - return uot.NewClientConn(tcpConn), nil + 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) default: @@ -90,14 +106,21 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. metadata.Destination = destination if h.uot { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) - tcpConn, err := h.client.DialContext(ctx, N.NetworkTCP, M.Socksaddr{ - Fqdn: uot.UOTMagicAddress, - Port: destination.Port, - }) + var uotDestination M.Socksaddr + 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 } - return uot.NewClientConn(tcpConn), nil + 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) return h.client.ListenPacket(ctx, destination) diff --git a/route/router.go b/route/router.go index 7b48f260..6e2f67cb 100644 --- a/route/router.go +++ b/route/router.go @@ -577,10 +577,24 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad case vmess.MuxDestination.Fqdn: r.logger.InfoContext(ctx, "inbound legacy multiplex connection") return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)) - case uot.UOTMagicAddress: - r.logger.InfoContext(ctx, "inbound UoT connection") + case uot.MagicAddress: + request, err := uot.ReadRequest(conn) + if err != nil { + return E.Cause(err, "read UoT request") + } + 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 + return r.RoutePacketConnection(ctx, uot.NewConn(conn, request.IsConnect, metadata.Destination), metadata) + case uot.LegacyMagicAddress: + r.logger.InfoContext(ctx, "inbound legacy UoT connection") + metadata.Domain = metadata.Destination.Fqdn metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} - return r.RoutePacketConnection(ctx, uot.NewClientConn(conn), metadata) + return r.RoutePacketConnection(ctx, uot.NewConn(conn, false, metadata.Destination), metadata) } if metadata.InboundOptions.SniffEnabled { buffer := buf.NewPacket() @@ -685,7 +699,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m } conn = bufio.NewCachedPacketConn(conn, buffer, destination) } - if metadata.Destination.IsFqdn() && metadata.Destination.Fqdn != uot.UOTMagicAddress && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { + if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) if err != nil { return err