Add override destination to route options

This commit is contained in:
世界 2024-11-14 18:31:37 +08:00
parent ccffca9e13
commit 7aa0c572cc
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
18 changed files with 326 additions and 123 deletions

View File

@ -64,6 +64,7 @@ type InboundContext struct {
InboundDetour string InboundDetour string
LastInbound string LastInbound string
OriginDestination M.Socksaddr OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
// Deprecated // Deprecated
InboundOptions option.InboundOptions InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool

View File

@ -25,11 +25,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
var outConn net.Conn var outConn net.Conn
var err error var err error
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
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)
}
} else { } else {
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) 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 { } else {
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
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)
}
} else { } else {
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination) outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
} }
@ -91,11 +83,17 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
return err return err
} }
if destinationAddress.IsValid() { if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() { var originDestination M.Socksaddr
if metadata.UDPDisableDomainUnmapping { if metadata.RouteOriginalDestination.IsValid() {
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) originDestination = metadata.RouteOriginalDestination
} else { } else {
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination) 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), originDestination)
} else {
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
} }
} }
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {

View File

@ -13,18 +13,28 @@ import (
N "github.com/sagernet/sing/common/network" 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 { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} }
var errors []error var errors []error
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
for _, address := range destinationAddresses { for _, address := range destinationAddresses {
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil { if err == nil {
return conn, nil return conn, nil
} }
errors = append(errors, err) 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)
}
}
return nil, E.Errors(errors...) 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 { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} }
var errors []error var errors []error
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
for _, address := range destinationAddresses { for _, address := range destinationAddresses {
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil { if err == nil {
return conn, address, nil return conn, address, nil
} }
errors = append(errors, err) 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)
}
}
return nil, netip.Addr{}, E.Errors(errors...) return nil, netip.Addr{}, E.Errors(errors...)
} }

View File

@ -1,16 +1,11 @@
--- ---
icon: material/new icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
:material-plus: [cache_capacity](#cache_capacity) :material-plus: [cache_capacity](#cache_capacity)
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)
# DNS # DNS
### Structure ### Structure
@ -70,7 +65,7 @@ Make each DNS server's cache independent for special purposes. If enabled, will
#### cache_capacity #### cache_capacity
!!! quote "Since sing-box 1.11.0" !!! question "Since sing-box 1.11.0"
LRU cache capacity. LRU cache capacity.

View File

@ -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) :material-plus: [cache_capacity](#cache_capacity)
!!! quote "自 sing-box 1.9.0 起"
:material-plus: [client_subnet](#client_subnet)
# DNS # DNS
### 结构 ### 结构
@ -68,7 +64,7 @@ icon: material/new
#### cache_capacity #### cache_capacity
!!! quote "自 sing-box 1.11.0 起" !!! question "自 sing-box 1.11.0 起"
LRU 缓存容量。 LRU 缓存容量。

View File

@ -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. `direct` outbound send requests directly.
### Structure ### Structure
@ -9,7 +18,6 @@
"override_address": "1.0.0.1", "override_address": "1.0.0.1",
"override_port": 53, "override_port": 53,
"proxy_protocol": 0,
... // Dial Fields ... // Dial Fields
} }
@ -19,16 +27,20 @@
#### override_address #### 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 the connection destination address.
#### override_port #### 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. 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`. Protocol value can be `1` or `2`.
### Dial Fields ### Dial Fields

View File

@ -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` 出站直接发送请求。 `direct` 出站直接发送请求。
### 结构 ### 结构
@ -9,7 +18,6 @@
"override_address": "1.0.0.1", "override_address": "1.0.0.1",
"override_port": 53, "override_port": 53,
"proxy_protocol": 0,
... // 拨号字段 ... // 拨号字段
} }
@ -19,18 +27,20 @@
#### override_address #### 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 #### 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/)。 参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@ -10,12 +10,8 @@ icon: material/new-box
{ {
"action": "route", // default "action": "route", // default
"outbound": "", "outbound": "",
"network_strategy": "",
"network_type": [], ... // route-options Fields
"fallback_network_type": [],
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false
} }
``` ```
@ -31,6 +27,34 @@ icon: material/new-box
Tag of target outbound. 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 #### network_strategy
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. 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. 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 ### reject
```json ```json

View File

@ -10,12 +10,8 @@ icon: material/new-box
{ {
"action": "route", // 默认 "action": "route", // 默认
"outbound": "", "outbound": "",
"network_strategy": "",
"fallback_delay": "", ... // route-options 字段
"network_type": [],
"fallback_network_type": [],
"udp_disable_domain_unmapping": false,
"udp_connect": false
} }
``` ```
@ -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 #### network_strategy
详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
@ -56,24 +84,6 @@ icon: material/new-box
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。 如果启用,将尝试将 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 ### reject
```json ```json

View File

@ -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. 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 ## 1.10.0
#### TUN address fields are merged #### TUN address fields are merged

View File

@ -20,6 +20,13 @@ icon: material/delete-alert
旧字段将在 sing-box 1.13.0 中被移除。 旧字段将在 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 ## 1.10.0
#### Match source 规则项已重命名 #### Match source 规则项已重命名

View File

@ -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 ## 1.10.0
### TUN address fields are merged ### TUN address fields are merged

View File

@ -104,6 +104,7 @@ icon: material/arrange-bring-forward
### 迁移旧的入站字段到规则动作 ### 迁移旧的入站字段到规则动作
入站选项已被弃用,且可以被规则动作替代。 入站选项已被弃用,且可以被规则动作替代。
!!! info "参考" !!! 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 ## 1.10.0
### TUN 地址字段已合并 ### TUN 地址字段已合并
@ -164,8 +204,6 @@ icon: material/arrange-bring-forward
`inet4_route_address``inet6_route_address` 已合并为 `route_address` `inet4_route_address``inet6_route_address` 已合并为 `route_address`
`inet4_route_exclude_address``inet6_route_exclude_address` 已合并为 `route_exclude_address` `inet4_route_exclude_address``inet6_route_exclude_address` 已合并为 `route_exclude_address`
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
!!! info "参考" !!! info "参考"
[TUN](/zh/configuration/inbound/tun/) [TUN](/zh/configuration/inbound/tun/)

View File

@ -104,6 +104,15 @@ var OptionInboundOptions = Note{
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", 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{ var Options = []Note{
OptionBadMatchSource, OptionBadMatchSource,
OptionGEOIP, OptionGEOIP,
@ -111,4 +120,5 @@ var Options = []Note{
OptionTUNAddressX, OptionTUNAddressX,
OptionSpecialOutbounds, OptionSpecialOutbounds,
OptionInboundOptions, OptionInboundOptions,
OptionDestinationOverrideFields,
} }

View File

@ -1,5 +1,12 @@
package option package option
import (
"context"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing/common/json"
)
type DirectInboundOptions struct { type DirectInboundOptions struct {
ListenOptions ListenOptions
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
@ -7,9 +14,25 @@ type DirectInboundOptions struct {
OverridePort uint16 `json:"override_port,omitempty"` OverridePort uint16 `json:"override_port,omitempty"`
} }
type DirectOutboundOptions struct { type _DirectOutboundOptions struct {
DialerOptions DialerOptions
// Deprecated: Use Route Action instead
OverrideAddress string `json:"override_address,omitempty"` OverrideAddress string `json:"override_address,omitempty"`
// Deprecated: Use Route Action instead
OverridePort uint16 `json:"override_port,omitempty"` OverridePort uint16 `json:"override_port,omitempty"`
// Deprecated: removed
ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` 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
}

View File

@ -138,23 +138,24 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
type RouteActionOptions struct { type RouteActionOptions struct {
Outbound string `json:"outbound,omitempty"` Outbound string `json:"outbound,omitempty"`
RawRouteOptionsActionOptions
}
type RawRouteOptionsActionOptions struct {
OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"override_port,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"` FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"` UDPConnect bool `json:"udp_connect,omitempty"`
} }
type _RouteOptionsActionOptions struct { type RouteOptionsActionOptions RawRouteOptionsActionOptions
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
func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error { func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, (*_RouteOptionsActionOptions)(r)) err := json.Unmarshal(data, (*RawRouteOptionsActionOptions)(r))
if err != nil { if err != nil {
return err return err
} }

View File

@ -422,17 +422,38 @@ match:
} }
} }
} }
var routeOptions *rule.RuleActionRouteOptions
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *rule.RuleActionRoute: case *rule.RuleActionRoute:
metadata.NetworkStrategy = action.NetworkStrategy routeOptions = &action.RuleActionRouteOptions
metadata.FallbackDelay = action.FallbackDelay
metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping
metadata.UDPConnect = action.UDPConnect
case *rule.RuleActionRouteOptions: case *rule.RuleActionRouteOptions:
metadata.NetworkStrategy = action.NetworkStrategy routeOptions = action
metadata.FallbackDelay = action.FallbackDelay }
metadata.UDPDisableDomainUnmapping = action.UDPDisableDomainUnmapping if routeOptions != nil {
metadata.UDPConnect = action.UDPConnect // 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: case *rule.RuleActionSniff:
if !preMatch { if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn) newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn)

View File

@ -19,6 +19,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -30,6 +31,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
return &RuleActionRoute{ return &RuleActionRoute{
Outbound: action.RouteOptions.Outbound, Outbound: action.RouteOptions.Outbound,
RuleActionRouteOptions: RuleActionRouteOptions{ RuleActionRouteOptions: RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
OverridePort: action.RouteOptions.OverridePort,
NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy), NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay), FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping, UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
@ -38,6 +41,8 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
}, nil }, nil
case C.RuleActionTypeRouteOptions: case C.RuleActionTypeRouteOptions:
return &RuleActionRouteOptions{ return &RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0),
OverridePort: action.RouteOptionsOptions.OverridePort,
NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy), NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay), FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping, UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
@ -139,6 +144,8 @@ func (r *RuleActionRoute) String() string {
} }
type RuleActionRouteOptions struct { type RuleActionRouteOptions struct {
OverrideAddress M.Socksaddr
OverridePort uint16
NetworkStrategy C.NetworkStrategy NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType FallbackNetworkType []C.InterfaceType