diff --git a/adapter/inbound.go b/adapter/inbound.go index 6e478ba3..356a3200 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -27,7 +27,7 @@ type InjectableInbound interface { type InboundContext struct { Inbound string InboundType string - IPVersion int + IPVersion uint8 Network string Source M.Socksaddr Destination M.Socksaddr diff --git a/adapter/router.go b/adapter/router.go index cdc03961..b9eace99 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -77,6 +77,7 @@ type Rule interface { type DNSRule interface { Rule DisableCache() bool + RewriteTTL() *uint32 } type InterfaceUpdateListener interface { diff --git a/common/badjsonmerge/merge_test.go b/common/badjsonmerge/merge_test.go index d1714cd6..be4481b5 100644 --- a/common/badjsonmerge/merge_test.go +++ b/common/badjsonmerge/merge_test.go @@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Network: N.NetworkTCP, + Network: []string{N.NetworkTCP}, Outbound: "direct", }, }, @@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) { { Type: C.RuleTypeDefault, DefaultOptions: option.DefaultRule{ - Network: N.NetworkUDP, + Network: []string{N.NetworkUDP}, Outbound: "direct", }, }, diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 0205daaf..d577560c 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -27,7 +27,12 @@ type slowOpenConn struct { func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP { - return dialer.DialContext(ctx, network, destination.String(), nil) + switch N.NetworkName(network) { + case N.NetworkTCP, N.NetworkUDP: + return dialer.Dialer.DialContext(ctx, network, destination.String()) + default: + return dialer.Dialer.DialContext(ctx, network, destination.AddrString()) + } } return &slowOpenConn{ dialer: dialer, diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index a838105c..3cee478d 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -9,7 +9,9 @@ "mixed-in" ], "ip_version": 6, - "network": "tcp", + "network": [ + "tcp" + ], "auth_user": [ "usera", "userb" @@ -244,18 +246,12 @@ Tag of the target outbound. #### mode +==Required== + `and` or `or` #### rules -Included default rules. - -#### invert - -Invert match result. - -#### outbound - ==Required== -Tag of the target outbound. +Included default rules. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index fc7d5990..4a09ed8e 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -9,7 +9,9 @@ "mixed-in" ], "ip_version": 6, - "network": "tcp", + "network": [ + "tcp" + ], "auth_user": [ "usera", "userb" @@ -242,18 +244,12 @@ #### mode +==必填== + `and` 或 `or` #### rules -包括的默认规则。 - -#### invert - -反选匹配结果。 - -#### outbound - ==必填== -目标出站的标签。 +包括的默认规则。 \ No newline at end of file diff --git a/inbound/tun.go b/inbound/tun.go index 94d692db..5ac0c33e 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -38,10 +38,6 @@ type Tun struct { } func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { - tunName := options.InterfaceName - if tunName == "" { - tunName = tun.CalculateInterfaceName("") - } tunMTU := options.MTU if tunMTU == 0 { tunMTU = 9000 @@ -75,7 +71,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger logger: logger, inboundOptions: options.InboundOptions, tunOptions: tun.Options{ - Name: tunName, + Name: options.InterfaceName, MTU: tunMTU, Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build), Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build), @@ -141,12 +137,17 @@ func (t *Tun) Tag() string { func (t *Tun) Start() error { if C.IsAndroid && t.platformInterface == nil { + t.logger.Trace("building android rules") t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t) } + if t.tunOptions.Name == "" { + t.tunOptions.Name = tun.CalculateInterfaceName("") + } var ( tunInterface tun.Tun err error ) + t.logger.Trace("opening interface") if t.platformInterface != nil { tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions) } else { @@ -155,6 +156,7 @@ func (t *Tun) Start() error { if err != nil { return E.Cause(err, "configure tun interface") } + t.logger.Trace("creating stack") t.tunIf = tunInterface t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{ Context: t.ctx, @@ -172,6 +174,7 @@ func (t *Tun) Start() error { if err != nil { return err } + t.logger.Trace("starting stack") err = t.tunStack.Start() if err != nil { return err diff --git a/option/dns.go b/option/dns.go index d376f433..df8e7e70 100644 --- a/option/dns.go +++ b/option/dns.go @@ -1,14 +1,5 @@ package option -import ( - "reflect" - - "github.com/sagernet/sing-box/common/json" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" -) - type DNSOptions struct { Servers []DNSServerOptions `json:"servers,omitempty"` Rules []DNSRule `json:"rules,omitempty"` @@ -32,97 +23,3 @@ type DNSServerOptions struct { Strategy DomainStrategy `json:"strategy,omitempty"` Detour string `json:"detour,omitempty"` } - -type _DNSRule struct { - Type string `json:"type,omitempty"` - DefaultOptions DefaultDNSRule `json:"-"` - LogicalOptions LogicalDNSRule `json:"-"` -} - -type DNSRule _DNSRule - -func (r DNSRule) MarshalJSON() ([]byte, error) { - var v any - switch r.Type { - case C.RuleTypeDefault: - r.Type = "" - v = r.DefaultOptions - case C.RuleTypeLogical: - v = r.LogicalOptions - default: - return nil, E.New("unknown rule type: " + r.Type) - } - return MarshallObjects((_DNSRule)(r), v) -} - -func (r *DNSRule) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_DNSRule)(r)) - if err != nil { - return err - } - var v any - switch r.Type { - case "", C.RuleTypeDefault: - r.Type = C.RuleTypeDefault - v = &r.DefaultOptions - case C.RuleTypeLogical: - v = &r.LogicalOptions - default: - return E.New("unknown rule type: " + r.Type) - } - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) - if err != nil { - return E.Cause(err, "dns route rule") - } - return nil -} - -type DefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` - Network string `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` -} - -func (r DefaultDNSRule) IsValid() bool { - var defaultValue DefaultDNSRule - defaultValue.Invert = r.Invert - defaultValue.Server = r.Server - defaultValue.DisableCache = r.DisableCache - return !reflect.DeepEqual(r, defaultValue) -} - -type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DefaultDNSRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` -} - -func (r LogicalDNSRule) IsValid() bool { - return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid) -} diff --git a/option/route.go b/option/route.go index 308c4802..43150576 100644 --- a/option/route.go +++ b/option/route.go @@ -1,14 +1,5 @@ package option -import ( - "reflect" - - "github.com/sagernet/sing-box/common/json" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" -) - type RouteOptions struct { GeoIP *GeoIPOptions `json:"geoip,omitempty"` Geosite *GeositeOptions `json:"geosite,omitempty"` @@ -32,94 +23,3 @@ type GeositeOptions struct { DownloadURL string `json:"download_url,omitempty"` DownloadDetour string `json:"download_detour,omitempty"` } - -type _Rule struct { - Type string `json:"type,omitempty"` - DefaultOptions DefaultRule `json:"-"` - LogicalOptions LogicalRule `json:"-"` -} - -type Rule _Rule - -func (r Rule) MarshalJSON() ([]byte, error) { - var v any - switch r.Type { - case C.RuleTypeDefault: - r.Type = "" - v = r.DefaultOptions - case C.RuleTypeLogical: - v = r.LogicalOptions - default: - return nil, E.New("unknown rule type: " + r.Type) - } - return MarshallObjects((_Rule)(r), v) -} - -func (r *Rule) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Rule)(r)) - if err != nil { - return err - } - var v any - switch r.Type { - case "", C.RuleTypeDefault: - r.Type = C.RuleTypeDefault - v = &r.DefaultOptions - case C.RuleTypeLogical: - v = &r.LogicalOptions - default: - return E.New("unknown rule type: " + r.Type) - } - err = UnmarshallExcluded(bytes, (*_Rule)(r), v) - if err != nil { - return E.Cause(err, "route rule") - } - return nil -} - -type DefaultRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network string `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - GeoIP Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR Listable[string] `json:"ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` -} - -func (r DefaultRule) IsValid() bool { - var defaultValue DefaultRule - defaultValue.Invert = r.Invert - defaultValue.Outbound = r.Outbound - return !reflect.DeepEqual(r, defaultValue) -} - -type LogicalRule struct { - Mode string `json:"mode"` - Rules []DefaultRule `json:"rules,omitempty"` - Invert bool `json:"invert,omitempty"` - Outbound string `json:"outbound,omitempty"` -} - -func (r LogicalRule) IsValid() bool { - return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid) -} diff --git a/option/rule.go b/option/rule.go new file mode 100644 index 00000000..f78a752d --- /dev/null +++ b/option/rule.go @@ -0,0 +1,101 @@ +package option + +import ( + "reflect" + + "github.com/sagernet/sing-box/common/json" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type _Rule struct { + Type string `json:"type,omitempty"` + DefaultOptions DefaultRule `json:"-"` + LogicalOptions LogicalRule `json:"-"` +} + +type Rule _Rule + +func (r Rule) MarshalJSON() ([]byte, error) { + var v any + switch r.Type { + case C.RuleTypeDefault: + r.Type = "" + v = r.DefaultOptions + case C.RuleTypeLogical: + v = r.LogicalOptions + default: + return nil, E.New("unknown rule type: " + r.Type) + } + return MarshallObjects((_Rule)(r), v) +} + +func (r *Rule) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_Rule)(r)) + if err != nil { + return err + } + var v any + switch r.Type { + case "", C.RuleTypeDefault: + r.Type = C.RuleTypeDefault + v = &r.DefaultOptions + case C.RuleTypeLogical: + v = &r.LogicalOptions + default: + return E.New("unknown rule type: " + r.Type) + } + err = UnmarshallExcluded(bytes, (*_Rule)(r), v) + if err != nil { + return E.Cause(err, "route rule") + } + return nil +} + +type DefaultRule struct { + Inbound Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network Listable[string] `json:"network,omitempty"` + AuthUser Listable[string] `json:"auth_user,omitempty"` + Protocol Listable[string] `json:"protocol,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + Geosite Listable[string] `json:"geosite,omitempty"` + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + GeoIP Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR Listable[string] `json:"ip_cidr,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + User Listable[string] `json:"user,omitempty"` + UserID Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + Invert bool `json:"invert,omitempty"` + Outbound string `json:"outbound,omitempty"` +} + +func (r DefaultRule) IsValid() bool { + var defaultValue DefaultRule + defaultValue.Invert = r.Invert + defaultValue.Outbound = r.Outbound + return !reflect.DeepEqual(r, defaultValue) +} + +type LogicalRule struct { + Mode string `json:"mode"` + Rules []DefaultRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` + Outbound string `json:"outbound,omitempty"` +} + +func (r LogicalRule) IsValid() bool { + return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid) +} diff --git a/option/rule_dns.go b/option/rule_dns.go new file mode 100644 index 00000000..563d3085 --- /dev/null +++ b/option/rule_dns.go @@ -0,0 +1,107 @@ +package option + +import ( + "reflect" + + "github.com/sagernet/sing-box/common/json" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type _DNSRule struct { + Type string `json:"type,omitempty"` + DefaultOptions DefaultDNSRule `json:"-"` + LogicalOptions LogicalDNSRule `json:"-"` +} + +type DNSRule _DNSRule + +func (r DNSRule) MarshalJSON() ([]byte, error) { + var v any + switch r.Type { + case C.RuleTypeDefault: + r.Type = "" + v = r.DefaultOptions + case C.RuleTypeLogical: + v = r.LogicalOptions + default: + return nil, E.New("unknown rule type: " + r.Type) + } + return MarshallObjects((_DNSRule)(r), v) +} + +func (r *DNSRule) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_DNSRule)(r)) + if err != nil { + return err + } + var v any + switch r.Type { + case "", C.RuleTypeDefault: + r.Type = C.RuleTypeDefault + v = &r.DefaultOptions + case C.RuleTypeLogical: + v = &r.LogicalOptions + default: + return E.New("unknown rule type: " + r.Type) + } + err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + if err != nil { + return E.Cause(err, "dns route rule") + } + return nil +} + +type DefaultDNSRule struct { + Inbound Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` + Network Listable[string] `json:"network,omitempty"` + AuthUser Listable[string] `json:"auth_user,omitempty"` + Protocol Listable[string] `json:"protocol,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + Geosite Listable[string] `json:"geosite,omitempty"` + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + User Listable[string] `json:"user,omitempty"` + UserID Listable[int32] `json:"user_id,omitempty"` + Outbound Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` +} + +func (r DefaultDNSRule) IsValid() bool { + var defaultValue DefaultDNSRule + defaultValue.Invert = r.Invert + defaultValue.Server = r.Server + defaultValue.DisableCache = r.DisableCache + defaultValue.RewriteTTL = r.RewriteTTL + return !reflect.DeepEqual(r, defaultValue) +} + +type LogicalDNSRule struct { + Mode string `json:"mode"` + Rules []DefaultDNSRule `json:"rules,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` + RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` +} + +func (r LogicalDNSRule) IsValid() bool { + return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid) +} diff --git a/route/router.go b/route/router.go index d988b70c..515d2402 100644 --- a/route/router.go +++ b/route/router.go @@ -2,14 +2,11 @@ package route import ( "context" - "io" "net" - "net/http" "net/netip" "net/url" "os" "os/user" - "path/filepath" "strings" "time" @@ -38,7 +35,6 @@ import ( F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/uot" ) @@ -127,6 +123,7 @@ func NewRouter( } router.dnsRules = append(router.dnsRules, dnsRule) } + transports := make([]dns.Transport, len(dnsOptions.Servers)) dummyTransportMap := make(map[string]dns.Transport) transportMap := make(map[string]dns.Transport) @@ -523,27 +520,6 @@ func (r *Router) Close() error { return err } -func (r *Router) GeoIPReader() *geoip.Reader { - return r.geoIPReader -} - -func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { - rule, cached := r.geositeCache[code] - if cached { - return rule, nil - } - items, err := r.geositeReader.Read(code) - if err != nil { - return nil, err - } - rule, err = NewDefaultRule(r, nil, geosite.Compile(items)) - if err != nil { - return nil, err - } - r.geositeCache[code] = rule - return rule, nil -} - func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { outbound, loaded := r.outboundByTag[tag] return outbound, loaded @@ -725,6 +701,13 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m } conn = bufio.NewCachedPacketConn(conn, buffer, destination) } + if r.dnsReverseMapping != nil && metadata.Domain == "" { + domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr) + if loaded { + metadata.Domain = domain + r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain) + } + } if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS { addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy)) if err != nil { @@ -898,239 +881,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) { r.v2rayServer = server } -func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - for _, subRule := range rule.LogicalOptions.Rules { - if cond(subRule) { - return true - } - } - } - } - return false -} - -func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if cond(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - for _, subRule := range rule.LogicalOptions.Rules { - if cond(subRule) { - return true - } - } - } - } - return false -} - -func isGeoIPRule(rule option.DefaultRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func isGeoIPDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) -} - -func isGeositeRule(rule option.DefaultRule) bool { - return len(rule.Geosite) > 0 -} - -func isGeositeDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.Geosite) > 0 -} - -func isProcessRule(rule option.DefaultRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 -} - -func isProcessDNSRule(rule option.DefaultDNSRule) bool { - return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 -} - -func notPrivateNode(code string) bool { - return code != "private" -} - -func (r *Router) prepareGeoIPDatabase() error { - var geoPath string - if r.geoIPOptions.Path != "" { - geoPath = r.geoIPOptions.Path - } else { - geoPath = "geoip.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - geoPath = C.BasePath(geoPath) - if !rw.FileExists(geoPath) { - r.logger.Warn("geoip database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeoIPDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geoip database: ", err) - os.Remove(geoPath) - // time.Sleep(10 * time.Second) - } - if err != nil { - return err - } - } - geoReader, codes, err := geoip.Open(geoPath) - if err != nil { - return E.Cause(err, "open geoip database") - } - r.logger.Info("loaded geoip database: ", len(codes), " codes") - r.geoIPReader = geoReader - return nil -} - -func (r *Router) prepareGeositeDatabase() error { - var geoPath string - if r.geositeOptions.Path != "" { - geoPath = r.geositeOptions.Path - } else { - geoPath = "geosite.db" - if foundPath, loaded := C.FindPath(geoPath); loaded { - geoPath = foundPath - } - } - geoPath = C.BasePath(geoPath) - if !rw.FileExists(geoPath) { - r.logger.Warn("geosite database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeositeDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geosite database: ", err) - os.Remove(geoPath) - // time.Sleep(10 * time.Second) - } - if err != nil { - return err - } - } - geoReader, codes, err := geosite.Open(geoPath) - if err == nil { - r.logger.Info("loaded geosite database: ", len(codes), " codes") - r.geositeReader = geoReader - } else { - return E.Cause(err, "open geosite database") - } - return nil -} - -func (r *Router) downloadGeoIPDatabase(savePath string) error { - var downloadURL string - if r.geoIPOptions.DownloadURL != "" { - downloadURL = r.geoIPOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" - } - r.logger.Info("downloading geoip database") - var detour adapter.Outbound - if r.geoIPOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.defaultOutboundForConnection - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - os.MkdirAll(parentDir, 0o755) - } - - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - defer saveFile.Close() - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - response, err := httpClient.Get(downloadURL) - if err != nil { - return err - } - defer response.Body.Close() - _, err = io.Copy(saveFile, response.Body) - return err -} - -func (r *Router) downloadGeositeDatabase(savePath string) error { - var downloadURL string - if r.geositeOptions.DownloadURL != "" { - downloadURL = r.geositeOptions.DownloadURL - } else { - downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" - } - r.logger.Info("downloading geosite database") - var detour adapter.Outbound - if r.geositeOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.defaultOutboundForConnection - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - os.MkdirAll(parentDir, 0o755) - } - - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - defer saveFile.Close() - - httpClient := &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - defer httpClient.CloseIdleConnections() - response, err := httpClient.Get(downloadURL) - if err != nil { - return err - } - defer response.Body.Close() - _, err = io.Copy(saveFile, response.Body) - return err -} - func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") } diff --git a/route/router_dns.go b/route/router_dns.go index 11c02c87..d343fb8b 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -47,6 +47,9 @@ func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport, if rule.DisableCache() { ctx = dns.ContextWithDisableCache(ctx, true) } + if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { + ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) + } detour := rule.Outbound() r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) if transport, loaded := r.transportMap[detour]; loaded { diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go new file mode 100644 index 00000000..a72b4bad --- /dev/null +++ b/route/router_geo_resources.go @@ -0,0 +1,283 @@ +package route + +import ( + "context" + "io" + "net" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/geoip" + "github.com/sagernet/sing-box/common/geosite" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/rw" +) + +func (r *Router) GeoIPReader() *geoip.Reader { + return r.geoIPReader +} + +func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { + rule, cached := r.geositeCache[code] + if cached { + return rule, nil + } + items, err := r.geositeReader.Read(code) + if err != nil { + return nil, err + } + rule, err = NewDefaultRule(r, nil, geosite.Compile(items)) + if err != nil { + return nil, err + } + r.geositeCache[code] = rule + return rule, nil +} + +func (r *Router) prepareGeoIPDatabase() error { + var geoPath string + if r.geoIPOptions.Path != "" { + geoPath = r.geoIPOptions.Path + } else { + geoPath = "geoip.db" + if foundPath, loaded := C.FindPath(geoPath); loaded { + geoPath = foundPath + } + } + geoPath = C.BasePath(geoPath) + if rw.FileExists(geoPath) { + geoReader, codes, err := geoip.Open(geoPath) + if err == nil { + r.logger.Info("loaded geoip database: ", len(codes), " codes") + r.geoIPReader = geoReader + return nil + } + } + if !rw.FileExists(geoPath) { + r.logger.Warn("geoip database not exists: ", geoPath) + var err error + for attempts := 0; attempts < 3; attempts++ { + err = r.downloadGeoIPDatabase(geoPath) + if err == nil { + break + } + r.logger.Error("download geoip database: ", err) + os.Remove(geoPath) + // time.Sleep(10 * time.Second) + } + if err != nil { + return err + } + } + geoReader, codes, err := geoip.Open(geoPath) + if err != nil { + return E.Cause(err, "open geoip database") + } + r.logger.Info("loaded geoip database: ", len(codes), " codes") + r.geoIPReader = geoReader + return nil +} + +func (r *Router) prepareGeositeDatabase() error { + var geoPath string + if r.geositeOptions.Path != "" { + geoPath = r.geositeOptions.Path + } else { + geoPath = "geosite.db" + if foundPath, loaded := C.FindPath(geoPath); loaded { + geoPath = foundPath + } + } + geoPath = C.BasePath(geoPath) + if !rw.FileExists(geoPath) { + r.logger.Warn("geosite database not exists: ", geoPath) + var err error + for attempts := 0; attempts < 3; attempts++ { + err = r.downloadGeositeDatabase(geoPath) + if err == nil { + break + } + r.logger.Error("download geosite database: ", err) + os.Remove(geoPath) + // time.Sleep(10 * time.Second) + } + if err != nil { + return err + } + } + geoReader, codes, err := geosite.Open(geoPath) + if err == nil { + r.logger.Info("loaded geosite database: ", len(codes), " codes") + r.geositeReader = geoReader + } else { + return E.Cause(err, "open geosite database") + } + return nil +} + +func (r *Router) downloadGeoIPDatabase(savePath string) error { + var downloadURL string + if r.geoIPOptions.DownloadURL != "" { + downloadURL = r.geoIPOptions.DownloadURL + } else { + downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" + } + r.logger.Info("downloading geoip database") + var detour adapter.Outbound + if r.geoIPOptions.DownloadDetour != "" { + outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) + if !loaded { + return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) + } + detour = outbound + } else { + detour = r.defaultOutboundForConnection + } + + if parentDir := filepath.Dir(savePath); parentDir != "" { + os.MkdirAll(parentDir, 0o755) + } + + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return E.Cause(err, "open output file: ", downloadURL) + } + defer saveFile.Close() + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: 5 * time.Second, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + defer httpClient.CloseIdleConnections() + response, err := httpClient.Get(downloadURL) + if err != nil { + return err + } + defer response.Body.Close() + _, err = io.Copy(saveFile, response.Body) + return err +} + +func (r *Router) downloadGeositeDatabase(savePath string) error { + var downloadURL string + if r.geositeOptions.DownloadURL != "" { + downloadURL = r.geositeOptions.DownloadURL + } else { + downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" + } + r.logger.Info("downloading geosite database") + var detour adapter.Outbound + if r.geositeOptions.DownloadDetour != "" { + outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) + if !loaded { + return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour) + } + detour = outbound + } else { + detour = r.defaultOutboundForConnection + } + + if parentDir := filepath.Dir(savePath); parentDir != "" { + os.MkdirAll(parentDir, 0o755) + } + + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return E.Cause(err, "open output file: ", downloadURL) + } + defer saveFile.Close() + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: 5 * time.Second, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + defer httpClient.CloseIdleConnections() + response, err := httpClient.Get(downloadURL) + if err != nil { + return err + } + defer response.Body.Close() + _, err = io.Copy(saveFile, response.Body) + return err +} + +func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + for _, subRule := range rule.LogicalOptions.Rules { + if cond(subRule) { + return true + } + } + } + } + return false +} + +func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + for _, subRule := range rule.LogicalOptions.Rules { + if cond(subRule) { + return true + } + } + } + } + return false +} + +func isGeoIPRule(rule option.DefaultRule) bool { + return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) +} + +func isGeoIPDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) +} + +func isGeositeRule(rule option.DefaultRule) bool { + return len(rule.Geosite) > 0 +} + +func isGeositeDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.Geosite) > 0 +} + +func isProcessRule(rule option.DefaultRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 +} + +func isProcessDNSRule(rule option.DefaultDNSRule) bool { + return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 +} + +func notPrivateNode(code string) bool { + return code != "private" +} diff --git a/route/rule_abstract.go b/route/rule_abstract.go new file mode 100644 index 00000000..be41401f --- /dev/null +++ b/route/rule_abstract.go @@ -0,0 +1,203 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" +) + +type abstractDefaultRule struct { + items []RuleItem + sourceAddressItems []RuleItem + sourcePortItems []RuleItem + destinationAddressItems []RuleItem + destinationPortItems []RuleItem + allItems []RuleItem + invert bool + outbound string +} + +func (r *abstractDefaultRule) Type() string { + return C.RuleTypeDefault +} + +func (r *abstractDefaultRule) Start() error { + for _, item := range r.allItems { + err := common.Start(item) + if err != nil { + return err + } + } + return nil +} + +func (r *abstractDefaultRule) Close() error { + for _, item := range r.allItems { + err := common.Close(item) + if err != nil { + return err + } + } + return nil +} + +func (r *abstractDefaultRule) UpdateGeosite() error { + for _, item := range r.allItems { + if geositeItem, isSite := item.(*GeositeItem); isSite { + err := geositeItem.Update() + if err != nil { + return err + } + } + } + return nil +} + +func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { + for _, item := range r.items { + if !item.Match(metadata) { + return r.invert + } + } + + if len(r.sourceAddressItems) > 0 { + var sourceAddressMatch bool + for _, item := range r.sourceAddressItems { + if item.Match(metadata) { + sourceAddressMatch = true + break + } + } + if !sourceAddressMatch { + return r.invert + } + } + + if len(r.sourcePortItems) > 0 { + var sourcePortMatch bool + for _, item := range r.sourcePortItems { + if item.Match(metadata) { + sourcePortMatch = true + break + } + } + if !sourcePortMatch { + return r.invert + } + } + + if len(r.destinationAddressItems) > 0 { + var destinationAddressMatch bool + for _, item := range r.destinationAddressItems { + if item.Match(metadata) { + destinationAddressMatch = true + break + } + } + if !destinationAddressMatch { + return r.invert + } + } + + if len(r.destinationPortItems) > 0 { + var destinationPortMatch bool + for _, item := range r.destinationPortItems { + if item.Match(metadata) { + destinationPortMatch = true + break + } + } + if !destinationPortMatch { + return r.invert + } + } + + return !r.invert +} + +func (r *abstractDefaultRule) Outbound() string { + return r.outbound +} + +func (r *abstractDefaultRule) String() string { + if !r.invert { + return strings.Join(F.MapToString(r.allItems), " ") + } else { + return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")" + } +} + +type abstractLogicalRule struct { + rules []adapter.Rule + mode string + invert bool + outbound string +} + +func (r *abstractLogicalRule) Type() string { + return C.RuleTypeLogical +} + +func (r *abstractLogicalRule) UpdateGeosite() error { + for _, rule := range r.rules { + err := rule.UpdateGeosite() + if err != nil { + return err + } + } + return nil +} + +func (r *abstractLogicalRule) Start() error { + for _, rule := range r.rules { + err := rule.Start() + if err != nil { + return err + } + } + return nil +} + +func (r *abstractLogicalRule) Close() error { + for _, rule := range r.rules { + err := rule.Close() + if err != nil { + return err + } + } + return nil +} + +func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool { + if r.mode == C.LogicalTypeAnd { + return common.All(r.rules, func(it adapter.Rule) bool { + return it.Match(metadata) + }) != r.invert + } else { + return common.Any(r.rules, func(it adapter.Rule) bool { + return it.Match(metadata) + }) != r.invert + } +} + +func (r *abstractLogicalRule) Outbound() string { + return r.outbound +} + +func (r *abstractLogicalRule) String() string { + var op string + switch r.mode { + case C.LogicalTypeAnd: + op = "&&" + case C.LogicalTypeOr: + op = "||" + } + if !r.invert { + return strings.Join(F.MapToString(r.rules), " "+op+" ") + } else { + return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")" + } +} diff --git a/route/rule.go b/route/rule_default.go similarity index 61% rename from route/rule.go rename to route/rule_default.go index 6f2f3baa..01322c13 100644 --- a/route/rule.go +++ b/route/rule_default.go @@ -1,16 +1,11 @@ package route import ( - "strings" - "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - N "github.com/sagernet/sing/common/network" ) func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) { @@ -39,14 +34,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul var _ adapter.Rule = (*DefaultRule)(nil) type DefaultRule struct { - items []RuleItem - sourceAddressItems []RuleItem - sourcePortItems []RuleItem - destinationAddressItems []RuleItem - destinationPortItems []RuleItem - allItems []RuleItem - invert bool - outbound string + abstractDefaultRule } type RuleItem interface { @@ -56,8 +44,10 @@ type RuleItem interface { func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) { rule := &DefaultRule{ - invert: options.Invert, - outbound: options.Outbound, + abstractDefaultRule{ + invert: options.Invert, + outbound: options.Outbound, + }, } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -74,15 +64,10 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt return nil, E.New("invalid ip version: ", options.IPVersion) } } - if options.Network != "" { - switch options.Network { - case N.NetworkTCP, N.NetworkUDP: - item := NewNetworkItem(options.Network) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - default: - return nil, E.New("invalid network: ", options.Network) - } + if len(options.Network) > 0 { + item := NewNetworkItem(options.Network) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) } if len(options.AuthUser) > 0 { item := NewAuthUserItem(options.AuthUser) @@ -202,130 +187,19 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt return rule, nil } -func (r *DefaultRule) Type() string { - return C.RuleTypeDefault -} - -func (r *DefaultRule) Start() error { - for _, item := range r.allItems { - err := common.Start(item) - if err != nil { - return err - } - } - return nil -} - -func (r *DefaultRule) Close() error { - for _, item := range r.allItems { - err := common.Close(item) - if err != nil { - return err - } - } - return nil -} - -func (r *DefaultRule) UpdateGeosite() error { - for _, item := range r.allItems { - if geositeItem, isSite := item.(*GeositeItem); isSite { - err := geositeItem.Update() - if err != nil { - return err - } - } - } - return nil -} - -func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool { - for _, item := range r.items { - if !item.Match(metadata) { - return r.invert - } - } - - if len(r.sourceAddressItems) > 0 { - var sourceAddressMatch bool - for _, item := range r.sourceAddressItems { - if item.Match(metadata) { - sourceAddressMatch = true - break - } - } - if !sourceAddressMatch { - return r.invert - } - } - - if len(r.sourcePortItems) > 0 { - var sourcePortMatch bool - for _, item := range r.sourcePortItems { - if item.Match(metadata) { - sourcePortMatch = true - break - } - } - if !sourcePortMatch { - return r.invert - } - } - - if len(r.destinationAddressItems) > 0 { - var destinationAddressMatch bool - for _, item := range r.destinationAddressItems { - if item.Match(metadata) { - destinationAddressMatch = true - break - } - } - if !destinationAddressMatch { - return r.invert - } - } - - if len(r.destinationPortItems) > 0 { - var destinationPortMatch bool - for _, item := range r.destinationPortItems { - if item.Match(metadata) { - destinationPortMatch = true - break - } - } - if !destinationPortMatch { - return r.invert - } - } - - return !r.invert -} - -func (r *DefaultRule) Outbound() string { - return r.outbound -} - -func (r *DefaultRule) String() string { - if !r.invert { - return strings.Join(F.MapToString(r.allItems), " ") - } else { - return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")" - } -} - var _ adapter.Rule = (*LogicalRule)(nil) type LogicalRule struct { - mode string - rules []*DefaultRule - invert bool - outbound string + abstractLogicalRule } func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) { r := &LogicalRule{ - rules: make([]*DefaultRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Outbound, + abstractLogicalRule{ + rules: make([]adapter.Rule, len(options.Rules)), + invert: options.Invert, + outbound: options.Outbound, + }, } switch options.Mode { case C.LogicalTypeAnd: @@ -344,68 +218,3 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt } return r, nil } - -func (r *LogicalRule) Type() string { - return C.RuleTypeLogical -} - -func (r *LogicalRule) UpdateGeosite() error { - for _, rule := range r.rules { - err := rule.UpdateGeosite() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalRule) Start() error { - for _, rule := range r.rules { - err := rule.Start() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalRule) Close() error { - for _, rule := range r.rules { - err := rule.Close() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalRule) Match(metadata *adapter.InboundContext) bool { - if r.mode == C.LogicalTypeAnd { - return common.All(r.rules, func(it *DefaultRule) bool { - return it.Match(metadata) - }) != r.invert - } else { - return common.Any(r.rules, func(it *DefaultRule) bool { - return it.Match(metadata) - }) != r.invert - } -} - -func (r *LogicalRule) Outbound() string { - return r.outbound -} - -func (r *LogicalRule) String() string { - var op string - switch r.mode { - case C.LogicalTypeAnd: - op = "&&" - case C.LogicalTypeOr: - op = "||" - } - if !r.invert { - return strings.Join(F.MapToString(r.rules), " "+op+" ") - } else { - return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")" - } -} diff --git a/route/rule_dns.go b/route/rule_dns.go index 3bfdb729..15e4b16f 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -1,16 +1,11 @@ package route import ( - "strings" - "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - N "github.com/sagernet/sing/common/network" ) func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) { @@ -39,22 +34,19 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option. var _ adapter.DNSRule = (*DefaultDNSRule)(nil) type DefaultDNSRule struct { - items []RuleItem - sourceAddressItems []RuleItem - sourcePortItems []RuleItem - destinationAddressItems []RuleItem - destinationPortItems []RuleItem - allItems []RuleItem - invert bool - outbound string - disableCache bool + abstractDefaultRule + disableCache bool + rewriteTTL *uint32 } func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { rule := &DefaultDNSRule{ - invert: options.Invert, - outbound: options.Server, + abstractDefaultRule: abstractDefaultRule{ + invert: options.Invert, + outbound: options.Server, + }, disableCache: options.DisableCache, + rewriteTTL: options.RewriteTTL, } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -76,15 +68,10 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } - if options.Network != "" { - switch options.Network { - case N.NetworkTCP, N.NetworkUDP: - item := NewNetworkItem(options.Network) - rule.items = append(rule.items, item) - rule.allItems = append(rule.allItems, item) - default: - return nil, E.New("invalid network: ", options.Network) - } + if len(options.Network) > 0 { + item := NewNetworkItem(options.Network) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) } if len(options.AuthUser) > 0 { item := NewAuthUserItem(options.AuthUser) @@ -196,132 +183,31 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options return rule, nil } -func (r *DefaultDNSRule) Type() string { - return C.RuleTypeDefault -} - -func (r *DefaultDNSRule) Start() error { - for _, item := range r.allItems { - err := common.Start(item) - if err != nil { - return err - } - } - return nil -} - -func (r *DefaultDNSRule) Close() error { - for _, item := range r.allItems { - err := common.Close(item) - if err != nil { - return err - } - } - return nil -} - -func (r *DefaultDNSRule) UpdateGeosite() error { - for _, item := range r.allItems { - if geositeItem, isSite := item.(*GeositeItem); isSite { - err := geositeItem.Update() - if err != nil { - return err - } - } - } - return nil -} - -func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool { - for _, item := range r.items { - if !item.Match(metadata) { - return r.invert - } - } - - if len(r.sourceAddressItems) > 0 { - var sourceAddressMatch bool - for _, item := range r.sourceAddressItems { - if item.Match(metadata) { - sourceAddressMatch = true - break - } - } - if !sourceAddressMatch { - return r.invert - } - } - - if len(r.sourcePortItems) > 0 { - var sourcePortMatch bool - for _, item := range r.sourcePortItems { - if item.Match(metadata) { - sourcePortMatch = true - break - } - } - if !sourcePortMatch { - return r.invert - } - } - - if len(r.destinationAddressItems) > 0 { - var destinationAddressMatch bool - for _, item := range r.destinationAddressItems { - if item.Match(metadata) { - destinationAddressMatch = true - break - } - } - if !destinationAddressMatch { - return r.invert - } - } - - if len(r.destinationPortItems) > 0 { - var destinationPortMatch bool - for _, item := range r.destinationPortItems { - if item.Match(metadata) { - destinationPortMatch = true - break - } - } - if !destinationPortMatch { - return r.invert - } - } - - return !r.invert -} - -func (r *DefaultDNSRule) Outbound() string { - return r.outbound -} - func (r *DefaultDNSRule) DisableCache() bool { return r.disableCache } -func (r *DefaultDNSRule) String() string { - return strings.Join(F.MapToString(r.allItems), " ") +func (r *DefaultDNSRule) RewriteTTL() *uint32 { + return r.rewriteTTL } var _ adapter.DNSRule = (*LogicalDNSRule)(nil) type LogicalDNSRule struct { - mode string - rules []*DefaultDNSRule - invert bool - outbound string + abstractLogicalRule disableCache bool + rewriteTTL *uint32 } func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { r := &LogicalDNSRule{ - rules: make([]*DefaultDNSRule, len(options.Rules)), - invert: options.Invert, - outbound: options.Server, + abstractLogicalRule: abstractLogicalRule{ + rules: make([]adapter.Rule, len(options.Rules)), + invert: options.Invert, + outbound: options.Server, + }, disableCache: options.DisableCache, + rewriteTTL: options.RewriteTTL, } switch options.Mode { case C.LogicalTypeAnd: @@ -341,71 +227,10 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options return r, nil } -func (r *LogicalDNSRule) Type() string { - return C.RuleTypeLogical -} - -func (r *LogicalDNSRule) UpdateGeosite() error { - for _, rule := range r.rules { - err := rule.UpdateGeosite() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalDNSRule) Start() error { - for _, rule := range r.rules { - err := rule.Start() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalDNSRule) Close() error { - for _, rule := range r.rules { - err := rule.Close() - if err != nil { - return err - } - } - return nil -} - -func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool { - if r.mode == C.LogicalTypeAnd { - return common.All(r.rules, func(it *DefaultDNSRule) bool { - return it.Match(metadata) - }) != r.invert - } else { - return common.Any(r.rules, func(it *DefaultDNSRule) bool { - return it.Match(metadata) - }) != r.invert - } -} - -func (r *LogicalDNSRule) Outbound() string { - return r.outbound -} - func (r *LogicalDNSRule) DisableCache() bool { return r.disableCache } -func (r *LogicalDNSRule) String() string { - var op string - switch r.mode { - case C.LogicalTypeAnd: - op = "&&" - case C.LogicalTypeOr: - op = "||" - } - if !r.invert { - return strings.Join(F.MapToString(r.rules), " "+op+" ") - } else { - return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")" - } +func (r *LogicalDNSRule) RewriteTTL() *uint32 { + return r.rewriteTTL } diff --git a/route/rule_auth_user.go b/route/rule_item_auth_user.go similarity index 100% rename from route/rule_auth_user.go rename to route/rule_item_auth_user.go diff --git a/route/rule_cidr.go b/route/rule_item_cidr.go similarity index 100% rename from route/rule_cidr.go rename to route/rule_item_cidr.go diff --git a/route/rule_clash_mode.go b/route/rule_item_clash_mode.go similarity index 100% rename from route/rule_clash_mode.go rename to route/rule_item_clash_mode.go diff --git a/route/rule_domain.go b/route/rule_item_domain.go similarity index 100% rename from route/rule_domain.go rename to route/rule_item_domain.go diff --git a/route/rule_domain_keyword.go b/route/rule_item_domain_keyword.go similarity index 100% rename from route/rule_domain_keyword.go rename to route/rule_item_domain_keyword.go diff --git a/route/rule_domain_regex.go b/route/rule_item_domain_regex.go similarity index 100% rename from route/rule_domain_regex.go rename to route/rule_item_domain_regex.go diff --git a/route/rule_geoip.go b/route/rule_item_geoip.go similarity index 100% rename from route/rule_geoip.go rename to route/rule_item_geoip.go diff --git a/route/rule_geosite.go b/route/rule_item_geosite.go similarity index 100% rename from route/rule_geosite.go rename to route/rule_item_geosite.go diff --git a/route/rule_inbound.go b/route/rule_item_inbound.go similarity index 100% rename from route/rule_inbound.go rename to route/rule_item_inbound.go diff --git a/route/rule_ipversion.go b/route/rule_item_ipversion.go similarity index 100% rename from route/rule_ipversion.go rename to route/rule_item_ipversion.go diff --git a/route/rule_item_network.go b/route/rule_item_network.go new file mode 100644 index 00000000..fc54f425 --- /dev/null +++ b/route/rule_item_network.go @@ -0,0 +1,42 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*NetworkItem)(nil) + +type NetworkItem struct { + networks []string + networkMap map[string]bool +} + +func NewNetworkItem(networks []string) *NetworkItem { + networkMap := make(map[string]bool) + for _, network := range networks { + networkMap[network] = true + } + return &NetworkItem{ + networks: networks, + networkMap: networkMap, + } +} + +func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool { + return r.networkMap[metadata.Network] +} + +func (r *NetworkItem) String() string { + description := "network=" + + pLen := len(r.networks) + if pLen == 1 { + description += F.ToString(r.networks[0]) + } else { + description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]" + } + return description +} diff --git a/route/rule_outbound.go b/route/rule_item_outbound.go similarity index 100% rename from route/rule_outbound.go rename to route/rule_item_outbound.go diff --git a/route/rule_package_name.go b/route/rule_item_package_name.go similarity index 100% rename from route/rule_package_name.go rename to route/rule_item_package_name.go diff --git a/route/rule_port.go b/route/rule_item_port.go similarity index 100% rename from route/rule_port.go rename to route/rule_item_port.go diff --git a/route/rule_port_range.go b/route/rule_item_port_range.go similarity index 100% rename from route/rule_port_range.go rename to route/rule_item_port_range.go diff --git a/route/rule_process_name.go b/route/rule_item_process_name.go similarity index 100% rename from route/rule_process_name.go rename to route/rule_item_process_name.go diff --git a/route/rule_process_path.go b/route/rule_item_process_path.go similarity index 100% rename from route/rule_process_path.go rename to route/rule_item_process_path.go diff --git a/route/rule_protocol.go b/route/rule_item_protocol.go similarity index 100% rename from route/rule_protocol.go rename to route/rule_item_protocol.go diff --git a/route/rule_query_type.go b/route/rule_item_query_type.go similarity index 100% rename from route/rule_query_type.go rename to route/rule_item_query_type.go diff --git a/route/rule_user.go b/route/rule_item_user.go similarity index 100% rename from route/rule_user.go rename to route/rule_item_user.go diff --git a/route/rule_user_id.go b/route/rule_item_user_id.go similarity index 100% rename from route/rule_user_id.go rename to route/rule_item_user_id.go diff --git a/route/rule_network.go b/route/rule_network.go deleted file mode 100644 index 0346cb13..00000000 --- a/route/rule_network.go +++ /dev/null @@ -1,23 +0,0 @@ -package route - -import ( - "github.com/sagernet/sing-box/adapter" -) - -var _ RuleItem = (*NetworkItem)(nil) - -type NetworkItem struct { - network string -} - -func NewNetworkItem(network string) *NetworkItem { - return &NetworkItem{network} -} - -func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool { - return r.network == metadata.Network -} - -func (r *NetworkItem) String() string { - return "network=" + r.network -}