sing-box/route/rule/rule_action.go

366 lines
11 KiB
Go
Raw Normal View History

2024-10-21 15:38:34 +00:00
package rule
import (
2024-11-06 09:23:00 +00:00
"context"
2024-10-21 15:38:34 +00:00
"net/netip"
"strings"
2024-11-06 09:23:00 +00:00
"sync"
2024-11-06 09:30:40 +00:00
"syscall"
2024-10-21 15:38:34 +00:00
"time"
"github.com/sagernet/sing-box/adapter"
2024-11-06 09:30:40 +00:00
"github.com/sagernet/sing-box/common/dialer"
2024-10-21 15:38:34 +00:00
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
2024-10-22 13:28:22 +00:00
"github.com/sagernet/sing-tun"
2024-11-06 09:23:00 +00:00
"github.com/sagernet/sing/common"
2024-10-21 15:38:34 +00:00
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
2024-11-06 09:23:00 +00:00
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
2024-11-06 09:30:40 +00:00
N "github.com/sagernet/sing/common/network"
2024-10-21 15:38:34 +00:00
)
func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) {
2024-10-21 15:38:34 +00:00
switch action.Action {
2024-11-06 09:30:40 +00:00
case "":
return nil, nil
2024-10-21 15:38:34 +00:00
case C.RuleActionTypeRoute:
return &RuleActionRoute{
2024-11-06 09:30:40 +00:00
Outbound: action.RouteOptions.Outbound,
2024-11-11 08:32:34 +00:00
RuleActionRouteOptions: RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
OverridePort: action.RouteOptions.OverridePort,
2024-11-12 11:37:10 +00:00
NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
2024-11-11 08:32:34 +00:00
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptions.UDPConnect,
},
2024-11-06 09:30:40 +00:00
}, nil
case C.RuleActionTypeRouteOptions:
return &RuleActionRouteOptions{
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptionsOptions.OverrideAddress, 0),
OverridePort: action.RouteOptionsOptions.OverridePort,
2024-11-12 11:37:10 +00:00
NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy),
FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
2024-11-06 09:30:40 +00:00
UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
UDPConnect: action.RouteOptionsOptions.UDPConnect,
2024-11-24 06:45:40 +00:00
UDPTimeout: time.Duration(action.RouteOptionsOptions.UDPTimeout),
2024-11-06 09:30:40 +00:00
}, nil
case C.RuleActionTypeDirect:
directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions))
2024-11-06 09:30:40 +00:00
if err != nil {
return nil, err
}
var description string
descriptions := action.DirectOptions.Descriptions()
switch len(descriptions) {
case 0:
case 1:
description = F.ToString("(", descriptions[0], ")")
case 2:
description = F.ToString("(", descriptions[0], ",", descriptions[1], ")")
default:
description = F.ToString("(", descriptions[0], ",", descriptions[1], ",...)")
}
return &RuleActionDirect{
Dialer: directDialer,
description: description,
2024-10-21 15:38:34 +00:00
}, nil
case C.RuleActionTypeReject:
return &RuleActionReject{
2024-10-22 13:28:22 +00:00
Method: action.RejectOptions.Method,
2024-11-06 09:23:00 +00:00
NoDrop: action.RejectOptions.NoDrop,
logger: logger,
2024-10-21 15:38:34 +00:00
}, nil
case C.RuleActionTypeHijackDNS:
return &RuleActionHijackDNS{}, nil
case C.RuleActionTypeSniff:
sniffAction := &RuleActionSniff{
snifferNames: action.SniffOptions.Sniffer,
Timeout: time.Duration(action.SniffOptions.Timeout),
}
return sniffAction, sniffAction.build()
case C.RuleActionTypeResolve:
return &RuleActionResolve{
Strategy: dns.DomainStrategy(action.ResolveOptions.Strategy),
Server: action.ResolveOptions.Server,
}, nil
default:
panic(F.ToString("unknown rule action: ", action.Action))
}
}
2024-11-06 09:23:00 +00:00
func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction {
2024-10-21 15:38:34 +00:00
switch action.Action {
2024-11-06 09:30:40 +00:00
case "":
return nil
2024-10-21 15:38:34 +00:00
case C.RuleActionTypeRoute:
return &RuleActionDNSRoute{
2024-11-11 08:32:34 +00:00
Server: action.RouteOptions.Server,
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
DisableCache: action.RouteOptions.DisableCache,
RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
},
2024-10-21 15:38:34 +00:00
}
2024-11-06 09:30:40 +00:00
case C.RuleActionTypeRouteOptions:
return &RuleActionDNSRouteOptions{
DisableCache: action.RouteOptionsOptions.DisableCache,
RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
2024-11-18 10:55:34 +00:00
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
2024-11-06 09:30:40 +00:00
}
2024-10-21 15:38:34 +00:00
case C.RuleActionTypeReject:
return &RuleActionReject{
2024-10-22 13:28:22 +00:00
Method: action.RejectOptions.Method,
2024-11-06 09:23:00 +00:00
NoDrop: action.RejectOptions.NoDrop,
logger: logger,
2024-10-21 15:38:34 +00:00
}
default:
panic(F.ToString("unknown rule action: ", action.Action))
}
}
type RuleActionRoute struct {
2024-11-06 09:30:40 +00:00
Outbound string
2024-11-11 08:32:34 +00:00
RuleActionRouteOptions
2024-10-21 15:38:34 +00:00
}
func (r *RuleActionRoute) Type() string {
return C.RuleActionTypeRoute
}
func (r *RuleActionRoute) String() string {
2024-11-11 08:32:34 +00:00
var descriptions []string
descriptions = append(descriptions, r.Outbound)
if r.UDPDisableDomainUnmapping {
descriptions = append(descriptions, "udp-disable-domain-unmapping")
}
if r.UDPConnect {
descriptions = append(descriptions, "udp-connect")
}
return F.ToString("route(", strings.Join(descriptions, ","), ")")
2024-10-21 15:38:34 +00:00
}
2024-11-06 09:30:40 +00:00
type RuleActionRouteOptions struct {
OverrideAddress M.Socksaddr
OverridePort uint16
2024-11-12 11:37:10 +00:00
NetworkStrategy C.NetworkStrategy
2024-11-13 11:05:28 +00:00
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
2024-11-12 11:37:10 +00:00
FallbackDelay time.Duration
2024-11-06 09:30:40 +00:00
UDPDisableDomainUnmapping bool
UDPConnect bool
2024-11-24 06:45:40 +00:00
UDPTimeout time.Duration
2024-11-06 09:30:40 +00:00
}
func (r *RuleActionRouteOptions) Type() string {
return C.RuleActionTypeRouteOptions
}
func (r *RuleActionRouteOptions) String() string {
var descriptions []string
if r.UDPDisableDomainUnmapping {
descriptions = append(descriptions, "udp-disable-domain-unmapping")
}
if r.UDPConnect {
descriptions = append(descriptions, "udp-connect")
}
return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
}
2024-10-21 15:38:34 +00:00
type RuleActionDNSRoute struct {
2024-11-11 08:32:34 +00:00
Server string
RuleActionDNSRouteOptions
2024-10-21 15:38:34 +00:00
}
func (r *RuleActionDNSRoute) Type() string {
return C.RuleActionTypeRoute
}
func (r *RuleActionDNSRoute) String() string {
2024-11-11 08:32:34 +00:00
var descriptions []string
descriptions = append(descriptions, r.Server)
if r.DisableCache {
descriptions = append(descriptions, "disable-cache")
}
if r.RewriteTTL != nil {
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
}
if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
}
return F.ToString("route(", strings.Join(descriptions, ","), ")")
2024-10-21 15:38:34 +00:00
}
2024-11-06 09:30:40 +00:00
type RuleActionDNSRouteOptions struct {
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
func (r *RuleActionDNSRouteOptions) Type() string {
return C.RuleActionTypeRouteOptions
}
func (r *RuleActionDNSRouteOptions) String() string {
var descriptions []string
if r.DisableCache {
descriptions = append(descriptions, "disable-cache")
}
if r.RewriteTTL != nil {
2024-11-11 08:32:34 +00:00
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
2024-11-06 09:30:40 +00:00
}
if r.ClientSubnet.IsValid() {
2024-11-11 08:32:34 +00:00
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
2024-11-06 09:30:40 +00:00
}
return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
}
type RuleActionDirect struct {
Dialer N.Dialer
description string
}
2024-10-21 15:38:34 +00:00
2024-11-06 09:30:40 +00:00
func (r *RuleActionDirect) Type() string {
return C.RuleActionTypeDirect
2024-10-21 15:38:34 +00:00
}
2024-11-06 09:30:40 +00:00
func (r *RuleActionDirect) String() string {
return "direct" + r.description
2024-10-21 15:38:34 +00:00
}
type RuleActionReject struct {
2024-11-06 09:23:00 +00:00
Method string
NoDrop bool
logger logger.ContextLogger
dropAccess sync.Mutex
dropCounter []time.Time
2024-10-21 15:38:34 +00:00
}
func (r *RuleActionReject) Type() string {
return C.RuleActionTypeReject
}
func (r *RuleActionReject) String() string {
if r.Method == C.RuleActionRejectMethodDefault {
return "reject"
}
return F.ToString("reject(", r.Method, ")")
}
2024-11-06 09:23:00 +00:00
func (r *RuleActionReject) Error(ctx context.Context) error {
var returnErr error
2024-10-22 13:28:22 +00:00
switch r.Method {
2024-11-06 09:23:00 +00:00
case C.RuleActionRejectMethodDefault:
2024-11-06 09:30:40 +00:00
returnErr = syscall.ECONNREFUSED
2024-10-22 13:28:22 +00:00
case C.RuleActionRejectMethodDrop:
return tun.ErrDrop
default:
panic(F.ToString("unknown reject method: ", r.Method))
}
2024-11-06 09:23:00 +00:00
r.dropAccess.Lock()
defer r.dropAccess.Unlock()
timeNow := time.Now()
r.dropCounter = common.Filter(r.dropCounter, func(t time.Time) bool {
return timeNow.Sub(t) <= 30*time.Second
})
r.dropCounter = append(r.dropCounter, timeNow)
if len(r.dropCounter) > 50 {
if ctx != nil {
r.logger.DebugContext(ctx, "dropped due to flooding")
}
return tun.ErrDrop
}
return returnErr
2024-10-22 13:28:22 +00:00
}
2024-10-21 15:38:34 +00:00
type RuleActionHijackDNS struct{}
func (r *RuleActionHijackDNS) Type() string {
return C.RuleActionTypeHijackDNS
}
func (r *RuleActionHijackDNS) String() string {
return "hijack-dns"
}
type RuleActionSniff struct {
snifferNames []string
StreamSniffers []sniff.StreamSniffer
PacketSniffers []sniff.PacketSniffer
Timeout time.Duration
// Deprecated
OverrideDestination bool
}
func (r *RuleActionSniff) Type() string {
return C.RuleActionTypeSniff
}
func (r *RuleActionSniff) build() error {
2024-10-23 05:44:08 +00:00
for _, name := range r.snifferNames {
switch name {
case C.ProtocolTLS:
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
case C.ProtocolHTTP:
r.StreamSniffers = append(r.StreamSniffers, sniff.HTTPHost)
case C.ProtocolQUIC:
r.PacketSniffers = append(r.PacketSniffers, sniff.QUICClientHello)
case C.ProtocolDNS:
r.StreamSniffers = append(r.StreamSniffers, sniff.StreamDomainNameQuery)
r.PacketSniffers = append(r.PacketSniffers, sniff.DomainNameQuery)
case C.ProtocolSTUN:
r.PacketSniffers = append(r.PacketSniffers, sniff.STUNMessage)
case C.ProtocolBitTorrent:
r.StreamSniffers = append(r.StreamSniffers, sniff.BitTorrent)
r.PacketSniffers = append(r.PacketSniffers, sniff.UTP)
r.PacketSniffers = append(r.PacketSniffers, sniff.UDPTracker)
case C.ProtocolDTLS:
r.PacketSniffers = append(r.PacketSniffers, sniff.DTLSRecord)
case C.ProtocolSSH:
r.StreamSniffers = append(r.StreamSniffers, sniff.SSH)
case C.ProtocolRDP:
r.StreamSniffers = append(r.StreamSniffers, sniff.RDP)
default:
return E.New("unknown sniffer: ", name)
2024-10-21 15:38:34 +00:00
}
}
return nil
}
func (r *RuleActionSniff) String() string {
if len(r.snifferNames) == 0 && r.Timeout == 0 {
return "sniff"
} else if len(r.snifferNames) > 0 && r.Timeout == 0 {
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")")
} else if len(r.snifferNames) == 0 && r.Timeout > 0 {
return F.ToString("sniff(", r.Timeout.String(), ")")
} else {
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")")
}
}
type RuleActionResolve struct {
Strategy dns.DomainStrategy
Server string
}
func (r *RuleActionResolve) Type() string {
return C.RuleActionTypeResolve
}
func (r *RuleActionResolve) String() string {
if r.Strategy == dns.DomainStrategyAsIS && r.Server == "" {
return F.ToString("resolve")
} else if r.Strategy != dns.DomainStrategyAsIS && r.Server == "" {
return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ")")
} else if r.Strategy == dns.DomainStrategyAsIS && r.Server != "" {
return F.ToString("resolve(", r.Server, ")")
} else {
return F.ToString("resolve(", option.DomainStrategy(r.Strategy).String(), ",", r.Server, ")")
}
}