From eab34d9145fa08560ef5d2e48a0886d5f21bdb40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Nov 2024 18:31:37 +0800 Subject: [PATCH] Add override destination to route options --- adapter/inbound.go | 7 +-- adapter/outbound/default.go | 24 ++++----- common/dialer/default_parallel_network.go | 44 +++++++++++----- docs/configuration/dns/index.md | 9 +--- docs/configuration/dns/index.zh.md | 10 ++-- docs/configuration/outbound/direct.md | 22 ++++++-- docs/configuration/outbound/direct.zh.md | 24 ++++++--- docs/configuration/route/rule_action.md | 50 +++++++++++-------- docs/configuration/route/rule_action.zh.md | 58 +++++++++++++--------- docs/deprecated.md | 6 +++ docs/deprecated.zh.md | 7 +++ docs/migration.md | 38 ++++++++++++++ docs/migration.zh.md | 42 +++++++++++++++- experimental/deprecated/constants.go | 10 ++++ option/direct.go | 29 +++++++++-- option/rule_action.go | 25 +++++----- route/route.go | 37 +++++++++++--- route/rule/rule_action.go | 7 +++ 18 files changed, 326 insertions(+), 123 deletions(-) diff --git a/adapter/inbound.go b/adapter/inbound.go index 1093bac4..d3cc95b7 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -61,9 +61,10 @@ type InboundContext struct { // cache // Deprecated: implement in rule action - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + RouteOriginalDestination M.Socksaddr // Deprecated InboundOptions option.InboundOptions UDPDisableDomainUnmapping bool diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 27cafb9d..573673f2 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -25,11 +25,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var outConn net.Conn var err error if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) - } + outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } @@ -73,11 +69,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } } else { if len(metadata.DestinationAddresses) > 0 { - if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) - } else { - outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) - } + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) } @@ -91,11 +83,17 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, return err } if destinationAddress.IsValid() { - if metadata.Destination.IsFqdn() { + var originDestination M.Socksaddr + if metadata.RouteOriginalDestination.IsValid() { + originDestination = metadata.RouteOriginalDestination + } else { + originDestination = metadata.Destination + } + if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) { if metadata.UDPDisableDomainUnmapping { - outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) } else { - outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) + outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) } } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go index ea043dfd..5145656b 100644 --- a/common/dialer/default_parallel_network.go +++ b/common/dialer/default_parallel_network.go @@ -13,17 +13,27 @@ import ( N "github.com/sagernet/sing/common/network" ) -func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { +func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error - for _, address := range destinationAddresses { - conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) - if err == nil { - return conn, nil + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, nil + } + errors = append(errors, err) } - errors = append(errors, err) } return nil, E.Errors(errors...) } @@ -106,17 +116,27 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne } } -func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error - for _, address := range destinationAddresses { - conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) - if err == nil { - return conn, address, nil + if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel { + for _, address := range destinationAddresses { + conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) + } + } else { + for _, address := range destinationAddresses { + conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port)) + if err == nil { + return conn, address, nil + } + errors = append(errors, err) } - errors = append(errors, err) } return nil, netip.Addr{}, E.Errors(errors...) } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index fc813334..0756281d 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,16 +1,11 @@ --- -icon: material/new +icon: material/new-box --- - !!! quote "Changes in sing-box 1.11.0" :material-plus: [cache_capacity](#cache_capacity) -!!! quote "Changes in sing-box 1.9.0" - - :material-plus: [client_subnet](#client_subnet) - # DNS ### Structure @@ -70,7 +65,7 @@ Make each DNS server's cache independent for special purposes. If enabled, will #### cache_capacity -!!! quote "Since sing-box 1.11.0" +!!! question "Since sing-box 1.11.0" LRU cache capacity. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index 20fbc00d..76c07b6a 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,15 +1,11 @@ --- -icon: material/new +icon: material/new-box --- -!!! quote "自 sing-box 1.11.0 起" +!!! quote "sing-box 1.11.0 中的更改" :material-plus: [cache_capacity](#cache_capacity) -!!! quote "自 sing-box 1.9.0 起" - - :material-plus: [client_subnet](#client_subnet) - # DNS ### 结构 @@ -68,7 +64,7 @@ icon: material/new #### cache_capacity -!!! quote "自 sing-box 1.11.0 起" +!!! question "自 sing-box 1.11.0 起" LRU 缓存容量。 diff --git a/docs/configuration/outbound/direct.md b/docs/configuration/outbound/direct.md index c2f5671a..71649243 100644 --- a/docs/configuration/outbound/direct.md +++ b/docs/configuration/outbound/direct.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "Changes in sing-box 1.11.0" + + :material-alert-decagram: [override_address](#override_address) + :material-alert-decagram: [override_port](#override_port) + `direct` outbound send requests directly. ### Structure @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // Dial Fields } @@ -19,16 +27,20 @@ #### override_address +!!! failure "Deprecated in sing-box 1.11.0" + + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). + Override the connection destination address. #### override_port +!!! failure "Deprecated in sing-box 1.11.0" + + Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options). + Override the connection destination port. -#### proxy_protocol - -Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header. - Protocol value can be `1` or `2`. ### Dial Fields diff --git a/docs/configuration/outbound/direct.zh.md b/docs/configuration/outbound/direct.zh.md index aee13175..55d3bf8c 100644 --- a/docs/configuration/outbound/direct.zh.md +++ b/docs/configuration/outbound/direct.zh.md @@ -1,3 +1,12 @@ +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.11.0 中的更改" + + :material-alert-decagram: [override_address](#override_address) + :material-alert-decagram: [override_port](#override_port) + `direct` 出站直接发送请求。 ### 结构 @@ -9,7 +18,6 @@ "override_address": "1.0.0.1", "override_port": 53, - "proxy_protocol": 0, ... // 拨号字段 } @@ -19,18 +27,20 @@ #### override_address +!!! failure "已在 sing-box 1.11.0 废弃" + + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 + 覆盖连接目标地址。 #### override_port +!!! failure "已在 sing-box 1.11.0 废弃" + + 目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 + 覆盖连接目标端口。 -#### proxy_protocol - -写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。 - -可用协议版本值:`1` 或 `2`。 - ### 拨号字段 参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 6462c848..63e2b00b 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -10,12 +10,8 @@ icon: material/new-box { "action": "route", // default "outbound": "", - "network_strategy": "", - "network_type": [], - "fallback_network_type": [], - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false + + ... // route-options Fields } ``` @@ -31,6 +27,34 @@ icon: material/new-box Tag of target outbound. +#### route-options Fields + +See `route-options` fields below. + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +`route-options` set options for routing. + +#### override_address + +Override the connection destination address. + +#### override_port + +Override the connection destination port. + #### network_strategy See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. @@ -62,20 +86,6 @@ do not support receiving UDP packets with domain addresses, such as Surge. If enabled, attempts to connect UDP connection to the destination instead of listen. -### route-options - -```json -{ - "action": "route-options", - "network_strategy": "", - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -`route-options` set options for routing. - ### reject ```json diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index acadb66d..7959fced 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -10,12 +10,8 @@ icon: material/new-box { "action": "route", // 默认 "outbound": "", - "network_strategy": "", - "fallback_delay": "", - "network_type": [], - "fallback_network_type": [], - "udp_disable_domain_unmapping": false, - "udp_connect": false + + ... // route-options 字段 } ``` @@ -27,6 +23,38 @@ icon: material/new-box 目标出站的标签。 +#### route-options 字段 + +参阅下方的 `route-options` 字段。 + +### route-options + +```json +{ + "action": "route-options", + "override_address": "", + "override_port": 0, + "network_strategy": "", + "fallback_delay": "", + "udp_disable_domain_unmapping": false, + "udp_connect": false +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +`route-options` 为路由设置选项。 + +#### override_address + +覆盖目标地址。 + +#### override_port + +覆盖目标端口。 + #### network_strategy 详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 @@ -56,24 +84,6 @@ icon: material/new-box 如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 -### route-options - -```json -{ - "action": "route-options", - "network_strategy": "", - "fallback_delay": "", - "udp_disable_domain_unmapping": false, - "udp_connect": false -} -``` - -!!! note "" - - 当内容只有一项时,可以忽略 JSON 数组 [] 标签 - -`route-options` 为路由设置选项。 - ### reject ```json diff --git a/docs/deprecated.md b/docs/deprecated.md index d57c93ad..5dcec562 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -22,6 +22,12 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions). Old fields will be removed in sing-box 1.13.0. +#### Destination override fields in direct outbound + +Destination override fields (`override_address` / `override_port`) in direct outbound are deprecated +and can be replaced by rule actions, +check [Migration](../migration/#migrate-destination-override-fields-to-route-options). + ## 1.10.0 #### TUN address fields are merged diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index 76a426d2..6f6c839f 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -20,6 +20,13 @@ icon: material/delete-alert 旧字段将在 sing-box 1.13.0 中被移除。 +#### direct 出站中的目标地址覆盖字段 + +direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代, +参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。 + +旧字段将在 sing-box 1.13.0 中被移除。 + ## 1.10.0 #### Match source 规则项已重命名 diff --git a/docs/migration.md b/docs/migration.md index 8f01c4f0..ea1afa2d 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -156,6 +156,44 @@ Inbound fields are deprecated and can be replaced by rule actions. } ``` +### Migrate destination override fields to route options + +Destination override fields in direct outbound are deprecated and can be replaced by route options. + +!!! info "References" + + [Rule Action](/configuration/route/rule_action/) / + [Direct](/configuration/outbound/direct/) + +=== ":material-card-remove: Deprecated" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: New" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // or route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + ## 1.10.0 ### TUN address fields are merged diff --git a/docs/migration.zh.md b/docs/migration.zh.md index dc62f370..73afbb05 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -104,6 +104,7 @@ icon: material/arrange-bring-forward ### 迁移旧的入站字段到规则动作 + 入站选项已被弃用,且可以被规则动作替代。 !!! info "参考" @@ -156,6 +157,45 @@ icon: material/arrange-bring-forward } ``` +### 迁移 direct 出站中的目标地址覆盖字段到路由字段 + +direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段替代。 + +!!! info "参考" + + [Rule Action](/zh/configuration/route/rule_action/) / + [Direct](/zh/configuration/outbound/direct/) + +=== ":material-card-remove: 弃用的" + + ```json + { + "outbounds": [ + { + "type": "direct", + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + ``` + +=== ":material-card-multiple: 新的" + + ```json + { + "route": { + "rules": [ + { + "action": "route-options", // 或 route + "override_address": "1.1.1.1", + "override_port": 443 + } + ] + } + } + ``` + ## 1.10.0 ### TUN 地址字段已合并 @@ -164,8 +204,6 @@ icon: material/arrange-bring-forward `inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`, `inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。 -旧字段已废弃,且将在 sing-box 1.11.0 中移除。 - !!! info "参考" [TUN](/zh/configuration/inbound/tun/) diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index bf848ffe..a62b89f8 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -100,6 +100,15 @@ var OptionInboundOptions = Note{ MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", } +var OptionDestinationOverrideFields = Note{ + Name: "destination-override-fields", + Description: "destination override fields in direct outbound", + DeprecatedVersion: "1.11.0", + ScheduledVersion: "1.13.0", + EnvName: "DESTINATION_OVERRIDE_FIELDS", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-destination-override-fields-to-route-options", +} + var Options = []Note{ OptionBadMatchSource, OptionGEOIP, @@ -107,4 +116,5 @@ var Options = []Note{ OptionTUNAddressX, OptionSpecialOutbounds, OptionInboundOptions, + OptionDestinationOverrideFields, } diff --git a/option/direct.go b/option/direct.go index 8624846d..180ff0aa 100644 --- a/option/direct.go +++ b/option/direct.go @@ -1,5 +1,12 @@ package option +import ( + "context" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common/json" +) + type DirectInboundOptions struct { ListenOptions Network NetworkList `json:"network,omitempty"` @@ -7,9 +14,25 @@ type DirectInboundOptions struct { OverridePort uint16 `json:"override_port,omitempty"` } -type DirectOutboundOptions struct { +type _DirectOutboundOptions struct { DialerOptions + // Deprecated: Use Route Action instead OverrideAddress string `json:"override_address,omitempty"` - OverridePort uint16 `json:"override_port,omitempty"` - ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` + // Deprecated: Use Route Action instead + OverridePort uint16 `json:"override_port,omitempty"` + // Deprecated: removed + ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` +} + +type DirectOutboundOptions _DirectOutboundOptions + +func (d *DirectOutboundOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalDisallowUnknownFields(content, (*_DirectOutboundOptions)(d)) + if err != nil { + return err + } + if d.OverrideAddress != "" || d.OverridePort != 0 { + deprecated.Report(ctx, deprecated.OptionDestinationOverrideFields) + } + return nil } diff --git a/option/rule_action.go b/option/rule_action.go index 7c31ea7a..ce3b92d9 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -137,24 +137,25 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e } type RouteActionOptions struct { - Outbound string `json:"outbound,omitempty"` - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay uint32 `json:"fallback_delay,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` + Outbound string `json:"outbound,omitempty"` + RawRouteOptionsActionOptions } -type _RouteOptionsActionOptions struct { - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay uint32 `json:"fallback_delay,omitempty"` - UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - UDPConnect bool `json:"udp_connect,omitempty"` +type RawRouteOptionsActionOptions struct { + OverrideAddress string `json:"override_address,omitempty"` + OverridePort uint16 `json:"override_port,omitempty"` + + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + FallbackDelay uint32 `json:"fallback_delay,omitempty"` + + UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + UDPConnect bool `json:"udp_connect,omitempty"` } -type RouteOptionsActionOptions _RouteOptionsActionOptions +type RouteOptionsActionOptions RawRouteOptionsActionOptions func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, (*_RouteOptionsActionOptions)(r)) + err := json.Unmarshal(data, (*RawRouteOptionsActionOptions)(r)) if err != nil { return err } diff --git a/route/route.go b/route/route.go index 051ee403..7d21d315 100644 --- a/route/route.go +++ b/route/route.go @@ -422,17 +422,38 @@ match: } } } + var routeOptions *rule.RuleActionRouteOptions switch action := currentRule.Action().(type) { case *rule.RuleActionRoute: - metadata.NetworkStrategy = action.NetworkStrategy - metadata.FallbackDelay = action.FallbackDelay - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - metadata.UDPConnect = action.UDPConnect + routeOptions = &action.RuleActionRouteOptions case *rule.RuleActionRouteOptions: - metadata.NetworkStrategy = action.NetworkStrategy - metadata.FallbackDelay = action.FallbackDelay - metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping - metadata.UDPConnect = action.UDPConnect + routeOptions = action + } + if routeOptions != nil { + // TODO: add nat + if (routeOptions.OverrideAddress.IsValid() || routeOptions.OverridePort > 0) && !metadata.RouteOriginalDestination.IsValid() { + metadata.RouteOriginalDestination = metadata.Destination + } + if routeOptions.OverrideAddress.IsValid() { + metadata.Destination = M.Socksaddr{ + Addr: routeOptions.OverrideAddress.Addr, + Port: metadata.Destination.Port, + Fqdn: routeOptions.OverrideAddress.Fqdn, + } + } + if routeOptions.OverridePort > 0 { + metadata.Destination = M.Socksaddr{ + Addr: metadata.Destination.Addr, + Port: routeOptions.OverridePort, + Fqdn: metadata.Destination.Fqdn, + } + } + metadata.NetworkStrategy = routeOptions.NetworkStrategy + metadata.FallbackDelay = routeOptions.FallbackDelay + metadata.UDPDisableDomainUnmapping = routeOptions.UDPDisableDomainUnmapping + metadata.UDPConnect = routeOptions.UDPConnect + } + switch action := currentRule.Action().(type) { case *rule.RuleActionSniff: if !preMatch { newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 52e95207..1b4099c9 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -19,6 +19,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -30,6 +31,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti return &RuleActionRoute{ Outbound: action.RouteOptions.Outbound, RuleActionRouteOptions: RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0), + OverridePort: action.RouteOptions.OverridePort, NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, @@ -38,6 +41,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti }, nil case C.RuleActionTypeRouteOptions: return &RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0), + OverridePort: action.RouteOptionsOptions.OverridePort, NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, @@ -139,6 +144,8 @@ func (r *RuleActionRoute) String() string { } type RuleActionRouteOptions struct { + OverrideAddress M.Socksaddr + OverridePort uint16 NetworkStrategy C.NetworkStrategy NetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType