From 7763c7847e2a27cae7232557bd23f492cb710f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 9 Feb 2024 18:37:25 +0800 Subject: [PATCH] Add support for `client-subnet` DNS options --- adapter/router.go | 1 + docs/configuration/dns/index.md | 17 ++- docs/configuration/dns/index.zh.md | 17 +++ docs/configuration/dns/rule.md | 19 ++- docs/configuration/dns/rule.zh.md | 17 ++- docs/configuration/dns/server.md | 34 +++-- docs/configuration/dns/server.zh.md | 32 +++-- docs/manual/proxy/client.md | 187 ++++++++++++++++++---------- experimental/libbox/dns.go | 8 +- include/dhcp_stub.go | 6 +- include/quic_stub.go | 3 +- option/dns.go | 2 + option/rule_dns.go | 15 ++- route/router.go | 22 +++- route/router_dns.go | 3 + route/rule_dns.go | 14 +++ transport/dhcp/server.go | 46 ++++--- transport/fakeip/server.go | 13 +- 18 files changed, 316 insertions(+), 140 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index b5eceb1f..0d771dee 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -86,6 +86,7 @@ type DNSRule interface { Rule DisableCache() bool RewriteTTL() *uint32 + ClientSubnet() *netip.Addr WithAddressLimit() bool MatchAddressLimit(metadata *InboundContext) bool } diff --git a/docs/configuration/dns/index.md b/docs/configuration/dns/index.md index cfb6bc6b..71219dbb 100644 --- a/docs/configuration/dns/index.md +++ b/docs/configuration/dns/index.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.9.0" + + :material-plus: [client_subnet](#client_subnet) + # DNS ### Structure @@ -13,6 +21,7 @@ "disable_expire": false, "independent_cache": false, "reverse_mapping": false, + "client_subnet": "", "fakeip": {} } } @@ -60,6 +69,10 @@ Stores a reverse mapping of IP addresses after responding to a DNS query in orde Since this process relies on the act of resolving domain names by an application before making a request, it can be problematic in environments such as macOS, where DNS is proxied and cached by the system. -#### fakeip +#### client_subnet -[FakeIP](./fakeip/) settings. +!!! question "Since sing-box 1.9.0" + +Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. + +Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`. diff --git a/docs/configuration/dns/index.zh.md b/docs/configuration/dns/index.zh.md index afc6e931..164c37cd 100644 --- a/docs/configuration/dns/index.zh.md +++ b/docs/configuration/dns/index.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.9.0 中的更改" + + :material-plus: [client_subnet](#client_subnet) + # DNS ### 结构 @@ -13,6 +21,7 @@ "disable_expire": false, "independent_cache": false, "reverse_mapping": false, + "client_subnet": "", "fakeip": {} } } @@ -58,6 +67,14 @@ 由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。 +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。 + #### fakeip [FakeIP](./fakeip/) 设置。 diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 26b86d95..5b42f20c 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) - :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [client_subnet](#client_subnet) !!! quote "Changes in sing-box 1.8.0" @@ -121,7 +122,8 @@ icon: material/new-box ], "server": "local", "disable_cache": false, - "rewrite_ttl": 100 + "rewrite_ttl": 100, + "client_subnet": "127.0.0.1" }, { "type": "logical", @@ -129,7 +131,8 @@ icon: material/new-box "rules": [], "server": "local", "disable_cache": false, - "rewrite_ttl": 100 + "rewrite_ttl": 100, + "client_subnet": "127.0.0.1" } ] } @@ -280,8 +283,6 @@ Match Clash mode. #### wifi_ssid - - !!! quote "" Only supported in graphical clients on Android and iOS. @@ -326,6 +327,14 @@ Disable cache and save cache in this query. Rewrite TTL in DNS responses. +#### client_subnet + +!!! question "Since sing-box 1.9.0" + +Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. + +Will overrides `dns.client_subnet` and `servers.[].client_subnet`. + ### Address Filter Fields Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index ebc81c0f..eaeb8e68 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [geoip](#geoip) :material-plus: [ip_cidr](#ip_cidr) - :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [ip_is_private](#ip_is_private) + :material-plus: [client_subnet](#client_subnet) !!! quote "sing-box 1.8.0 中的更改" @@ -120,14 +121,16 @@ icon: material/new-box "direct" ], "server": "local", - "disable_cache": false + "disable_cache": false, + "client_subnet": "127.0.0.1" }, { "type": "logical", "mode": "and", "rules": [], "server": "local", - "disable_cache": false + "disable_cache": false, + "client_subnet": "127.0.0.1" } ] } @@ -322,6 +325,14 @@ DNS 查询类型。值可以为整数或者类型名称字符串。 重写 DNS 回应中的 TTL。 +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。 + ### 地址筛选字段 仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。 diff --git a/docs/configuration/dns/server.md b/docs/configuration/dns/server.md index 545810bf..e4d93544 100644 --- a/docs/configuration/dns/server.md +++ b/docs/configuration/dns/server.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.9.0" + + :material-plus: [client_subnet](#client_subnet) + ### Structure ```json @@ -5,17 +13,17 @@ "dns": { "servers": [ { - "tag": "google", - "address": "tls://dns.google", - "address_resolver": "local", - "address_strategy": "prefer_ipv4", - "strategy": "ipv4_only", - "detour": "direct" + "tag": "", + "address": "", + "address_resolver": "", + "address_strategy": "", + "strategy": "", + "detour": "", + "client_subnet": "" } ] } } - ``` ### Fields @@ -80,10 +88,20 @@ Default domain strategy for resolving the domain names. One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`. -Take no effect if override by other settings. +Take no effect if overridden by other settings. #### detour Tag of an outbound for connecting to the dns server. Default outbound will be used if empty. + +#### client_subnet + +!!! question "Since sing-box 1.9.0" + +Append a `edns0-subnet` OPT extra record with the specified IP address to every query by default. + +Can be overrides by `rules.[].client_subnet`. + +Will overrides `dns.client_subnet`. diff --git a/docs/configuration/dns/server.zh.md b/docs/configuration/dns/server.zh.md index 36bcde5d..a15fdfd3 100644 --- a/docs/configuration/dns/server.zh.md +++ b/docs/configuration/dns/server.zh.md @@ -1,3 +1,11 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.9.0 中的更改" + + :material-plus: [client_subnet](#client_subnet) + ### 结构 ```json @@ -5,17 +13,17 @@ "dns": { "servers": [ { - "tag": "google", - "address": "tls://dns.google", - "address_resolver": "local", - "address_strategy": "prefer_ipv4", - "strategy": "ipv4_only", - "detour": "direct" + "tag": "", + "address": "", + "address_resolver": "", + "address_strategy": "", + "strategy": "", + "detour": "", + "client_subnet": "" } ] } } - ``` ### 字段 @@ -87,3 +95,13 @@ DNS 服务器的地址。 用于连接到 DNS 服务器的出站的标签。 如果为空,将使用默认出站。 + +#### client_subnet + +!!! question "自 sing-box 1.9.0 起" + +默认情况下,将带有指定 IP 地址的 `edns0-subnet` OPT 附加记录附加到每个查询。 + +可以被 `rules.[].client_subnet` 覆盖。 + +将覆盖 `dns.client_subnet`。 diff --git a/docs/manual/proxy/client.md b/docs/manual/proxy/client.md index 41755cca..12a83039 100644 --- a/docs/manual/proxy/client.md +++ b/docs/manual/proxy/client.md @@ -338,74 +338,131 @@ flowchart TB === ":material-dns: DNS rules (1.9.0+)" - !!! warning "DNS leaks" - - The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method. - - ```json - { - "dns": { - "servers": [ - { - "tag": "google", - "address": "tls://8.8.8.8" + === ":material-shield-off: With DNS Leaks" + + ```json + { + "dns": { + "servers": [ + { + "tag": "google", + "address": "tls://8.8.8.8" + }, + { + "tag": "local", + "address": "https://223.5.5.5/dns-query", + "detour": "direct" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + }, + { + "clash_mode": "Direct", + "server": "local" + }, + { + "clash_mode": "Global", + "server": "google" + }, + { + "rule_set": "geosite-geolocation-cn", + "server": "local" + }, + { + "clash_mode": "Default", + "server": "google" + }, + { + "rule_set": "geoip-cn", + "server": "local" + } + ] }, - { - "tag": "local", - "address": "https://223.5.5.5/dns-query", - "detour": "direct" + "route": { + "rule_set": [ + { + "type": "remote", + "tag": "geosite-geolocation-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs" + }, + { + "type": "remote", + "tag": "geoip-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" + } + ] + }, + "experimental": { + "clash_api": { + "default_mode": "Leak" + } } - ], - "rules": [ - { - "outbound": "any", - "server": "local" - }, - { - "clash_mode": "Direct", - "server": "local" - }, - { - "clash_mode": "Global", - "server": "google" - }, - { - "rule_set": "geosite-geolocation-cn", - "server": "local" - }, - { - "clash_mode": "Default", - "server": "google" - }, - { - "rule_set": "geoip-cn", - "server": "local" - } - ] - }, - "route": { - "rule_set": [ - { - "type": "remote", - "tag": "geosite-geolocation-cn", - "format": "binary", - "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs" - }, - { - "type": "remote", - "tag": "geoip-cn", - "format": "binary", - "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" - } - ] - }, - "experimental": { - "clash_api": { - "default_mode": "Leak" } - } - } - ``` + ``` + + === ":material-security: Without DNS Leaks (1.9.0-alpha.2+)" + + ```json + { + "dns": { + "servers": [ + { + "tag": "google", + "address": "tls://8.8.8.8" + }, + { + "tag": "local", + "address": "https://223.5.5.5/dns-query", + "detour": "direct" + } + ], + "rules": [ + { + "outbound": "any", + "server": "local" + }, + { + "clash_mode": "Direct", + "server": "local" + }, + { + "clash_mode": "Global", + "server": "google" + }, + { + "rule_set": "geosite-geolocation-cn", + "server": "local" + }, + { + "rule_set": "geoip-cn", + "server": "google", + "client_subnet": "114.114.114.114" // Any China client IP address + } + ] + }, + "route": { + "rule_set": [ + { + "type": "remote", + "tag": "geosite-geolocation-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs" + }, + { + "type": "remote", + "tag": "geoip-cn", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs" + } + ] + } + } + ``` === ":material-router-network: Route rules" diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index fcdaaa92..e1f8bcc3 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -9,9 +9,7 @@ import ( "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" mDNS "github.com/miekg/dns" @@ -25,9 +23,11 @@ type LocalDNSTransport interface { func RegisterLocalDNSTransport(transport LocalDNSTransport) { if transport == nil { - dns.RegisterTransport([]string{"local"}, dns.CreateLocalTransport) + dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { + return dns.NewLocalTransport(options), nil + }) } else { - dns.RegisterTransport([]string{"local"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) { return &platformLocalDNSTransport{ iif: transport, }, nil diff --git a/include/dhcp_stub.go b/include/dhcp_stub.go index c57aa430..47a19d2e 100644 --- a/include/dhcp_stub.go +++ b/include/dhcp_stub.go @@ -3,16 +3,12 @@ package include import ( - "context" - "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" ) func init() { - dns.RegisterTransport([]string{"dhcp"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) }) } diff --git a/include/quic_stub.go b/include/quic_stub.go index 17b502a7..ddf9723f 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -11,13 +11,12 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" - "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) func init() { - dns.RegisterTransport([]string{"quic", "h3"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + dns.RegisterTransport([]string{"quic", "h3"}, func(options dns.TransportOptions) (dns.Transport, error) { return nil, C.ErrQUICNotIncluded }) v2ray.RegisterQUICConstructor( diff --git a/option/dns.go b/option/dns.go index e0d237b7..15201343 100644 --- a/option/dns.go +++ b/option/dns.go @@ -19,6 +19,7 @@ type DNSServerOptions struct { AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"` Detour string `json:"detour,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } type DNSClientOptions struct { @@ -26,6 +27,7 @@ type DNSClientOptions struct { DisableCache bool `json:"disable_cache,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } type DNSFakeIPOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index d148e264..dc5e5c2b 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -100,6 +100,7 @@ type DefaultDNSRule struct { Server string `json:"server,omitempty"` DisableCache bool `json:"disable_cache,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } func (r DefaultDNSRule) IsValid() bool { @@ -108,16 +109,18 @@ func (r DefaultDNSRule) IsValid() bool { defaultValue.Server = r.Server defaultValue.DisableCache = r.DisableCache defaultValue.RewriteTTL = r.RewriteTTL + defaultValue.ClientSubnet = r.ClientSubnet return !reflect.DeepEqual(r, defaultValue) } type LogicalDNSRule struct { - Mode string `json:"mode"` - Rules []DNSRule `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"` + Mode string `json:"mode"` + Rules []DNSRule `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"` + ClientSubnet *ListenAddress `json:"client_subnet,omitempty"` } func (r LogicalDNSRule) IsValid() bool { diff --git a/route/router.go b/route/router.go index f2fe1a6d..ae57fc6b 100644 --- a/route/router.go +++ b/route/router.go @@ -225,7 +225,20 @@ func NewRouter( return nil, E.New("parse dns server[", tag, "]: missing address_resolver") } } - transport, err := dns.CreateTransport(tag, ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address) + var clientSubnet netip.Addr + if server.ClientSubnet != nil { + clientSubnet = server.ClientSubnet.Build() + } else if dnsOptions.ClientSubnet != nil { + clientSubnet = dnsOptions.ClientSubnet.Build() + } + transport, err := dns.CreateTransport(dns.TransportOptions{ + Context: ctx, + Logger: logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), + Name: tag, + Dialer: detour, + Address: server.Address, + ClientSubnet: clientSubnet, + }) if err != nil { return nil, E.Cause(err, "parse dns server[", tag, "]") } @@ -265,7 +278,12 @@ func NewRouter( } if defaultTransport == nil { if len(transports) == 0 { - transports = append(transports, dns.NewLocalTransport("local", N.SystemDialer)) + transports = append(transports, common.Must1(dns.CreateTransport(dns.TransportOptions{ + Context: ctx, + Name: "local", + Address: "local", + Dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{})), + }))) } defaultTransport = transports[0] } diff --git a/route/router_dns.go b/route/router_dns.go index ee767e9e..7114882b 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -70,6 +70,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (con if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil { ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL) } + if clientSubnet := rule.ClientSubnet(); clientSubnet != nil { + ctx = dns.ContextWithClientSubnet(ctx, *clientSubnet) + } if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded { return ctx, transport, domainStrategy, rule, ruleIndex } else { diff --git a/route/rule_dns.go b/route/rule_dns.go index 3eab61f8..760ff910 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -1,6 +1,8 @@ package route import ( + "net/netip" + "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -38,6 +40,7 @@ type DefaultDNSRule struct { abstractDefaultRule disableCache bool rewriteTTL *uint32 + clientSubnet *netip.Addr } func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) { @@ -48,6 +51,7 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, + clientSubnet: (*netip.Addr)(options.ClientSubnet), } if len(options.Inbound) > 0 { item := NewInboundRule(options.Inbound) @@ -230,6 +234,10 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +func (r *DefaultDNSRule) ClientSubnet() *netip.Addr { + return r.clientSubnet +} + func (r *DefaultDNSRule) WithAddressLimit() bool { if len(r.destinationIPCIDRItems) > 0 { return true @@ -264,6 +272,7 @@ type LogicalDNSRule struct { abstractLogicalRule disableCache bool rewriteTTL *uint32 + clientSubnet *netip.Addr } func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) { @@ -275,6 +284,7 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options }, disableCache: options.DisableCache, rewriteTTL: options.RewriteTTL, + clientSubnet: (*netip.Addr)(options.ClientSubnet), } switch options.Mode { case C.LogicalTypeAnd: @@ -302,6 +312,10 @@ func (r *LogicalDNSRule) RewriteTTL() *uint32 { return r.rewriteTTL } +func (r *LogicalDNSRule) ClientSubnet() *netip.Addr { + return r.clientSubnet +} + func (r *LogicalDNSRule) WithAddressLimit() bool { for _, rawRule := range r.rules { switch rule := rawRule.(type) { diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 1a2c2938..2b7346c6 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -21,9 +21,6 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" "github.com/sagernet/sing/common/x/list" @@ -32,14 +29,14 @@ import ( ) func init() { - dns.RegisterTransport([]string{"dhcp"}, NewTransport) + dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { + return NewTransport(options) + }) } type Transport struct { - name string - ctx context.Context + options dns.TransportOptions router adapter.Router - logger logger.Logger interfaceName string autoInterface bool interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] @@ -48,23 +45,20 @@ type Transport struct { updatedAt time.Time } -func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { - linkURL, err := url.Parse(link) +func NewTransport(options dns.TransportOptions) (*Transport, error) { + linkURL, err := url.Parse(options.Address) if err != nil { return nil, err } if linkURL.Host == "" { return nil, E.New("missing interface name for DHCP") } - router := adapter.RouterFromContext(ctx) + router := adapter.RouterFromContext(options.Context) if router == nil { return nil, E.New("missing router in context") } transport := &Transport{ - name: name, - ctx: ctx, router: router, - logger: logger, interfaceName: linkURL.Host, autoInterface: linkURL.Host == "auto", } @@ -72,7 +66,7 @@ func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, } func (t *Transport) Name() string { - return t.name + return t.options.Name } func (t *Transport) Start() error { @@ -158,8 +152,8 @@ func (t *Transport) updateServers() error { return E.Cause(err, "dhcp: prepare interface") } - t.logger.Info("dhcp: query DNS servers on ", iface.Name) - fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) + t.options.Logger.Info("dhcp: query DNS servers on ", iface.Name) + fetchCtx, cancel := context.WithTimeout(t.options.Context, C.DHCPTimeout) err = t.fetchServers0(fetchCtx, iface) cancel() if err != nil { @@ -175,7 +169,7 @@ func (t *Transport) updateServers() error { func (t *Transport) interfaceUpdated(int) { err := t.updateServers() if err != nil { - t.logger.Error("update servers: ", err) + t.options.Logger.Error("update servers: ", err) } } @@ -187,7 +181,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err if runtime.GOOS == "linux" || runtime.GOOS == "android" { listenAddr = "255.255.255.255:68" } - packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr) + packetConn, err := listener.ListenPacket(t.options.Context, "udp4", listenAddr) if err != nil { return err } @@ -225,17 +219,17 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa dhcpPacket, err := dhcpv4.FromBytes(buffer.Bytes()) if err != nil { - t.logger.Trace("dhcp: parse DHCP response: ", err) + t.options.Logger.Trace("dhcp: parse DHCP response: ", err) return err } if dhcpPacket.MessageType() != dhcpv4.MessageTypeOffer { - t.logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) + t.options.Logger.Trace("dhcp: expected OFFER response, but got ", dhcpPacket.MessageType()) continue } if dhcpPacket.TransactionID != transactionID { - t.logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) + t.options.Logger.Trace("dhcp: expected transaction ID ", transactionID, ", but got ", dhcpPacket.TransactionID) continue } @@ -255,20 +249,22 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { if len(serverAddrs) > 0 { - t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { + t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { return it.String() }), ","), "]") } - serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, })) var transports []dns.Transport for _, serverAddr := range serverAddrs { - serverTransport, err := dns.NewUDPTransport(t.name, t.ctx, serverDialer, M.Socksaddr{Addr: serverAddr, Port: 53}) + newOptions := t.options + newOptions.Address = serverAddr.String() + newOptions.Dialer = serverDialer + serverTransport, err := dns.NewUDPTransport(newOptions) if err != nil { - return err + return E.Cause(err, "create UDP transport from DHCP result: ", serverAddr) } transports = append(transports, serverTransport) } diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go index 40149aa4..5e0c7eef 100644 --- a/transport/fakeip/server.go +++ b/transport/fakeip/server.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" mDNS "github.com/miekg/dns" ) @@ -20,7 +19,9 @@ var ( ) func init() { - dns.RegisterTransport([]string{"fakeip"}, NewTransport) + dns.RegisterTransport([]string{"fakeip"}, func(options dns.TransportOptions) (dns.Transport, error) { + return NewTransport(options) + }) } type Transport struct { @@ -30,15 +31,15 @@ type Transport struct { logger logger.ContextLogger } -func NewTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { - router := adapter.RouterFromContext(ctx) +func NewTransport(options dns.TransportOptions) (*Transport, error) { + router := adapter.RouterFromContext(options.Context) if router == nil { return nil, E.New("missing router in context") } return &Transport{ - name: name, + name: options.Name, router: router, - logger: logger, + logger: options.Logger, }, nil }