Add override destination to route options

This commit is contained in:
世界 2024-11-14 18:31:37 +08:00
parent b585a55b5c
commit ac847e1f63
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
8 changed files with 132 additions and 51 deletions

View file

@ -61,9 +61,10 @@ type InboundContext struct {
// cache // cache
// Deprecated: implement in rule action // Deprecated: implement in rule action
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.RouteOriginalDestination.IsValid() {
originDestination = metadata.RouteOriginalDestination
} else {
originDestination = metadata.Destination
}
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
if metadata.UDPDisableDomainUnmapping { 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 { } 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 { if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {

View file

@ -13,17 +13,27 @@ 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
for _, address := range destinationAddresses { if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) for _, address := range destinationAddresses {
if err == nil { conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
return conn, nil 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...) 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
for _, address := range destinationAddresses { if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) for _, address := range destinationAddresses {
if err == nil { conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
return conn, address, nil 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...) return nil, netip.Addr{}, E.Errors(errors...)
} }

View file

@ -100,6 +100,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,
@ -107,4 +116,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"`
OverridePort uint16 `json:"override_port,omitempty"` // Deprecated: Use Route Action instead
ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` 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
} }

View file

@ -137,24 +137,25 @@ 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"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` RawRouteOptionsActionOptions
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,omitempty"`
} }
type _RouteOptionsActionOptions struct { type RawRouteOptionsActionOptions struct {
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` OverrideAddress string `json:"override_address,omitempty"`
FallbackDelay uint32 `json:"fallback_delay,omitempty"` OverridePort uint16 `json:"override_port,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
UDPConnect bool `json:"udp_connect,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 { 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