| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- package rule
- import (
- "context"
- "net/netip"
- "strings"
- "sync"
- "syscall"
- "time"
- "github.com/sagernet/sing-box/adapter"
- "github.com/sagernet/sing-box/common/dialer"
- "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"
- "github.com/sagernet/sing-tun"
- "github.com/sagernet/sing/common"
- E "github.com/sagernet/sing/common/exceptions"
- F "github.com/sagernet/sing/common/format"
- "github.com/sagernet/sing/common/logger"
- N "github.com/sagernet/sing/common/network"
- )
- func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action option.RuleAction) (adapter.RuleAction, error) {
- switch action.Action {
- case "":
- return nil, nil
- case C.RuleActionTypeRoute:
- return &RuleActionRoute{
- Outbound: action.RouteOptions.Outbound,
- RuleActionRouteOptions: RuleActionRouteOptions{
- NetworkStrategy: C.NetworkStrategy(action.RouteOptions.NetworkStrategy),
- FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
- UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
- UDPConnect: action.RouteOptions.UDPConnect,
- },
- }, nil
- case C.RuleActionTypeRouteOptions:
- return &RuleActionRouteOptions{
- NetworkStrategy: C.NetworkStrategy(action.RouteOptionsOptions.NetworkStrategy),
- FallbackDelay: time.Duration(action.RouteOptionsOptions.FallbackDelay),
- UDPDisableDomainUnmapping: action.RouteOptionsOptions.UDPDisableDomainUnmapping,
- UDPConnect: action.RouteOptionsOptions.UDPConnect,
- }, nil
- case C.RuleActionTypeDirect:
- directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions))
- 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,
- }, nil
- case C.RuleActionTypeReject:
- return &RuleActionReject{
- Method: action.RejectOptions.Method,
- NoDrop: action.RejectOptions.NoDrop,
- logger: logger,
- }, 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))
- }
- }
- func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) adapter.RuleAction {
- switch action.Action {
- case "":
- return nil
- case C.RuleActionTypeRoute:
- return &RuleActionDNSRoute{
- Server: action.RouteOptions.Server,
- RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
- DisableCache: action.RouteOptions.DisableCache,
- RewriteTTL: action.RouteOptions.RewriteTTL,
- ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
- },
- }
- case C.RuleActionTypeRouteOptions:
- return &RuleActionDNSRouteOptions{
- DisableCache: action.RouteOptionsOptions.DisableCache,
- RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
- ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
- }
- case C.RuleActionTypeReject:
- return &RuleActionReject{
- Method: action.RejectOptions.Method,
- NoDrop: action.RejectOptions.NoDrop,
- logger: logger,
- }
- default:
- panic(F.ToString("unknown rule action: ", action.Action))
- }
- }
- type RuleActionRoute struct {
- Outbound string
- RuleActionRouteOptions
- }
- func (r *RuleActionRoute) Type() string {
- return C.RuleActionTypeRoute
- }
- func (r *RuleActionRoute) String() string {
- 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, ","), ")")
- }
- type RuleActionRouteOptions struct {
- NetworkStrategy C.NetworkStrategy
- FallbackDelay time.Duration
- UDPDisableDomainUnmapping bool
- UDPConnect bool
- }
- 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, ","), ")")
- }
- type RuleActionDNSRoute struct {
- Server string
- RuleActionDNSRouteOptions
- }
- func (r *RuleActionDNSRoute) Type() string {
- return C.RuleActionTypeRoute
- }
- func (r *RuleActionDNSRoute) String() string {
- 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, ","), ")")
- }
- 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 {
- 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-options(", strings.Join(descriptions, ","), ")")
- }
- type RuleActionDirect struct {
- Dialer N.Dialer
- description string
- }
- func (r *RuleActionDirect) Type() string {
- return C.RuleActionTypeDirect
- }
- func (r *RuleActionDirect) String() string {
- return "direct" + r.description
- }
- type RuleActionReject struct {
- Method string
- NoDrop bool
- logger logger.ContextLogger
- dropAccess sync.Mutex
- dropCounter []time.Time
- }
- 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, ")")
- }
- func (r *RuleActionReject) Error(ctx context.Context) error {
- var returnErr error
- switch r.Method {
- case C.RuleActionRejectMethodDefault:
- returnErr = syscall.ECONNREFUSED
- case C.RuleActionRejectMethodDrop:
- return tun.ErrDrop
- default:
- panic(F.ToString("unknown reject method: ", r.Method))
- }
- 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
- }
- 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 {
- 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)
- }
- }
- 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, ")")
- }
- }
|