diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 49751909..42554b72 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -211,6 +211,10 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti } } + if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid { + return conn, err + } + conn, err := internet.Dial(ctx, dest, h.streamSettings) return h.getStatCouterConnection(conn), err } diff --git a/app/proxyman/outbound/uot.go b/app/proxyman/outbound/uot.go new file mode 100644 index 00000000..b1a851d9 --- /dev/null +++ b/app/proxyman/outbound/uot.go @@ -0,0 +1,25 @@ +//go:build go1.18 + +package outbound + +import ( + "context" + "os" + + "github.com/sagernet/sing/common/uot" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/stat" +) + +func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) { + if !dest.Address.Family().IsDomain() || dest.Address.Domain() != uot.UOTMagicAddress { + return nil, os.ErrInvalid + } + packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings) + if err != nil { + return nil, newError("unable to listen socket").Base(err) + } + conn := uot.NewServerConn(packetConn) + return h.getStatCouterConnection(conn), nil +} diff --git a/app/proxyman/outbound/uot_stub.go b/app/proxyman/outbound/uot_stub.go new file mode 100644 index 00000000..05f421cf --- /dev/null +++ b/app/proxyman/outbound/uot_stub.go @@ -0,0 +1,15 @@ +//go:build !go1.18 + +package outbound + +import ( + "context" + "os" + + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet/stat" +) + +func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) { + return nil, os.ErrInvalid +} diff --git a/go.mod b/go.mod index ee5c92d3..4a7f694b 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,8 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.6.2 github.com/refraction-networking/utls v1.1.0 - github.com/sagernet/sing v0.0.0-20220530041323-c82c144a8e00 - github.com/sagernet/sing-shadowsocks v0.0.0-20220531032427-182b7837c827 + github.com/sagernet/sing v0.0.0-20220601033944-4e04bbd3d84d + github.com/sagernet/sing-shadowsocks v0.0.0-20220601034426-ee9be8af94e4 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb github.com/stretchr/testify v1.7.1 github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e diff --git a/go.sum b/go.sum index 7d04076b..ec1cf9b1 100644 --- a/go.sum +++ b/go.sum @@ -172,10 +172,10 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sagernet/sing v0.0.0-20220530041323-c82c144a8e00 h1:kQCITv1yBrSSL6RIBPWTe1+goyWLbS4DELWTeRKmoh8= -github.com/sagernet/sing v0.0.0-20220530041323-c82c144a8e00/go.mod h1:w2HnJzXKHpD6F5Z/9XlSD4qbcpHY2RSZuQnFzqgELMg= -github.com/sagernet/sing-shadowsocks v0.0.0-20220531032427-182b7837c827 h1:dy8/Ltn4TW8op9esqdXBCuOVuaUSEZW2gCVE609z2UY= -github.com/sagernet/sing-shadowsocks v0.0.0-20220531032427-182b7837c827/go.mod h1:24Hwpi7iyZ8KSUqV5GpG29xSc8jUfU//RlClJcOqph4= +github.com/sagernet/sing v0.0.0-20220601033944-4e04bbd3d84d h1:BNhKTknI2tBPUOPDV3lcgwOX6iZimL7K3TPdTdp5hiA= +github.com/sagernet/sing v0.0.0-20220601033944-4e04bbd3d84d/go.mod h1:w2HnJzXKHpD6F5Z/9XlSD4qbcpHY2RSZuQnFzqgELMg= +github.com/sagernet/sing-shadowsocks v0.0.0-20220601034426-ee9be8af94e4 h1:POJZphM4VaovhB/7GjJTG+pUTcI/Q9jaOHBqTL6jq1E= +github.com/sagernet/sing-shadowsocks v0.0.0-20220601034426-ee9be8af94e4/go.mod h1:8NNH8ACh+C0oZb4ielvRJHYA4kFmhDdSQxmwAa+N8PM= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go index effff0e1..e8607993 100644 --- a/infra/conf/shadowsocks.go +++ b/infra/conf/shadowsocks.go @@ -136,6 +136,7 @@ type ShadowsocksServerTarget struct { Email string `json:"email"` Level byte `json:"level"` IVCheck bool `json:"ivCheck"` + UoT bool `json:"uot"` } type ShadowsocksClientConfig struct { @@ -165,6 +166,7 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) { config.Port = uint32(server.Port) config.Method = server.Cipher config.Key = server.Password + config.UdpOverTcp = server.UoT return config, nil } } diff --git a/infra/conf/shadowsocks_legacy.go b/infra/conf/shadowsocks_legacy.go index 7bd6e6d4..c3f0e67a 100644 --- a/infra/conf/shadowsocks_legacy.go +++ b/infra/conf/shadowsocks_legacy.go @@ -1,4 +1,5 @@ //go:build !go1.18 + package conf import ( @@ -98,6 +99,7 @@ type ShadowsocksServerTarget struct { Email string `json:"email"` Level byte `json:"level"` IVCheck bool `json:"ivCheck"` + UoT bool `json:"uot"` } type ShadowsocksClientConfig struct { diff --git a/proxy/shadowsocks_2022/config.pb.go b/proxy/shadowsocks_2022/config.pb.go index 1ab3b047..cd5a1de8 100644 --- a/proxy/shadowsocks_2022/config.pb.go +++ b/proxy/shadowsocks_2022/config.pb.go @@ -239,10 +239,11 @@ type ClientConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` - Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` - Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` + Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` + UdpOverTcp bool `protobuf:"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"` } func (x *ClientConfig) Reset() { @@ -305,6 +306,13 @@ func (x *ClientConfig) GetKey() string { return "" } +func (x *ClientConfig) GetUdpOverTcp() bool { + if x != nil { + return x.UdpOverTcp + } + return false +} + var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ @@ -340,7 +348,7 @@ var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x83, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, + 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xa5, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, @@ -348,15 +356,17 @@ var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x72, - 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, - 0x32, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, - 0x5f, 0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, - 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x32, 0x30, - 0x32, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x20, + 0x0a, 0x0c, 0x75, 0x64, 0x70, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x63, 0x70, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x4f, 0x76, 0x65, 0x72, 0x54, 0x63, 0x70, + 0x42, 0x72, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, + 0x30, 0x32, 0x32, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, + 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, + 0x32, 0x30, 0x32, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/shadowsocks_2022/config.proto b/proxy/shadowsocks_2022/config.proto index cc6cc383..83b4f588 100644 --- a/proxy/shadowsocks_2022/config.proto +++ b/proxy/shadowsocks_2022/config.proto @@ -35,4 +35,5 @@ message ClientConfig { uint32 port = 2; string method = 3; string key = 4; + bool udp_over_tcp = 5; } diff --git a/proxy/shadowsocks_2022/inbound.go b/proxy/shadowsocks_2022/inbound.go index ecea7107..6ff7fc67 100644 --- a/proxy/shadowsocks_2022/inbound.go +++ b/proxy/shadowsocks_2022/inbound.go @@ -10,6 +10,7 @@ import ( C "github.com/sagernet/sing/common" B "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/xtls/xray-core/common" @@ -74,20 +75,23 @@ func (i *Inbound) Process(ctx context.Context, network net.Network, connection s ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return i.service.NewConnection(ctx, connection, metadata) + return returnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} for { mb, err := reader.ReadMultiBuffer() if err != nil { - return err + buf.ReleaseMulti(mb) + return returnError(err) } for _, buffer := range mb { - err = i.service.NewPacket(ctx, pc, B.As(buffer.Bytes()), metadata) + err = i.service.NewPacket(ctx, pc, B.As(buffer.Bytes()).ToOwned(), metadata) if err != nil { + buf.ReleaseMulti(mb) return err } + buffer.Release() } } } @@ -147,6 +151,9 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me } func (i *Inbound) HandleError(err error) { + if E.IsClosed(err) { + return + } newError(err).AtWarning().WriteToLog() } diff --git a/proxy/shadowsocks_2022/inbound_multi.go b/proxy/shadowsocks_2022/inbound_multi.go index 19ca9050..03ab1f9b 100644 --- a/proxy/shadowsocks_2022/inbound_multi.go +++ b/proxy/shadowsocks_2022/inbound_multi.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-shadowsocks/shadowaead_2022" B "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/xtls/xray-core/common" @@ -93,20 +94,23 @@ func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, con ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return i.service.NewConnection(ctx, connection, metadata) + return returnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} for { mb, err := reader.ReadMultiBuffer() if err != nil { - return err + buf.ReleaseMulti(mb) + return returnError(err) } for _, buffer := range mb { - err = i.service.NewPacket(ctx, pc, B.As(buffer.Bytes()), metadata) + err = i.service.NewPacket(ctx, pc, B.As(buffer.Bytes()).ToOwned(), metadata) if err != nil { + buf.ReleaseMulti(mb) return err } + buffer.Release() } } } @@ -170,5 +174,8 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe } func (i *MultiUserInbound) HandleError(err error) { + if E.IsClosed(err) { + return + } newError(err).AtWarning().WriteToLog() } diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index 6686f981..f4bd3121 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -13,7 +13,9 @@ import ( C "github.com/sagernet/sing/common" B "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" @@ -32,6 +34,7 @@ type Outbound struct { ctx context.Context server net.Destination method shadowsocks.Method + uot bool } func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { @@ -42,6 +45,7 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { Port: net.Port(config.Port), Network: net.Network_TCP, }, + uot: config.UdpOverTcp, } if C.Contains(shadowaead_2022.List, config.Method) { if config.Key == "" { @@ -75,7 +79,11 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int newError("tunneling request to ", destination, " via ", o.server.NetAddr()).WriteToLog(session.ExportIDToError(ctx)) serverDestination := o.server - serverDestination.Network = network + if o.uot { + serverDestination.Network = net.Network_TCP + } else { + serverDestination.Network = network + } connection, err := dialer.Dial(ctx, serverDestination) if err != nil { return newError("failed to connect to server").Base(err) @@ -143,7 +151,12 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } } - serverConn := o.method.DialPacketConn(connection) - return returnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) + if o.uot { + serverConn := o.method.DialEarlyConn(connection, M.Socksaddr{Fqdn: uot.UOTMagicAddress}) + return returnError(bufio.CopyPacketConn(ctx, packetConn, uot.NewClientConn(serverConn))) + } else { + serverConn := o.method.DialPacketConn(connection) + return returnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) + } } } diff --git a/proxy/shadowsocks_2022/shadowsocks_2022.go b/proxy/shadowsocks_2022/shadowsocks_2022.go index fa9e6afb..c238ecf3 100644 --- a/proxy/shadowsocks_2022/shadowsocks_2022.go +++ b/proxy/shadowsocks_2022/shadowsocks_2022.go @@ -3,10 +3,10 @@ package shadowsocks_2022 import ( - "errors" "io" B "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" @@ -140,7 +140,7 @@ func (w *packetConnWrapper) Close() error { } func returnError(err error) error { - if errors.Is(err, io.EOF) { + if E.IsClosed(err) { return nil } return err diff --git a/testing/scenarios/shadowsocks_2022_test.go b/testing/scenarios/shadowsocks_2022_test.go index 69904dea..aa595844 100644 --- a/testing/scenarios/shadowsocks_2022_test.go +++ b/testing/scenarios/shadowsocks_2022_test.go @@ -3,11 +3,10 @@ package scenarios import ( "crypto/rand" "encoding/base64" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "github.com/xtls/xray-core/proxy/shadowsocks_2022" "testing" "time" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" "github.com/xtls/xray-core/app/log" "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/common" @@ -17,6 +16,7 @@ import ( "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/proxy/dokodemo" "github.com/xtls/xray-core/proxy/freedom" + "github.com/xtls/xray-core/proxy/shadowsocks_2022" "github.com/xtls/xray-core/testing/servers/tcp" "github.com/xtls/xray-core/testing/servers/udp" "golang.org/x/sync/errgroup"