Add resolver for outbound dialer

This commit is contained in:
世界 2022-07-07 21:47:21 +08:00
parent ecac383477
commit 538a1f5909
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
32 changed files with 1058 additions and 222 deletions

View file

@ -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"
}