diff --git a/common/singbridge/destination.go b/common/singbridge/destination.go new file mode 100644 index 00000000..7a89c9ef --- /dev/null +++ b/common/singbridge/destination.go @@ -0,0 +1,46 @@ +package singbridge + +import ( + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/net" +) + +func ToNetwork(network string) net.Network { + switch N.NetworkName(network) { + case N.NetworkTCP: + return net.Network_TCP + case N.NetworkUDP: + return net.Network_UDP + default: + return net.Network_Unknown + } +} + +func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination { + if socksaddr.IsFqdn() { + return net.Destination{ + Network: network, + Address: net.DomainAddress(socksaddr.Fqdn), + Port: net.Port(socksaddr.Port), + } + } else { + return net.Destination{ + Network: network, + Address: net.IPAddress(socksaddr.Addr.AsSlice()), + Port: net.Port(socksaddr.Port), + } + } +} + +func ToSocksaddr(destination net.Destination) M.Socksaddr { + var addr M.Socksaddr + switch destination.Address.Family() { + case net.AddressFamilyDomain: + addr.Fqdn = destination.Address.Domain() + default: + addr.Addr = M.AddrFromIP(destination.Address.IP()) + } + addr.Port = uint16(destination.Port) + return addr +} diff --git a/common/singbridge/dialer.go b/common/singbridge/dialer.go new file mode 100644 index 00000000..b2e353f1 --- /dev/null +++ b/common/singbridge/dialer.go @@ -0,0 +1,29 @@ +package singbridge + +import ( + "context" + "os" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" +) + +var _ N.Dialer = (*XrayDialer)(nil) + +type XrayDialer struct { + internet.Dialer +} + +func NewDialer(dialer internet.Dialer) *XrayDialer { + return &XrayDialer{dialer} +} + +func (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network))) +} + +func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} diff --git a/common/singbridge/dialer_tls.go b/common/singbridge/dialer_tls.go new file mode 100644 index 00000000..29bd0491 --- /dev/null +++ b/common/singbridge/dialer_tls.go @@ -0,0 +1,41 @@ +package singbridge + +import ( + "context" + gotls "crypto/tls" + "os" + + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/tls" +) + +type XrayTLSDialer struct { + dialer internet.Dialer + clientFunc tls.CustomClientFunc +} + +func NewTLSDialer(dialer internet.Dialer, clientFunc tls.CustomClientFunc) *XrayTLSDialer { + return &XrayTLSDialer{dialer, clientFunc} +} + +func (d *XrayTLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + var tlsConfig *gotls.Config + conn, err := d.dialer.Dial(tls.ContextWithCustomClient(ctx, func(conn net.Conn, config *gotls.Config) net.Conn { + tlsConfig = config + return conn + }), ToDestination(destination, ToNetwork(network))) + if err != nil { + return nil, err + } + if tlsConfig == nil { + return nil, E.New("missing TLS config") + } + return d.clientFunc(conn, tlsConfig), nil +} + +func (d *XrayTLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} diff --git a/common/singbridge/error.go b/common/singbridge/error.go new file mode 100644 index 00000000..9bcb0c9f --- /dev/null +++ b/common/singbridge/error.go @@ -0,0 +1,10 @@ +package singbridge + +import E "github.com/sagernet/sing/common/exceptions" + +func ReturnError(err error) error { + if E.IsClosed(err) { + return nil + } + return err +} diff --git a/common/singbridge/logger.go b/common/singbridge/logger.go new file mode 100644 index 00000000..74b9efbf --- /dev/null +++ b/common/singbridge/logger.go @@ -0,0 +1,70 @@ +package singbridge + +import ( + "context" + + "github.com/sagernet/sing/common/logger" + "github.com/xtls/xray-core/common/errors" +) + +var _ logger.ContextLogger = (*XrayLogger)(nil) + +type XrayLogger struct { + newError func(values ...any) *errors.Error +} + +func NewLogger(newErrorFunc func(values ...any) *errors.Error) *XrayLogger { + return &XrayLogger{ + newErrorFunc, + } +} + +func (l *XrayLogger) Trace(args ...any) { +} + +func (l *XrayLogger) Debug(args ...any) { + l.newError(args...).AtDebug().WriteToLog() +} + +func (l *XrayLogger) Info(args ...any) { + l.newError(args...).AtInfo().WriteToLog() +} + +func (l *XrayLogger) Warn(args ...any) { + l.newError(args...).AtWarning().WriteToLog() +} + +func (l *XrayLogger) Error(args ...any) { + l.newError(args...).AtError().WriteToLog() +} + +func (l *XrayLogger) Fatal(args ...any) { +} + +func (l *XrayLogger) Panic(args ...any) { +} + +func (l *XrayLogger) TraceContext(ctx context.Context, args ...any) { +} + +func (l *XrayLogger) DebugContext(ctx context.Context, args ...any) { + l.newError(args...).AtDebug().WriteToLog() +} + +func (l *XrayLogger) InfoContext(ctx context.Context, args ...any) { + l.newError(args...).AtInfo().WriteToLog() +} + +func (l *XrayLogger) WarnContext(ctx context.Context, args ...any) { + l.newError(args...).AtWarning().WriteToLog() +} + +func (l *XrayLogger) ErrorContext(ctx context.Context, args ...any) { + l.newError(args...).AtError().WriteToLog() +} + +func (l *XrayLogger) FatalContext(ctx context.Context, args ...any) { +} + +func (l *XrayLogger) PanicContext(ctx context.Context, args ...any) { +} diff --git a/common/singbridge/pipe.go b/common/singbridge/pipe.go new file mode 100644 index 00000000..d04ebda4 --- /dev/null +++ b/common/singbridge/pipe.go @@ -0,0 +1,61 @@ +package singbridge + +import ( + "context" + "io" + "net" + + "github.com/sagernet/sing/common/bufio" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/transport" +) + +func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, serverConn net.Conn) error { + conn := &PipeConnWrapper{ + W: link.Writer, + Conn: inboundConn, + } + if ir, ok := link.Reader.(io.Reader); ok { + conn.R = ir + } else { + conn.R = &buf.BufferedReader{Reader: link.Reader} + } + return ReturnError(bufio.CopyConn(ctx, conn, serverConn)) +} + +type PipeConnWrapper struct { + R io.Reader + W buf.Writer + net.Conn +} + +func (w *PipeConnWrapper) Close() error { + return nil +} + +func (w *PipeConnWrapper) Read(b []byte) (n int, err error) { + return w.R.Read(b) +} + +func (w *PipeConnWrapper) Write(p []byte) (n int, err error) { + n = len(p) + var mb buf.MultiBuffer + pLen := len(p) + for pLen > 0 { + buffer := buf.New() + if pLen > buf.Size { + _, err = buffer.Write(p[:buf.Size]) + p = p[buf.Size:] + } else { + buffer.Write(p) + } + pLen -= int(buffer.Len()) + mb = append(mb, buffer) + } + err = w.W.WriteMultiBuffer(mb) + if err != nil { + n = 0 + buf.ReleaseMulti(mb) + } + return +} diff --git a/go.mod b/go.mod index d14f5d2d..89d07702 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/refraction-networking/utls v1.2.2 github.com/sagernet/sing v0.1.6 github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 + github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99 github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb github.com/stretchr/testify v1.8.1 diff --git a/go.sum b/go.sum index d32d88e7..88c47031 100644 --- a/go.sum +++ b/go.sum @@ -147,6 +147,14 @@ github.com/sagernet/sing v0.1.6 h1:Qy63OUfKpcqKjfd5rPmUlj0RGjHZSK/PJn0duyCCsRg= github.com/sagernet/sing v0.1.6/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w= github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 h1:Plup6oEiyLzY3HDqQ+QsUBzgBGdVmcsgf3t8h940z9U= github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7/go.mod h1:O5LtOs8Ivw686FqLpO0Zu+A0ROVE15VeqEK3yDRRAms= +github.com/sagernet/sing-shadowtls v0.0.0-20230221081357-574313aaae1d h1:yETevmRbJ6Mf9xgavSmgxo9UCdKNyplU4ubgFXqd4XU= +github.com/sagernet/sing-shadowtls v0.0.0-20230221081357-574313aaae1d/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc= +github.com/sagernet/sing-shadowtls v0.0.0-20230221093358-af0356df4755 h1:5/GdWkRlHv00+4JrIy0ilWAw2p/EWtuH0CClC2/gko4= +github.com/sagernet/sing-shadowtls v0.0.0-20230221093358-af0356df4755/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc= +github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99 h1:LhGnlaH8bV0MCNp/LW4PDPagtkJDSAX0ftN9bJ6HMxY= +github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc= +github.com/sagernet/sing-shadowtls v0.0.0-20230221110738-214729669cdc h1:T1XsW+0eNGlyy7NXY8AG2pE3d+RsdphRCH5ux0jjWf4= +github.com/sagernet/sing-shadowtls v0.0.0-20230221110738-214729669cdc/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= diff --git a/infra/conf/shadowtls.go b/infra/conf/shadowtls.go new file mode 100644 index 00000000..6746ad48 --- /dev/null +++ b/infra/conf/shadowtls.go @@ -0,0 +1,75 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + "github.com/sagernet/sing/common" + "github.com/xtls/xray-core/proxy/shadowtls" +) + +type ShadowTLSServerConfig struct { + Version uint16 `json:"version"` + Password string `json:"password,omitempty"` + Users []ShadowTLSUser `json:"users,omitempty"` + Handshake *ShadowTLSHandshakeConfig `json:"handshake"` + HandshakeForServerName map[string]*ShadowTLSHandshakeConfig `json:"handshakeForServerName,omitempty"` + StrictMode bool `json:"strictMode,omitempty"` + Detour string `json:"detour"` +} + +type ShadowTLSUser struct { + Email string `json:"email,omitempty"` + Password string `json:"password,omitempty"` +} + +func (c ShadowTLSUser) Build() *shadowtls.User { + return &shadowtls.User{ + Email: c.Email, + Password: c.Password, + } +} + +type ShadowTLSHandshakeConfig struct { + Address *Address `json:"address"` + Port uint16 `json:"port"` +} + +func (c ShadowTLSHandshakeConfig) Build() *shadowtls.HandshakeConfig { + return &shadowtls.HandshakeConfig{ + Address: c.Address.Build(), + Port: uint32(c.Port), + } +} + +func (c *ShadowTLSServerConfig) Build() (proto.Message, error) { + var handshakeForServerName map[string]*shadowtls.HandshakeConfig + if c.HandshakeForServerName != nil { + for serverName, serverConfig := range c.HandshakeForServerName { + handshakeForServerName[serverName] = serverConfig.Build() + } + } + return &shadowtls.ServerConfig{ + Version: uint32(c.Version), + Password: c.Password, + Users: common.Map(c.Users, ShadowTLSUser.Build), + Handshake: c.Handshake.Build(), + HandshakeForServerName: handshakeForServerName, + StrictMode: c.StrictMode, + Detour: c.Detour, + }, nil +} + +type ShadowTLSClientConfig struct { + Address *Address `json:"address"` + Port uint16 `json:"port"` + Version uint16 `json:"version"` + Password string `json:"password,omitempty"` +} + +func (c *ShadowTLSClientConfig) Build() (proto.Message, error) { + return &shadowtls.ClientConfig{ + Address: c.Address.Build(), + Port: uint32(c.Port), + Version: uint32(c.Version), + Password: c.Password, + }, nil +} diff --git a/infra/conf/xray.go b/infra/conf/xray.go index cda512da..f8d6448e 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -26,6 +26,7 @@ var ( "vmess": func() interface{} { return new(VMessInboundConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) }, "mtproto": func() interface{} { return new(MTProtoServerConfig) }, + "shadow-tls": func() interface{} { return new(ShadowTLSServerConfig) }, }, "protocol", "settings") outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ @@ -41,6 +42,7 @@ var ( "mtproto": func() interface{} { return new(MTProtoClientConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) }, "wireguard": func() interface{} { return new(WireGuardConfig) }, + "shadow-tls": func() interface{} { return new(ShadowTLSClientConfig) }, }, "protocol", "settings") ctllog = log.New(os.Stderr, "xctl> ", 0) diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 7fb73071..f26611b6 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -42,6 +42,7 @@ import ( _ "github.com/xtls/xray-core/proxy/loopback" _ "github.com/xtls/xray-core/proxy/mtproto" _ "github.com/xtls/xray-core/proxy/shadowsocks" + _ "github.com/xtls/xray-core/proxy/shadowtls" _ "github.com/xtls/xray-core/proxy/socks" _ "github.com/xtls/xray-core/proxy/trojan" _ "github.com/xtls/xray-core/proxy/vless/inbound" diff --git a/proxy/shadowsocks_2022/inbound.go b/proxy/shadowsocks_2022/inbound.go index 55bdda9f..a47b966c 100644 --- a/proxy/shadowsocks_2022/inbound.go +++ b/proxy/shadowsocks_2022/inbound.go @@ -17,6 +17,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" ) @@ -73,7 +74,7 @@ func (i *Inbound) Process(ctx context.Context, network net.Network, connection s ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -81,7 +82,7 @@ func (i *Inbound) Process(ctx context.Context, network net.Network, connection s mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -111,16 +112,11 @@ func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.M }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, nil, link, conn) } func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -137,7 +133,7 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err diff --git a/proxy/shadowsocks_2022/inbound_multi.go b/proxy/shadowsocks_2022/inbound_multi.go index 662a171c..11f6aa00 100644 --- a/proxy/shadowsocks_2022/inbound_multi.go +++ b/proxy/shadowsocks_2022/inbound_multi.go @@ -21,6 +21,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" @@ -162,7 +163,7 @@ func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, con ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -170,7 +171,7 @@ func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, con mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -202,16 +203,11 @@ func (i *MultiUserInbound) NewConnection(ctx context.Context, conn net.Conn, met }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, conn, link, conn) } func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -230,7 +226,7 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err diff --git a/proxy/shadowsocks_2022/inbound_relay.go b/proxy/shadowsocks_2022/inbound_relay.go index 3e0043ee..ceb8e8aa 100644 --- a/proxy/shadowsocks_2022/inbound_relay.go +++ b/proxy/shadowsocks_2022/inbound_relay.go @@ -19,6 +19,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" @@ -66,7 +67,7 @@ func NewRelayServer(ctx context.Context, config *RelayServerConfig) (*RelayInbou C.MapIndexed(config.Destinations, func(index int, it *RelayDestination) int { return index }), C.Map(config.Destinations, func(it *RelayDestination) string { return it.Key }), C.Map(config.Destinations, func(it *RelayDestination) M.Socksaddr { - return toSocksaddr(net.Destination{ + return singbridge.ToSocksaddr(net.Destination{ Address: it.Address.AsAddress(), Port: net.Port(it.Port), }) @@ -94,7 +95,7 @@ func (i *RelayInbound) Process(ctx context.Context, network net.Network, connect ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -102,7 +103,7 @@ func (i *RelayInbound) Process(ctx context.Context, network net.Network, connect mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -134,16 +135,11 @@ func (i *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadat }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, nil, link, conn) } func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -162,7 +158,7 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index cc23f737..7b032b46 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -2,7 +2,6 @@ package shadowsocks_2022 import ( "context" - "io" "runtime" "time" @@ -18,6 +17,7 @@ import ( "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" ) @@ -88,7 +88,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } if network == net.Network_TCP { - serverConn := o.method.DialEarlyConn(connection, toSocksaddr(destination)) + serverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination)) var handshake bool if timeoutReader, isTimeoutReader := link.Reader.(buf.TimeoutReader); isTimeoutReader { mb, err := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 100) @@ -123,17 +123,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int return newError("client handshake").Base(err) } } - conn := &pipeConnWrapper{ - W: link.Writer, - Conn: inboundConn, - } - if ir, ok := link.Reader.(io.Reader); ok { - conn.R = ir - } else { - conn.R = &buf.BufferedReader{Reader: link.Reader} - } - - return returnError(bufio.CopyConn(ctx, conn, serverConn)) + return singbridge.CopyConn(ctx, inboundConn, link, serverConn) } else { var packetConn N.PacketConn if pc, isPacketConn := inboundConn.(N.PacketConn); isPacketConn { @@ -151,10 +141,10 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int if o.uot { serverConn := o.method.DialEarlyConn(connection, M.Socksaddr{Fqdn: uot.UOTMagicAddress}) - return returnError(bufio.CopyPacketConn(ctx, packetConn, uot.NewClientConn(serverConn))) + return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, uot.NewClientConn(serverConn))) } else { serverConn := o.method.DialPacketConn(connection) - return returnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) + return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) } } } diff --git a/proxy/shadowsocks_2022/shadowsocks_2022.go b/proxy/shadowsocks_2022/shadowsocks_2022.go index 945c4499..6c41f596 100644 --- a/proxy/shadowsocks_2022/shadowsocks_2022.go +++ b/proxy/shadowsocks_2022/shadowsocks_2022.go @@ -1,82 +1,15 @@ package shadowsocks_2022 import ( - "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" + "github.com/xtls/xray-core/common/singbridge" ) //go:generate go run github.com/xtls/xray-core/common/errors/errorgen -func toDestination(socksaddr M.Socksaddr, network net.Network) net.Destination { - if socksaddr.IsFqdn() { - return net.Destination{ - Network: network, - Address: net.DomainAddress(socksaddr.Fqdn), - Port: net.Port(socksaddr.Port), - } - } else { - return net.Destination{ - Network: network, - Address: net.IPAddress(socksaddr.Addr.AsSlice()), - Port: net.Port(socksaddr.Port), - } - } -} - -func toSocksaddr(destination net.Destination) M.Socksaddr { - var addr M.Socksaddr - switch destination.Address.Family() { - case net.AddressFamilyDomain: - addr.Fqdn = destination.Address.Domain() - default: - addr.Addr = M.AddrFromIP(destination.Address.IP()) - } - addr.Port = uint16(destination.Port) - return addr -} - -type pipeConnWrapper struct { - R io.Reader - W buf.Writer - net.Conn -} - -func (w *pipeConnWrapper) Close() error { - return nil -} - -func (w *pipeConnWrapper) Read(b []byte) (n int, err error) { - return w.R.Read(b) -} - -func (w *pipeConnWrapper) Write(p []byte) (n int, err error) { - n = len(p) - var mb buf.MultiBuffer - pLen := len(p) - for pLen > 0 { - buffer := buf.New() - if pLen > buf.Size { - _, err = buffer.Write(p[:buf.Size]) - p = p[buf.Size:] - } else { - buffer.Write(p) - } - pLen -= int(buffer.Len()) - mb = append(mb, buffer) - } - err = w.W.WriteMultiBuffer(mb) - if err != nil { - n = 0 - buf.ReleaseMulti(mb) - } - return -} - type packetConnWrapper struct { buf.Reader buf.Writer @@ -100,7 +33,7 @@ func (w *packetConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) { destination = w.Dest } bb.Release() - return toSocksaddr(destination), nil + return singbridge.ToSocksaddr(destination), nil } } mb, err := w.ReadMultiBuffer() @@ -120,14 +53,14 @@ func (w *packetConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) { destination = w.Dest } bb.Release() - return toSocksaddr(destination), nil + return singbridge.ToSocksaddr(destination), nil } } func (w *packetConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error { vBuf := buf.New() vBuf.Write(buffer.Bytes()) - endpoint := toDestination(destination, net.Network_UDP) + endpoint := singbridge.ToDestination(destination, net.Network_UDP) vBuf.UDP = &endpoint return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf}) } @@ -136,10 +69,3 @@ func (w *packetConnWrapper) Close() error { buf.ReleaseMulti(w.cached) return nil } - -func returnError(err error) error { - if E.IsClosed(err) { - return nil - } - return err -} diff --git a/proxy/shadowtls/config.pb.go b/proxy/shadowtls/config.pb.go new file mode 100644 index 00000000..e4ff4ea5 --- /dev/null +++ b/proxy/shadowtls/config.pb.go @@ -0,0 +1,483 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: proxy/shadowtls/config.proto + +package shadowtls + +import ( + net "github.com/xtls/xray-core/common/net" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ServerConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Users []*User `protobuf:"bytes,3,rep,name=users,proto3" json:"users,omitempty"` + Handshake *HandshakeConfig `protobuf:"bytes,4,opt,name=handshake,proto3" json:"handshake,omitempty"` + HandshakeForServerName map[string]*HandshakeConfig `protobuf:"bytes,5,rep,name=handshake_for_server_name,json=handshakeForServerName,proto3" json:"handshake_for_server_name,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + StrictMode bool `protobuf:"varint,6,opt,name=strict_mode,json=strictMode,proto3" json:"strict_mode,omitempty"` + Detour string `protobuf:"bytes,7,opt,name=detour,proto3" json:"detour,omitempty"` +} + +func (x *ServerConfig) Reset() { + *x = ServerConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_shadowtls_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerConfig) ProtoMessage() {} + +func (x *ServerConfig) ProtoReflect() protoreflect.Message { + mi := &file_proxy_shadowtls_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. +func (*ServerConfig) Descriptor() ([]byte, []int) { + return file_proxy_shadowtls_config_proto_rawDescGZIP(), []int{0} +} + +func (x *ServerConfig) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *ServerConfig) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *ServerConfig) GetUsers() []*User { + if x != nil { + return x.Users + } + return nil +} + +func (x *ServerConfig) GetHandshake() *HandshakeConfig { + if x != nil { + return x.Handshake + } + return nil +} + +func (x *ServerConfig) GetHandshakeForServerName() map[string]*HandshakeConfig { + if x != nil { + return x.HandshakeForServerName + } + return nil +} + +func (x *ServerConfig) GetStrictMode() bool { + if x != nil { + return x.StrictMode + } + return false +} + +func (x *ServerConfig) GetDetour() string { + if x != nil { + return x.Detour + } + return "" +} + +type HandshakeConfig struct { + state protoimpl.MessageState + 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"` +} + +func (x *HandshakeConfig) Reset() { + *x = HandshakeConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_shadowtls_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HandshakeConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HandshakeConfig) ProtoMessage() {} + +func (x *HandshakeConfig) ProtoReflect() protoreflect.Message { + mi := &file_proxy_shadowtls_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HandshakeConfig.ProtoReflect.Descriptor instead. +func (*HandshakeConfig) Descriptor() ([]byte, []int) { + return file_proxy_shadowtls_config_proto_rawDescGZIP(), []int{1} +} + +func (x *HandshakeConfig) GetAddress() *net.IPOrDomain { + if x != nil { + return x.Address + } + return nil +} + +func (x *HandshakeConfig) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_shadowtls_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_proxy_shadowtls_config_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_proxy_shadowtls_config_proto_rawDescGZIP(), []int{2} +} + +func (x *User) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *User) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *User) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +type ClientConfig struct { + state protoimpl.MessageState + 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"` + Version uint32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *ClientConfig) Reset() { + *x = ClientConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_shadowtls_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientConfig) ProtoMessage() {} + +func (x *ClientConfig) ProtoReflect() protoreflect.Message { + mi := &file_proxy_shadowtls_config_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. +func (*ClientConfig) Descriptor() ([]byte, []int) { + return file_proxy_shadowtls_config_proto_rawDescGZIP(), []int{3} +} + +func (x *ClientConfig) GetAddress() *net.IPOrDomain { + if x != nil { + return x.Address + } + return nil +} + +func (x *ClientConfig) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *ClientConfig) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *ClientConfig) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +var File_proxy_shadowtls_config_proto protoreflect.FileDescriptor + +var file_proxy_shadowtls_config_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x74, 0x6c, + 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, + 0x77, 0x74, 0x6c, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, + 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe1, + 0x03, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, + 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x74, 0x6c, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x43, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, + 0x68, 0x61, 0x6b, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x74, 0x6c, + 0x73, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x79, 0x0a, 0x19, + 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, + 0x64, 0x6f, 0x77, 0x74, 0x6c, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x46, 0x6f, 0x72, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x16, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x46, 0x6f, 0x72, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x63, + 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x74, + 0x72, 0x69, 0x63, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x6f, + 0x75, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, + 0x1a, 0x70, 0x0a, 0x1b, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x46, 0x6f, 0x72, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, + 0x61, 0x64, 0x6f, 0x77, 0x74, 0x6c, 0x73, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x5c, 0x0a, 0x0f, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 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, 0x72, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x22, 0x4e, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x22, 0x8f, 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, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x74, 0x6c, 0x73, 0x50, 0x01, + 0x5a, 0x29, 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, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x14, 0x58, 0x72, + 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x54, + 0x4c, 0x53, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proxy_shadowtls_config_proto_rawDescOnce sync.Once + file_proxy_shadowtls_config_proto_rawDescData = file_proxy_shadowtls_config_proto_rawDesc +) + +func file_proxy_shadowtls_config_proto_rawDescGZIP() []byte { + file_proxy_shadowtls_config_proto_rawDescOnce.Do(func() { + file_proxy_shadowtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_shadowtls_config_proto_rawDescData) + }) + return file_proxy_shadowtls_config_proto_rawDescData +} + +var file_proxy_shadowtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_proxy_shadowtls_config_proto_goTypes = []interface{}{ + (*ServerConfig)(nil), // 0: xray.proxy.shadowtls.ServerConfig + (*HandshakeConfig)(nil), // 1: xray.proxy.shadowtls.HandshakeConfig + (*User)(nil), // 2: xray.proxy.shadowtls.User + (*ClientConfig)(nil), // 3: xray.proxy.shadowtls.ClientConfig + nil, // 4: xray.proxy.shadowtls.ServerConfig.HandshakeForServerNameEntry + (*net.IPOrDomain)(nil), // 5: xray.common.net.IPOrDomain +} +var file_proxy_shadowtls_config_proto_depIdxs = []int32{ + 2, // 0: xray.proxy.shadowtls.ServerConfig.users:type_name -> xray.proxy.shadowtls.User + 1, // 1: xray.proxy.shadowtls.ServerConfig.handshake:type_name -> xray.proxy.shadowtls.HandshakeConfig + 4, // 2: xray.proxy.shadowtls.ServerConfig.handshake_for_server_name:type_name -> xray.proxy.shadowtls.ServerConfig.HandshakeForServerNameEntry + 5, // 3: xray.proxy.shadowtls.HandshakeConfig.address:type_name -> xray.common.net.IPOrDomain + 5, // 4: xray.proxy.shadowtls.ClientConfig.address:type_name -> xray.common.net.IPOrDomain + 1, // 5: xray.proxy.shadowtls.ServerConfig.HandshakeForServerNameEntry.value:type_name -> xray.proxy.shadowtls.HandshakeConfig + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_proxy_shadowtls_config_proto_init() } +func file_proxy_shadowtls_config_proto_init() { + if File_proxy_shadowtls_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proxy_shadowtls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ServerConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_shadowtls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HandshakeConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_shadowtls_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_shadowtls_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClientConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proxy_shadowtls_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proxy_shadowtls_config_proto_goTypes, + DependencyIndexes: file_proxy_shadowtls_config_proto_depIdxs, + MessageInfos: file_proxy_shadowtls_config_proto_msgTypes, + }.Build() + File_proxy_shadowtls_config_proto = out.File + file_proxy_shadowtls_config_proto_rawDesc = nil + file_proxy_shadowtls_config_proto_goTypes = nil + file_proxy_shadowtls_config_proto_depIdxs = nil +} diff --git a/proxy/shadowtls/config.proto b/proxy/shadowtls/config.proto new file mode 100644 index 00000000..bfbed0cf --- /dev/null +++ b/proxy/shadowtls/config.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package xray.proxy.shadowtls; +option csharp_namespace = "Xray.Proxy.ShadowTLS"; +option go_package = "github.com/xtls/xray-core/proxy/shadowtls"; +option java_package = "com.xray.proxy.shadowtls"; +option java_multiple_files = true; + +import "common/net/address.proto"; + +message ServerConfig { + uint32 version = 1; + string password = 2; + repeated User users = 3; + HandshakeConfig handshake = 4; + map handshake_for_server_name = 5; + bool strict_mode = 6; + string detour = 7; +} + +message HandshakeConfig { + xray.common.net.IPOrDomain address = 1; + uint32 port = 2; +} + +message User { + string email = 1; + string password = 2; + int32 level = 3; +} + +message ClientConfig { + xray.common.net.IPOrDomain address = 1; + uint32 port = 2; + uint32 version = 3; + string password = 4; +} diff --git a/proxy/shadowtls/errors.generated.go b/proxy/shadowtls/errors.generated.go new file mode 100644 index 00000000..ee2710d5 --- /dev/null +++ b/proxy/shadowtls/errors.generated.go @@ -0,0 +1,9 @@ +package shadowtls + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/proxy/shadowtls/inbound.go b/proxy/shadowtls/inbound.go new file mode 100644 index 00000000..fae3f6a0 --- /dev/null +++ b/proxy/shadowtls/inbound.go @@ -0,0 +1,136 @@ +package shadowtls + +import ( + "context" + "os" + + "github.com/sagernet/sing-shadowtls" + sing_common "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + 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" + "github.com/xtls/xray-core/common/log" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/inbound" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/proxy" + "github.com/xtls/xray-core/transport/internet/stat" +) + +func init() { + common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewServer(ctx, config.(*ServerConfig)) + })) +} + +type Inbound struct { + service *shadowtls.Service + inboundManager inbound.Manager + detour string +} + +func NewServer(ctx context.Context, config *ServerConfig) (*Inbound, error) { + v := core.MustFromContext(ctx) + inbound := &Inbound{ + inboundManager: v.GetFeature(inbound.ManagerType()).(inbound.Manager), + detour: config.Detour, + } + var handshakeForServerName map[string]shadowtls.HandshakeConfig + if config.Version > 1 { + handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) + for serverName, serverConfig := range config.HandshakeForServerName { + handshakeForServerName[serverName] = shadowtls.HandshakeConfig{ + Server: singbridge.ToSocksaddr(net.Destination{ + Address: serverConfig.Address.AsAddress(), + Port: net.Port(serverConfig.Port), + }), + Dialer: N.SystemDialer, + } + } + } + service, err := shadowtls.NewService(shadowtls.ServiceConfig{ + Version: int(config.Version), + Password: config.Password, + Users: sing_common.Map(config.Users, func(it *User) shadowtls.User { + return shadowtls.User{ + Name: it.Email, + Password: it.Password, + } + }), + Handshake: shadowtls.HandshakeConfig{ + Server: singbridge.ToSocksaddr(net.Destination{ + Address: config.Handshake.Address.AsAddress(), + Port: net.Port(config.Handshake.Port), + }), + Dialer: N.SystemDialer, + }, + HandshakeForServerName: handshakeForServerName, + StrictMode: config.StrictMode, + Handler: inbound, + Logger: singbridge.NewLogger(newError), + }) + if err != nil { + return nil, E.Cause(err, "create service") + } + inbound.service = service + return inbound, nil +} + +func (i *Inbound) Network() []net.Network { + return []net.Network{net.Network_TCP} +} + +func (i *Inbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { + inbound := session.InboundFromContext(ctx) + var metadata M.Metadata + if inbound.Source.IsValid() { + metadata.Source = M.ParseSocksaddr(inbound.Source.NetAddr()) + } + ctx = session.ContextWithDispatcher(ctx, dispatcher) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) +} + +func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + inboundHandler, err := i.inboundManager.GetHandler(ctx, i.detour) + if err != nil { + return E.Cause(err, "detour not found") + } + + inboundWrapper, loaded := inboundHandler.(proxy.GetInbound) + if !loaded { + return newError("can't get inbound proxy from handler.") + } + + inboundDetour := inboundWrapper.GetInbound() + + email, _ := auth.UserFromContext[string](ctx) + inbound := session.InboundFromContext(ctx) + inbound.User = &protocol.MemoryUser{ + Email: email, + } + ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ + From: metadata.Source, + To: metadata.Destination, + Status: log.AccessAccepted, + Email: email, + }) + newError("tunnelling request to detour").WriteToLog(session.ExportIDToError(ctx)) + return inboundDetour.Process(ctx, net.Network_TCP, conn, session.DispatcherFromContext(ctx)) +} + +func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + return os.ErrInvalid +} + +func (i *Inbound) NewError(ctx context.Context, err error) { + if E.IsClosed(err) { + return + } + newError(err).AtWarning().WriteToLog() +} diff --git a/proxy/shadowtls/outbound.go b/proxy/shadowtls/outbound.go new file mode 100644 index 00000000..031536f0 --- /dev/null +++ b/proxy/shadowtls/outbound.go @@ -0,0 +1,83 @@ +package shadowtls + +import ( + "context" + "crypto/tls" + + "github.com/sagernet/sing-shadowtls" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" + "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" +) + +func init() { + common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewClient(ctx, config.(*ClientConfig)) + })) +} + +type Outbound struct { + ctx context.Context + clientConfig shadowtls.ClientConfig +} + +func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { + return &Outbound{ + ctx: ctx, + clientConfig: shadowtls.ClientConfig{ + Version: int(config.Version), + Password: config.Password, + Server: singbridge.ToSocksaddr(net.Destination{ + Address: config.Address.AsAddress(), + Port: net.Port(config.Port), + }), + Logger: singbridge.NewLogger(newError), + }, + }, nil +} + +func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + var inboundConn net.Conn + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inboundConn = inbound.Conn + } + + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified") + } + destination := outbound.Target + + if destination.Network != net.Network_TCP { + return newError("only TCP is supported") + } + + newError("tunneling request to ", destination, " via ", o.clientConfig.Server).WriteToLog(session.ExportIDToError(ctx)) + + var client *shadowtls.Client + clientConfig := o.clientConfig + if clientConfig.Version == 3 { + clientConfig.Dialer = singbridge.NewTLSDialer(dialer, func(conn net.Conn, config *tls.Config) net.Conn { + client.SetTLSConfig(config) + return conn + }) + } else { + clientConfig.Dialer = singbridge.NewDialer(dialer) + } + var err error + client, err = shadowtls.NewClient(clientConfig) + if err != nil { + return newError("failed to create client").Base(err) + } + + conn, err := client.DialContext(ctx) + if err != nil { + return newError("failed to connect to server").Base(err) + } + + return singbridge.CopyConn(ctx, inboundConn, link, conn) +} diff --git a/proxy/shadowtls/shadowtls.go b/proxy/shadowtls/shadowtls.go new file mode 100644 index 00000000..7a6ed3fc --- /dev/null +++ b/proxy/shadowtls/shadowtls.go @@ -0,0 +1,3 @@ +package shadowtls + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/testing/scenarios/shadowtls_test.go b/testing/scenarios/shadowtls_test.go new file mode 100644 index 00000000..3ff544d7 --- /dev/null +++ b/testing/scenarios/shadowtls_test.go @@ -0,0 +1,156 @@ +package scenarios + +import ( + "crypto/rand" + "encoding/base64" + "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" + clog "github.com/xtls/xray-core/common/log" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/serial" + "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/proxy/shadowtls" + "github.com/xtls/xray-core/testing/servers/tcp" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/tls" + "golang.org/x/sync/errgroup" +) + +func TestShadowTLSTcp(t *testing.T) { + password := make([]byte, 32) + rand.Read(password) + testShadowTLSTcp(t, shadowaead_2022.List[0], base64.StdEncoding.EncodeToString(password)) +} + +func testShadowTLSTcp(t *testing.T, method string, password string) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + serverPort := tcp.PickPort() + serverPort = 18462 + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&shadowtls.ServerConfig{ + Version: 3, + Users: []*shadowtls.User{{Password: password}}, + Handshake: &shadowtls.HandshakeConfig{ + Address: net.NewIPOrDomain(net.DomainAddress("google.com")), + Port: 443, + }, + Detour: "detour", + }), + }, + { + Tag: "detour", + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort + 1)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{ + Method: method, + Key: password, + Network: []net.Network{net.Network_TCP}, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientPort = 12434 + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + Networks: []net.Network{net.Network_TCP}, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ClientConfig{ + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + Method: method, + Key: password, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + ProxySettings: &internet.ProxyConfig{ + Tag: "detour", + }, + }), + }, + { + Tag: "detour", + ProxySettings: serial.ToTypedMessage(&shadowtls.ClientConfig{ + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + Version: 3, + Password: password, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + ServerName: "google.com", + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errGroup errgroup.Group + for i := 0; i < 10; i++ { + errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) + } + + if err := errGroup.Wait(); err != nil { + t.Error(err) + } +} diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index c806246f..0a838506 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -23,13 +23,18 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { - conn = tls.UClient(conn, tlsConfig, fingerprint) - if err := conn.(*tls.UConn).Handshake(); err != nil { - return nil, err - } + customClient, loaded := tls.CustomClientFromContext(ctx) + if loaded { + conn = customClient(conn, tlsConfig) } else { - conn = tls.Client(conn, tlsConfig) + if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { + conn = tls.UClient(conn, tlsConfig, fingerprint) + if err := conn.(*tls.UConn).Handshake(); err != nil { + return nil, err + } + } else { + conn = tls.Client(conn, tlsConfig) + } } } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { xtlsConfig := config.GetXTLSConfig(xtls.WithDestination(dest)) diff --git a/transport/internet/tls/custom.go b/transport/internet/tls/custom.go new file mode 100644 index 00000000..f6468291 --- /dev/null +++ b/transport/internet/tls/custom.go @@ -0,0 +1,21 @@ +package tls + +import ( + "context" + "crypto/tls" + + "github.com/xtls/xray-core/common/net" +) + +type customClientKey struct{} + +type CustomClientFunc func(conn net.Conn, config *tls.Config) net.Conn + +func CustomClientFromContext(ctx context.Context) (CustomClientFunc, bool) { + client, loaded := ctx.Value(customClientKey{}).(CustomClientFunc) + return client, loaded +} + +func ContextWithCustomClient(ctx context.Context, customClient CustomClientFunc) context.Context { + return context.WithValue(ctx, customClientKey{}, customClient) +}