Add back UoT support

This commit is contained in:
世界 2022-08-11 10:36:28 +08:00
parent cf80073f27
commit 517a89fa9c
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
7 changed files with 119 additions and 11 deletions

View file

@ -51,7 +51,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
} }
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsFqdn() { if !destination.IsFqdn() || destination.Fqdn == "" {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx, metadata := adapter.AppendContext(ctx) ctx, metadata := adapter.AppendContext(ctx)

View file

@ -1,9 +1,13 @@
#### 2022/08/09 #### 2022/08/11
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds.
#### 2022/08/10
* Add full-featured [Naive](/configuration/inbound/naive) inbound * Add full-featured [Naive](/configuration/inbound/naive) inbound
* Fix default dns server option [#9] by iKirby * Fix default dns server option [#9] by iKirby
#### 2022/08/08 #### 2022/08/09
No changelog before. No changelog before.

View file

@ -14,6 +14,7 @@
"method": "2022-blake3-aes-128-gcm", "method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg==", "password": "8JCsPssfgS8tiRwiMlhARg==",
"network": "udp", "network": "udp",
"udp_over_tcp": false,
"multiplex": {}, "multiplex": {},
"detour": "upstream-out", "detour": "upstream-out",
@ -85,6 +86,12 @@ One of `tcp` `udp`.
Both is enabled by default. Both is enabled by default.
#### udp_over_tcp
Enable UDP over TCP protocol.
Conflict with `multiplex`.
#### multiplex #### multiplex
Multiplex configuration, see [Multiplex structure](/configuration/shared/multiplex). Multiplex configuration, see [Multiplex structure](/configuration/shared/multiplex).

View file

@ -27,5 +27,6 @@ type ShadowsocksOutboundOptions struct {
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
UoT bool `json:"udp_over_tcp,omitempty"`
MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"` MultiplexOptions *MultiplexOptions `json:"multiplex,omitempty"`
} }

View file

@ -17,6 +17,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"
) )
var _ adapter.Outbound = (*Shadowsocks)(nil) var _ adapter.Outbound = (*Shadowsocks)(nil)
@ -26,6 +27,7 @@ type Shadowsocks struct {
dialer N.Dialer dialer N.Dialer
method shadowsocks.Method method shadowsocks.Method
serverAddr M.Socksaddr serverAddr M.Socksaddr
uot bool
multiplexDialer N.Dialer multiplexDialer N.Dialer
} }
@ -45,11 +47,14 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
dialer: dialer.NewOutbound(router, options.OutboundDialerOptions), dialer: dialer.NewOutbound(router, options.OutboundDialerOptions),
method: method, method: method,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
uot: options.UoT,
} }
if !options.UoT {
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
} }
}
return outbound, nil return outbound, nil
} }
@ -59,6 +64,17 @@ 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 {
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,
})
if err != nil {
return nil, err
}
return uot.NewClientConn(tcpConn), 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)
@ -75,6 +91,17 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati
func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if h.multiplexDialer == nil { 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,
})
if err != nil {
return nil, err
}
return uot.NewClientConn(tcpConn), 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)
} else { } else {

View file

@ -35,6 +35,7 @@ import (
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/rw" "github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/uot"
) )
var warnDefaultInterfaceOnUnsupportedPlatform = warning.New( var warnDefaultInterfaceOnUnsupportedPlatform = warning.New(
@ -492,9 +493,15 @@ func (r *Router) DefaultOutbound(network string) adapter.Outbound {
} }
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination.Fqdn == mux.Destination.Fqdn { switch metadata.Destination.Fqdn {
case mux.Destination.Fqdn:
r.logger.InfoContext(ctx, "inbound multiplex connection") r.logger.InfoContext(ctx, "inbound multiplex connection")
return mux.NewConnection(ctx, r, r, r.logger, conn, metadata) return mux.NewConnection(ctx, r, r, r.logger, conn, metadata)
case uot.UOTMagicAddress:
r.logger.InfoContext(ctx, "inbound UoT connection")
metadata.Network = N.NetworkUDP
metadata.Destination = M.Socksaddr{}
return r.RoutePacketConnection(ctx, uot.NewClientConn(conn), metadata)
} }
if metadata.SniffEnabled { if metadata.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()
@ -543,13 +550,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
if metadata.SniffEnabled { if metadata.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()
buffer.FullReset() buffer.FullReset()
_, err := conn.ReadPacket(buffer) destination, err := conn.ReadPacket(buffer)
if err != nil { if err != nil {
buffer.Release() buffer.Release()
return err return err
} }
sniffMetadata, err := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) sniffMetadata, err := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
originDestination := metadata.Destination
if err == nil { if err == nil {
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
@ -562,9 +568,9 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol) r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
} }
} }
conn = bufio.NewCachedPacketConn(conn, buffer, originDestination) conn = bufio.NewCachedPacketConn(conn, buffer, destination)
} }
if metadata.Destination.IsFqdn() && metadata.DomainStrategy != dns.DomainStrategyAsIS { if metadata.Destination.IsFqdn() && metadata.Destination.Fqdn != uot.UOTMagicAddress && metadata.DomainStrategy != dns.DomainStrategyAsIS {
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, metadata.DomainStrategy) addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, metadata.DomainStrategy)
if err != nil { if err != nil {
return err return err

View file

@ -8,6 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -193,6 +194,68 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
testSuit(t, clientPort, testPort) testSuit(t, clientPort, testPort)
} }
func TestShadowsocksUoT(t *testing.T) {
method := shadowaead_2022.List[0]
password := mkBase64(t, 16)
startInstance(t, option.Options{
Log: &option.LogOptions{
Level: "trace",
},
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
Tag: "mixed-in",
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
{
Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: serverPort,
},
Method: method,
Password: password,
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeDirect,
},
{
Type: C.TypeShadowsocks,
Tag: "ss-out",
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
Method: method,
Password: password,
UoT: true,
},
},
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
DefaultOptions: option.DefaultRule{
Inbound: []string{"mixed-in"},
Outbound: "ss-out",
},
},
},
},
})
testSuit(t, clientPort, testPort)
}
func mkBase64(t *testing.T, length int) string { func mkBase64(t *testing.T, length int) string {
psk := make([]byte, length) psk := make([]byte, length)
_, err := rand.Read(psk) _, err := rand.Read(psk)