mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-03 20:07:36 +03:00
Add resolver for outbound dialer
This commit is contained in:
parent
ecac383477
commit
538a1f5909
32 changed files with 1058 additions and 222 deletions
190
route/router.go
190
route/router.go
|
@ -5,8 +5,10 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
|
@ -19,19 +21,24 @@ import (
|
|||
"github.com/sagernet/sing/common/rw"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-box/common/geosite"
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
)
|
||||
|
||||
var _ adapter.Router = (*Router)(nil)
|
||||
|
||||
type Router struct {
|
||||
ctx context.Context
|
||||
logger log.Logger
|
||||
ctx context.Context
|
||||
logger log.Logger
|
||||
dnsLogger log.Logger
|
||||
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
rules []adapter.Rule
|
||||
|
@ -46,26 +53,116 @@ type Router struct {
|
|||
geositeOptions option.GeositeOptions
|
||||
geoIPReader *geoip.Reader
|
||||
geositeReader *geosite.Reader
|
||||
|
||||
dnsClient adapter.DNSClient
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
|
||||
defaultTransport adapter.DNSTransport
|
||||
transports []adapter.DNSTransport
|
||||
transportMap map[string]adapter.DNSTransport
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptions) (*Router, error) {
|
||||
func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
|
||||
router := &Router{
|
||||
ctx: ctx,
|
||||
logger: logger.WithPrefix("router: "),
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||
needGeoIPDatabase: hasGeoRule(options.Rules, isGeoIPRule),
|
||||
needGeositeDatabase: hasGeoRule(options.Rules, isGeositeRule),
|
||||
geoIPOptions: common.PtrValueOrDefault(options.GeoIP),
|
||||
defaultDetour: options.DefaultDetour,
|
||||
ctx: ctx,
|
||||
logger: logger.WithPrefix("router: "),
|
||||
dnsLogger: logger.WithPrefix("dns: "),
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||
needGeoIPDatabase: hasGeoRule(options.Rules, isGeoIPRule) || hasGeoDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||
needGeositeDatabase: hasGeoRule(options.Rules, isGeositeRule) || hasGeoDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||
geoIPOptions: common.PtrValueOrDefault(options.GeoIP),
|
||||
defaultDetour: options.Final,
|
||||
dnsClient: dns.NewClient(dnsOptions.DNSClientOptions),
|
||||
defaultDomainStrategy: C.DomainStrategy(dnsOptions.Strategy),
|
||||
}
|
||||
for i, ruleOptions := range options.Rules {
|
||||
rule, err := NewRule(router, logger, ruleOptions)
|
||||
routeRule, err := NewRule(router, logger, ruleOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse rule[", i, "]")
|
||||
}
|
||||
router.rules = append(router.rules, rule)
|
||||
router.rules = append(router.rules, routeRule)
|
||||
}
|
||||
for i, dnsRuleOptions := range dnsOptions.Rules {
|
||||
dnsRule, err := NewDNSRule(router, logger, dnsRuleOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
router.rules = append(router.rules, dnsRule)
|
||||
}
|
||||
transports := make([]adapter.DNSTransport, len(dnsOptions.Servers))
|
||||
dummyTransportMap := make(map[string]adapter.DNSTransport)
|
||||
transportMap := make(map[string]adapter.DNSTransport)
|
||||
transportTags := make([]string, len(dnsOptions.Servers))
|
||||
transportTagMap := make(map[string]bool)
|
||||
for i, server := range dnsOptions.Servers {
|
||||
var tag string
|
||||
if server.Tag != "" {
|
||||
tag = server.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
transportTags[i] = tag
|
||||
transportTagMap[tag] = true
|
||||
}
|
||||
for {
|
||||
lastLen := len(dummyTransportMap)
|
||||
for i, server := range dnsOptions.Servers {
|
||||
tag := transportTags[i]
|
||||
if _, exists := dummyTransportMap[tag]; exists {
|
||||
continue
|
||||
}
|
||||
detour := dialer.New(router, server.DialerOptions)
|
||||
if server.AddressResolver != "" {
|
||||
if !transportTagMap[server.AddressResolver] {
|
||||
return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver)
|
||||
}
|
||||
if upstream, exists := dummyTransportMap[server.AddressResolver]; exists {
|
||||
detour = dns.NewDialerWrapper(detour, C.DomainStrategy(server.AddressStrategy), router.dnsClient, upstream)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
transport, err := dns.NewTransport(ctx, detour, logger, server.Address)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse dns server[", tag, "]")
|
||||
}
|
||||
transports[i] = transport
|
||||
dummyTransportMap[tag] = transport
|
||||
if server.Tag != "" {
|
||||
transportMap[server.Tag] = transport
|
||||
}
|
||||
}
|
||||
if len(transports) == len(dummyTransportMap) {
|
||||
break
|
||||
}
|
||||
if lastLen != len(dummyTransportMap) {
|
||||
continue
|
||||
}
|
||||
unresolvedTags := common.MapIndexed(common.FilterIndexed(dnsOptions.Servers, func(index int, server option.DNSServerOptions) bool {
|
||||
_, exists := dummyTransportMap[transportTags[index]]
|
||||
return !exists
|
||||
}), func(index int, server option.DNSServerOptions) string {
|
||||
return transportTags[index]
|
||||
})
|
||||
return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
|
||||
}
|
||||
var defaultTransport adapter.DNSTransport
|
||||
if options.Final != "" {
|
||||
defaultTransport = dummyTransportMap[options.Final]
|
||||
if defaultTransport == nil {
|
||||
return nil, E.New("default dns server not found: ", options.Final)
|
||||
}
|
||||
}
|
||||
if defaultTransport == nil {
|
||||
if len(transports) == 0 {
|
||||
transports = append(transports, dns.NewLocalTransport())
|
||||
}
|
||||
defaultTransport = transports[0]
|
||||
}
|
||||
router.defaultTransport = defaultTransport
|
||||
router.transports = transports
|
||||
router.transportMap = transportMap
|
||||
return router, nil
|
||||
}
|
||||
|
||||
|
@ -135,6 +232,11 @@ func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func()
|
|||
r.defaultOutboundForConnection = defaultOutboundForConnection
|
||||
r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection
|
||||
r.outboundByTag = outboundByTag
|
||||
for i, rule := range r.rules {
|
||||
if _, loaded := outboundByTag[rule.Outbound()]; !loaded {
|
||||
return E.New("outbound not found for rule[", i, "]: ", rule.Outbound())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -228,7 +330,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||
conn.Close()
|
||||
return E.New("missing supported outbound, closing connection")
|
||||
}
|
||||
return detour.NewConnection(ctx, conn, metadata.Destination)
|
||||
return detour.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Destination)
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
|
@ -262,7 +364,19 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||
conn.Close()
|
||||
return E.New("missing supported outbound, closing packet connection")
|
||||
}
|
||||
return detour.NewPacketConnection(ctx, conn, metadata.Destination)
|
||||
return detour.NewPacketConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Destination)
|
||||
}
|
||||
|
||||
func (r *Router) Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error) {
|
||||
return r.dnsClient.Exchange(ctx, r.matchDNS(ctx), message)
|
||||
}
|
||||
|
||||
func (r *Router) Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error) {
|
||||
return r.dnsClient.Lookup(ctx, r.matchDNS(ctx), domain, strategy)
|
||||
}
|
||||
|
||||
func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
|
||||
return r.dnsClient.Lookup(ctx, r.matchDNS(ctx), domain, r.defaultDomainStrategy)
|
||||
}
|
||||
|
||||
func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, defaultOutbound adapter.Outbound) adapter.Outbound {
|
||||
|
@ -280,6 +394,26 @@ func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, def
|
|||
return defaultOutbound
|
||||
}
|
||||
|
||||
func (r *Router) matchDNS(ctx context.Context) adapter.DNSTransport {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
r.dnsLogger.WithContext(ctx).Info("no context")
|
||||
return r.defaultTransport
|
||||
}
|
||||
for i, rule := range r.rules {
|
||||
if rule.Match(metadata) {
|
||||
detour := rule.Outbound()
|
||||
r.dnsLogger.WithContext(ctx).Info("match[", i, "] ", rule.String(), " => ", detour)
|
||||
if transport, loaded := r.transportMap[detour]; loaded {
|
||||
return transport
|
||||
}
|
||||
r.dnsLogger.WithContext(ctx).Error("transport not found: ", detour)
|
||||
}
|
||||
}
|
||||
r.dnsLogger.WithContext(ctx).Info("no match")
|
||||
return r.defaultTransport
|
||||
}
|
||||
|
||||
func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||
for _, rule := range rules {
|
||||
switch rule.Type {
|
||||
|
@ -298,14 +432,40 @@ func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bo
|
|||
return false
|
||||
}
|
||||
|
||||
func hasGeoDNSRule(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 notPrivateNode(code string) bool {
|
||||
return code != "private"
|
||||
}
|
||||
|
|
|
@ -225,3 +225,91 @@ func (r *DefaultRule) Outbound() string {
|
|||
func (r *DefaultRule) String() string {
|
||||
return strings.Join(common.Map(r.allItems, F.ToString0[RuleItem]), " ")
|
||||
}
|
||||
|
||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||
|
||||
type LogicalRule struct {
|
||||
mode string
|
||||
rules []*DefaultRule
|
||||
outbound string
|
||||
}
|
||||
|
||||
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 NewLogicalRule(router adapter.Router, logger log.Logger, options option.LogicalRule) (*LogicalRule, error) {
|
||||
r := &LogicalRule{
|
||||
rules: make([]*DefaultRule, len(options.Rules)),
|
||||
outbound: options.Outbound,
|
||||
}
|
||||
switch options.Mode {
|
||||
case C.LogicalTypeAnd:
|
||||
r.mode = C.LogicalTypeAnd
|
||||
case C.LogicalTypeOr:
|
||||
r.mode = C.LogicalTypeOr
|
||||
default:
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subRule := range options.Rules {
|
||||
rule, err := NewDefaultRule(router, logger, subRule)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
r.rules[i] = rule
|
||||
}
|
||||
return r, 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)
|
||||
})
|
||||
} else {
|
||||
return common.Any(r.rules, func(it *DefaultRule) bool {
|
||||
return it.Match(metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 = "||"
|
||||
}
|
||||
return "logical(" + strings.Join(common.Map(r.rules, F.ToString0[*DefaultRule]), " "+op+" ") + ")"
|
||||
}
|
||||
|
|
250
route/rule_dns.go
Normal file
250
route/rule_dns.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewDNSRule(router adapter.Router, logger log.Logger, options option.DNSRule) (adapter.Rule, error) {
|
||||
if common.IsEmptyByEquals(options) {
|
||||
return nil, E.New("empty rule config")
|
||||
}
|
||||
switch options.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
if !options.DefaultOptions.IsValid() {
|
||||
return nil, E.New("missing conditions")
|
||||
}
|
||||
if options.DefaultOptions.Server == "" {
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
return NewDefaultDNSRule(router, logger, options.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
if !options.LogicalOptions.IsValid() {
|
||||
return nil, E.New("missing conditions")
|
||||
}
|
||||
if options.LogicalOptions.Server == "" {
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
return NewLogicalDNSRule(router, logger, options.LogicalOptions)
|
||||
default:
|
||||
return nil, E.New("unknown rule type: ", options.Type)
|
||||
}
|
||||
}
|
||||
|
||||
var _ adapter.Rule = (*DefaultDNSRule)(nil)
|
||||
|
||||
type DefaultDNSRule struct {
|
||||
items []RuleItem
|
||||
outbound string
|
||||
}
|
||||
|
||||
func NewDefaultDNSRule(router adapter.Router, logger log.Logger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||
rule := &DefaultDNSRule{
|
||||
outbound: options.Server,
|
||||
}
|
||||
if len(options.Inbound) > 0 {
|
||||
item := NewInboundRule(options.Inbound)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if options.Network != "" {
|
||||
switch options.Network {
|
||||
case C.NetworkTCP, C.NetworkUDP:
|
||||
item := NewNetworkItem(options.Network)
|
||||
rule.items = append(rule.items, item)
|
||||
default:
|
||||
return nil, E.New("invalid network: ", options.Network)
|
||||
}
|
||||
}
|
||||
if len(options.Protocol) > 0 {
|
||||
item := NewProtocolItem(options.Protocol)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
|
||||
item := NewDomainItem(options.Domain, options.DomainSuffix)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.DomainKeyword) > 0 {
|
||||
item := NewDomainKeywordItem(options.DomainKeyword)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.DomainRegex) > 0 {
|
||||
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "domain_regex")
|
||||
}
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.Geosite) > 0 {
|
||||
item := NewGeositeItem(router, logger, options.Geosite)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.SourceGeoIP) > 0 {
|
||||
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.SourceIPCIDR) > 0 {
|
||||
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "source_ipcidr")
|
||||
}
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.SourcePort) > 0 {
|
||||
item := NewPortItem(true, options.SourcePort)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.Port) > 0 {
|
||||
item := NewPortItem(false, options.Port)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
if len(options.Outbound) > 0 {
|
||||
item := NewOutboundRule(options.Outbound)
|
||||
rule.items = append(rule.items, item)
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Start() error {
|
||||
for _, item := range r.items {
|
||||
err := common.Start(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Close() error {
|
||||
for _, item := range r.items {
|
||||
err := common.Close(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) UpdateGeosite() error {
|
||||
for _, item := range r.items {
|
||||
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 false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Outbound() string {
|
||||
return r.outbound
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) String() string {
|
||||
return strings.Join(common.Map(r.items, F.ToString0[RuleItem]), " ")
|
||||
}
|
||||
|
||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||
|
||||
type LogicalDNSRule struct {
|
||||
mode string
|
||||
rules []*DefaultDNSRule
|
||||
outbound string
|
||||
}
|
||||
|
||||
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 NewLogicalDNSRule(router adapter.Router, logger log.Logger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||
r := &LogicalDNSRule{
|
||||
rules: make([]*DefaultDNSRule, len(options.Rules)),
|
||||
outbound: options.Server,
|
||||
}
|
||||
switch options.Mode {
|
||||
case C.LogicalTypeAnd:
|
||||
r.mode = C.LogicalTypeAnd
|
||||
case C.LogicalTypeOr:
|
||||
r.mode = C.LogicalTypeOr
|
||||
default:
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subRule := range options.Rules {
|
||||
rule, err := NewDefaultDNSRule(router, logger, subRule)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
r.rules[i] = rule
|
||||
}
|
||||
return r, 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)
|
||||
})
|
||||
} else {
|
||||
return common.Any(r.rules, func(it *DefaultDNSRule) bool {
|
||||
return it.Match(metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) Outbound() string {
|
||||
return r.outbound
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) String() string {
|
||||
var op string
|
||||
switch r.mode {
|
||||
case C.LogicalTypeAnd:
|
||||
op = "&&"
|
||||
case C.LogicalTypeOr:
|
||||
op = "||"
|
||||
}
|
||||
return "logical(" + strings.Join(common.Map(r.rules, F.ToString0[*DefaultDNSRule]), " "+op+" ") + ")"
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||
|
||||
type LogicalRule struct {
|
||||
mode string
|
||||
rules []*DefaultRule
|
||||
outbound string
|
||||
}
|
||||
|
||||
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 NewLogicalRule(router adapter.Router, logger log.Logger, options option.LogicalRule) (*LogicalRule, error) {
|
||||
r := &LogicalRule{
|
||||
rules: make([]*DefaultRule, len(options.Rules)),
|
||||
outbound: options.Outbound,
|
||||
}
|
||||
switch options.Mode {
|
||||
case C.LogicalTypeAnd:
|
||||
r.mode = C.LogicalTypeAnd
|
||||
case C.LogicalTypeOr:
|
||||
r.mode = C.LogicalTypeOr
|
||||
default:
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subRule := range options.Rules {
|
||||
rule, err := NewDefaultRule(router, logger, subRule)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
r.rules[i] = rule
|
||||
}
|
||||
return r, 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)
|
||||
})
|
||||
} else {
|
||||
return common.Any(r.rules, func(it *DefaultRule) bool {
|
||||
return it.Match(metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 = "||"
|
||||
}
|
||||
return "logical(" + strings.Join(common.Map(r.rules, F.ToString0[*DefaultRule]), " "+op+" ") + ")"
|
||||
}
|
36
route/rule_outbound.go
Normal file
36
route/rule_outbound.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*OutboundItem)(nil)
|
||||
|
||||
type OutboundItem struct {
|
||||
outbounds []string
|
||||
outboundMap map[string]bool
|
||||
}
|
||||
|
||||
func NewOutboundRule(outbounds []string) *OutboundItem {
|
||||
rule := &OutboundItem{outbounds, make(map[string]bool)}
|
||||
for _, outbound := range outbounds {
|
||||
rule.outboundMap[outbound] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.outboundMap[metadata.Outbound]
|
||||
}
|
||||
|
||||
func (r *OutboundItem) String() string {
|
||||
if len(r.outbounds) == 1 {
|
||||
return F.ToString("outbound=", r.outbounds[0])
|
||||
} else {
|
||||
return F.ToString("outbound=[", strings.Join(r.outbounds, " "), "]")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue