Add outbound sniff option

This commit is contained in:
129tyc 2023-04-11 22:23:14 +08:00
parent ea18d75a28
commit 1abe1a8ca8
7 changed files with 58 additions and 10 deletions

View File

@ -19,6 +19,11 @@ type Outbound interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }
type SniffOutbound interface {
Outbound
UseSniffedDestination() bool
}
type IPOutbound interface { type IPOutbound interface {
Outbound Outbound
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)

View File

@ -14,6 +14,7 @@
"password": "admin", "password": "admin",
"network": "udp", "network": "udp",
"udp_over_tcp": false | {}, "udp_over_tcp": false | {},
"use_sniffed_destination": false
... // Dial Fields ... // Dial Fields
} }
@ -61,6 +62,10 @@ UDP over TCP protocol settings.
See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details. See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details.
#### use_sniffed_destination
When an inbound request is routed to the outbound, the detected domain name will be used to override the connection target address before establishing a connection. This option is only effective when `sniff` is set to `true` and `sniff_override_destination` is `false` in the inbound.
### Dial Fields ### Dial Fields
See [Dial Fields](/configuration/shared/dial) for details. See [Dial Fields](/configuration/shared/dial) for details.

View File

@ -14,6 +14,7 @@
"password": "admin", "password": "admin",
"network": "udp", "network": "udp",
"udp_over_tcp": false | {}, "udp_over_tcp": false | {},
"use_sniffed_destination": false
... // 拨号字段 ... // 拨号字段
} }
@ -61,6 +62,10 @@ UDP over TCP 配置。
参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。 参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。
#### use_sniffed_destination
当入站请求路由到该出站时,在建立连接前会用探测出的域名覆盖连接目标地址,仅当入站设置了`sniff``true`同时`sniff_override_destination``false`时该选项有效。
### 拨号字段 ### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。 参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@ -144,6 +144,10 @@ func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
} }
type SniffOptions struct {
UseSniffedDestination bool `json:"use_sniffed_destination,omitempty"`
}
type MultiplexOptions struct { type MultiplexOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Protocol string `json:"protocol,omitempty"` Protocol string `json:"protocol,omitempty"`

View File

@ -17,6 +17,7 @@ type HTTPMixedInboundOptions struct {
type SocksOutboundOptions struct { type SocksOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
SniffOptions
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`

View File

@ -17,13 +17,18 @@ import (
"github.com/sagernet/sing/protocol/socks" "github.com/sagernet/sing/protocol/socks"
) )
var _ adapter.Outbound = (*Socks)(nil) var _ adapter.SniffOutbound = (*Socks)(nil)
type Socks struct { type Socks struct {
myOutboundAdapter myOutboundAdapter
client *socks.Client client *socks.Client
resolve bool resolve bool
uotClient *uot.Client uotClient *uot.Client
useSniffedDestination bool
}
func (h *Socks) UseSniffedDestination() bool {
return h.useSniffedDestination
} }
func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) {
@ -45,8 +50,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
logger: logger, logger: logger,
tag: tag, tag: tag,
}, },
client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password), client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password),
resolve: version == socks.Version4, resolve: version == socks.Version4,
useSniffedDestination: options.UseSniffedDestination,
} }
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions) uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
if uotOptions.Enabled { if uotOptions.Enabled {

View File

@ -657,6 +657,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
r.logger.DebugContext(ctx, "found fakeip domain: ", domain) r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
} }
var sniffedDestination M.Socksaddr
if metadata.InboundOptions.SniffEnabled { if metadata.InboundOptions.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()
buffer.FullReset() buffer.FullReset()
@ -664,11 +665,14 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
if sniffMetadata != nil { if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { if M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{ sniffedDestination = M.Socksaddr{
Fqdn: metadata.Domain, Fqdn: metadata.Domain,
Port: metadata.Destination.Port, Port: metadata.Destination.Port,
} }
if metadata.InboundOptions.SniffOverrideDestination {
metadata.Destination = sniffedDestination
}
} }
if metadata.Domain != "" { if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
@ -706,6 +710,13 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
if !common.Contains(detour.Network(), N.NetworkTCP) { if !common.Contains(detour.Network(), N.NetworkTCP) {
return E.New("missing supported outbound, closing connection") return E.New("missing supported outbound, closing connection")
} }
if metadata.InboundOptions.SniffEnabled && !metadata.InboundOptions.SniffOverrideDestination && sniffedDestination.IsValid() {
sniffOutbound, loaded := detour.(adapter.SniffOutbound)
if loaded && sniffOutbound.UseSniffedDestination() {
r.logger.DebugContext(ctx, "use sniffed destination, detor: ", detour.Tag(), ", domain: ", metadata.Domain)
metadata.Destination = sniffedDestination
}
}
if r.clashServer != nil { if r.clashServer != nil {
trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule) trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule)
defer tracker.Leave() defer tracker.Leave()
@ -760,6 +771,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
r.logger.DebugContext(ctx, "found fakeip domain: ", domain) r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
} }
var sniffedDestination M.Socksaddr
if metadata.InboundOptions.SniffEnabled { if metadata.InboundOptions.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()
buffer.FullReset() buffer.FullReset()
@ -772,11 +784,14 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
if sniffMetadata != nil { if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { if M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{ sniffedDestination = M.Socksaddr{
Fqdn: metadata.Domain, Fqdn: metadata.Domain,
Port: metadata.Destination.Port, Port: metadata.Destination.Port,
} }
if metadata.InboundOptions.SniffOverrideDestination {
metadata.Destination = sniffedDestination
}
} }
if metadata.Domain != "" { if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain) r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
@ -808,6 +823,13 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
if !common.Contains(detour.Network(), N.NetworkUDP) { if !common.Contains(detour.Network(), N.NetworkUDP) {
return E.New("missing supported outbound, closing packet connection") return E.New("missing supported outbound, closing packet connection")
} }
if metadata.InboundOptions.SniffEnabled && !metadata.InboundOptions.SniffOverrideDestination && sniffedDestination.IsValid() {
sniffOutbound, loaded := detour.(adapter.SniffOutbound)
if loaded && sniffOutbound.UseSniffedDestination() {
r.logger.DebugContext(ctx, "use sniffed destination, detor: ", detour.Tag(), ", domain: ", metadata.Domain)
metadata.Destination = sniffedDestination
}
}
if r.clashServer != nil { if r.clashServer != nil {
trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule) trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule)
defer tracker.Leave() defer tracker.Leave()