mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-08-27 03:41:09 +00:00
Allow customizing TUN DNS mode and hijack interface DNS by default
This commit is contained in:
parent
ce360eece8
commit
e8643485d2
15 changed files with 184 additions and 60 deletions
|
|
@ -513,6 +513,7 @@ Match specified DNS servers' preferred domains.
|
|||
| `local` | Match hosts entries, neighbor-resolved hosts, and mDNS local domains |
|
||||
| `mdns` | Match mDNS local domains (`*.local.` and IPv4/IPv6 link-local reverse zones) |
|
||||
| `tailscale` | Match MagicDNS hosts and DNS route suffixes |
|
||||
| `resolved` | Match split DNS and search domains from systemd-resolved links |
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
|
|
|
|||
|
|
@ -505,6 +505,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||
| `local` | 匹配 hosts 中的条目、邻居解析得到的主机名以及 mDNS 本地域名 |
|
||||
| `mdns` | 匹配 mDNS 本地域名(`*.local.` 以及 IPv4/IPv6 链路本地反向区域) |
|
||||
| `tailscale` | 匹配 MagicDNS 主机和 DNS 路由后缀 |
|
||||
| `resolved` | 匹配 systemd-resolved 链路中的分流域名和搜索域 |
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
|
|
|
|||
|
|
@ -89,13 +89,9 @@ Example:
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "hosts",
|
||||
"action": "route",
|
||||
"server": "hosts"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,13 +89,9 @@ hosts 文件路径列表。
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "hosts",
|
||||
"action": "route",
|
||||
"server": "hosts"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,13 +61,9 @@ If not enabled, `NXDOMAIN` will be returned for requests that do not match searc
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "resolved",
|
||||
"action": "route",
|
||||
"server": "resolved"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,13 +60,9 @@ icon: material/new-box
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "resolved",
|
||||
"action": "route",
|
||||
"server": "resolved"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,13 +73,9 @@ Default resolvers are not consulted for single-label queries regardless of `acce
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "ts",
|
||||
"action": "route",
|
||||
"server": "ts"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,13 +73,9 @@ icon: material/new-box
|
|||
],
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"preferred_by": "ts",
|
||||
"action": "route",
|
||||
"server": "ts"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"ip_accept_any": true,
|
||||
"action": "respond"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ icon: material/new-box
|
|||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [include_mac_address](#include_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
:material-plus: [dns_mode](#dns_mode)
|
||||
:material-plus: [dns_address](#dns_address)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.3"
|
||||
|
||||
|
|
@ -73,6 +75,11 @@ icon: material/new-box
|
|||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"dns_mode": "hijack",
|
||||
"dns_address": [
|
||||
"172.18.0.2",
|
||||
"fdfe:dcba:9876::2"
|
||||
],
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
|
|
@ -216,6 +223,52 @@ IPv6 prefix for the tun interface.
|
|||
|
||||
The maximum transmission unit.
|
||||
|
||||
#### dns_mode
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
How DNS is handled on the TUN interface.
|
||||
|
||||
| Mode | Description |
|
||||
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `disabled` | Do not configure native DNS and do not hijack DNS traffic. |
|
||||
| `native` | Set the platform's native interface DNS where possible: per-interface DNS on Windows and Apple platforms, and `systemd-resolved` interface DNS on Linux. |
|
||||
| `hijack` | Same as `native`, with additional port 53 hijacking described below. Used by default. |
|
||||
|
||||
`hijack` adds the following on top of `native`:
|
||||
|
||||
*On Linux*: only DNS sent to non-local destinations can be intercepted.
|
||||
Traffic destined to addresses on the host's own interfaces (such as
|
||||
`127.0.0.53` or the host's LAN-side IP) is delivered through the kernel
|
||||
`local` routing table before any user rule applies, and `OUTPUT` NAT cannot
|
||||
redirect packets going through `lo`.
|
||||
|
||||
- Without `auto_redirect`, an `iproute2` rule makes port 53 skip the `main`
|
||||
table's specific-route lookup, forcing DNS that would otherwise be
|
||||
delivered through a directly-attached subnet through the TUN. Destination
|
||||
addresses are not rewritten.
|
||||
- With `auto_redirect`, an nftables rule DNATs port 53 traffic directly to
|
||||
[`dns_address`](#dns_address).
|
||||
|
||||
*On Windows with [`strict_route`](#strict_route)*: a WFP filter blocks port
|
||||
53 traffic going through interfaces other than the TUN.
|
||||
|
||||
#### dns_address
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
List of DNS server addresses used by [`dns_mode`](#dns_mode).
|
||||
|
||||
When unset, sing-box derives one address per family by taking the next IP after
|
||||
the first IPv4/IPv6 entry in [`address`](#address). Connections toward those
|
||||
derived addresses are additionally hijacked into the sing-box DNS module,
|
||||
equivalent to a [`hijack-dns`](/configuration/route/rule_action/#hijack-dns)
|
||||
route action; this preserves the behaviour from before this option was added.
|
||||
|
||||
When set, this auto-hijack is not applied; configure an explicit
|
||||
[`hijack-dns`](/configuration/route/rule_action/#hijack-dns) route rule if the
|
||||
behaviour is still required.
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ icon: material/new-box
|
|||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [include_mac_address](#include_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||
:material-plus: [dns_mode](#dns_mode)
|
||||
:material-plus: [dns_address](#dns_address)
|
||||
|
||||
!!! quote "sing-box 1.13.3 中的更改"
|
||||
|
||||
|
|
@ -73,6 +75,11 @@ icon: material/new-box
|
|||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"dns_mode": "hijack",
|
||||
"dns_address": [
|
||||
"172.18.0.2",
|
||||
"fdfe:dcba:9876::2"
|
||||
],
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
|
|
@ -222,6 +229,46 @@ tun 接口的 IPv6 前缀。
|
|||
|
||||
最大传输单元。
|
||||
|
||||
#### dns_mode
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
TUN 接口上 DNS 的处理方式。
|
||||
|
||||
| 模式 | 描述 |
|
||||
|------------|-------------------------------------------------------------------------------------------------------|
|
||||
| `disabled` | 不设置原生 DNS,也不劫持 DNS 流量。 |
|
||||
| `native` | 尽可能设置平台的原生接口 DNS:Windows 与 Apple 上的接口 DNS,Linux 上的 `systemd-resolved` 接口 DNS。 |
|
||||
| `hijack` | 与 `native` 相同,并额外执行下文所述的 53 端口劫持。默认使用。 |
|
||||
|
||||
`hijack` 在 `native` 之上额外执行:
|
||||
|
||||
*Linux*:只能劫持发往非本机地址的 DNS。发往本机接口地址(如 `127.0.0.53`
|
||||
或本机 LAN 接口 IP)的流量由内核 `local` 路由表在所有用户规则之前直接交付,
|
||||
`OUTPUT` 链 NAT 也无法对走 `lo` 的包生效。
|
||||
|
||||
- 未启用 `auto_redirect` 时:通过 `iproute2` 规则让 53 端口跳过 `main` 表的
|
||||
具体路由查找,把本来会经直连子网直接送达的 DNS 改走 TUN —— 不重写目的地址。
|
||||
- 启用 `auto_redirect` 时:通过 nftables 规则将 53 端口流量直接 DNAT 至
|
||||
[`dns_address`](#dns_address)。
|
||||
|
||||
*Windows 启用 [`strict_route`](#strict_route) 时*:通过 WFP 过滤器阻止经由非
|
||||
TUN 接口的 53 端口流量。
|
||||
|
||||
#### dns_address
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
[`dns_mode`](#dns_mode) 使用的 DNS 服务器地址列表。
|
||||
|
||||
未设置时,sing-box 会按地址族在 [`address`](#address) 的第一个 IPv4/IPv6
|
||||
条目后面取下一个 IP 作为 DNS 服务器地址,并将流向这些推导地址的连接额外劫持到
|
||||
sing-box DNS 模块,等价于一条
|
||||
[`hijack-dns`](/zh/configuration/route/rule_action/#hijack-dns) 路由动作;这与此选项加入之前的行为一致。
|
||||
|
||||
设置后,将不再自动劫持;如仍需此行为,请显式配置
|
||||
[`hijack-dns`](/zh/configuration/route/rule_action/#hijack-dns) 路由规则。
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
|
|
|||
|
|
@ -7,13 +7,19 @@ import (
|
|||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const (
|
||||
DNSModeDisabled = tun.DNSModeDisabled
|
||||
DNSModeNative = tun.DNSModeNative
|
||||
DNSModeHijack = tun.DNSModeHijack
|
||||
)
|
||||
|
||||
type TunOptions interface {
|
||||
GetInet4Address() RoutePrefixIterator
|
||||
GetInet6Address() RoutePrefixIterator
|
||||
GetDNSServerAddress() (*StringBox, error)
|
||||
GetDNSMode() *StringBox
|
||||
GetDNSServerAddress() (StringIterator, error)
|
||||
GetMTU() int32
|
||||
GetAutoRoute() bool
|
||||
GetStrictRoute() bool
|
||||
|
|
@ -89,11 +95,16 @@ func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
|
|||
return mapRoutePrefix(o.Inet6Address)
|
||||
}
|
||||
|
||||
func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
|
||||
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
||||
return nil, E.New("need one more IPv4 address for DNS hijacking")
|
||||
func (o *tunOptions) GetDNSMode() *StringBox {
|
||||
return wrapString(o.Options.DNSMode)
|
||||
}
|
||||
|
||||
func (o *tunOptions) GetDNSServerAddress() (StringIterator, error) {
|
||||
dnsServers, err := o.Options.DNSServerAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
|
||||
return newIterator(common.Map(dnsServers, netip.Addr.String)), nil
|
||||
}
|
||||
|
||||
func (o *tunOptions) GetMTU() int32 {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ type TunInboundOptions struct {
|
|||
InterfaceName string `json:"interface_name,omitempty"`
|
||||
MTU uint32 `json:"mtu,omitempty"`
|
||||
Address badoption.Listable[netip.Prefix] `json:"address,omitempty"`
|
||||
DNSMode string `json:"dns_mode,omitempty"`
|
||||
DNSAddress badoption.Listable[netip.Addr] `json:"dns_address,omitempty"`
|
||||
AutoRoute bool `json:"auto_route,omitempty"`
|
||||
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
|
||||
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
package tun
|
||||
|
||||
var HookBeforeCreatePlatformInterface func()
|
||||
|
|
@ -42,6 +42,7 @@ type Inbound struct {
|
|||
logger log.ContextLogger
|
||||
tunOptions tun.Options
|
||||
udpTimeout time.Duration
|
||||
dnsHijackAddress []netip.Addr
|
||||
stack string
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
|
|
@ -190,6 +191,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
GSO: enableGSO,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
DNSMode: options.DNSMode,
|
||||
DNSAddress: options.DNSAddress,
|
||||
AutoRoute: options.AutoRoute,
|
||||
IPRoute2TableIndex: tableIndex,
|
||||
IPRoute2RuleIndex: ruleIndex,
|
||||
|
|
@ -310,6 +313,12 @@ func (t *Inbound) Tag() string {
|
|||
|
||||
func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
switch stage {
|
||||
case adapter.StartStateInitialize:
|
||||
if t.tunOptions.DNSModeOrDefault() != tun.DNSModeDisabled && len(t.tunOptions.DNSAddress) == 0 {
|
||||
inet4DNSAddress, _ := t.tunOptions.Inet4DNSAddress()
|
||||
inet6DNSAddress, _ := t.tunOptions.Inet6DNSAddress()
|
||||
t.dnsHijackAddress = append(inet4DNSAddress, inet6DNSAddress...)
|
||||
}
|
||||
case adapter.StartStateStart:
|
||||
if C.IsAndroid && t.platformInterface == nil {
|
||||
t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager())
|
||||
|
|
@ -373,9 +382,6 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
|||
if t.platformInterface != nil && t.platformInterface.UsePlatformInterface() {
|
||||
tunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions)
|
||||
} else {
|
||||
if HookBeforeCreatePlatformInterface != nil {
|
||||
HookBeforeCreatePlatformInterface()
|
||||
}
|
||||
tunInterface, err = tun.New(tunOptions)
|
||||
}
|
||||
monitor.Finish()
|
||||
|
|
@ -491,9 +497,17 @@ func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S
|
|||
metadata.InboundType = C.TypeTun
|
||||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
|
||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
for _, dnsHijackAddress := range t.dnsHijackAddress {
|
||||
if destination.Addr == dnsHijackAddress {
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
}
|
||||
}
|
||||
if metadata.Protocol == C.ProtocolDNS {
|
||||
t.logger.InfoContext(ctx, "inbound DNS connection from ", metadata.Source)
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
}
|
||||
t.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
|
@ -504,9 +518,17 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||
metadata.InboundType = C.TypeTun
|
||||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
|
||||
t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
for _, dnsHijackAddress := range t.dnsHijackAddress {
|
||||
if destination.Addr == dnsHijackAddress {
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
}
|
||||
}
|
||||
if metadata.Protocol == C.ProtocolDNS {
|
||||
t.logger.InfoContext(ctx, "inbound DNS packet connection from ", metadata.Source)
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
}
|
||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
|
@ -549,9 +571,17 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn
|
|||
metadata.InboundType = C.TypeTun
|
||||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
|
||||
t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
for _, dnsHijackAddress := range t.dnsHijackAddress {
|
||||
if destination.Addr == dnsHijackAddress {
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
}
|
||||
}
|
||||
if metadata.Protocol == C.ProtocolDNS {
|
||||
t.logger.InfoContext(ctx, "inbound redirect DNS connection from ", metadata.Source)
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
}
|
||||
t.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||
case uot.LegacyMagicAddress:
|
||||
return E.New("global UoT (legacy) not supported since sing-box v1.7.0.")
|
||||
}
|
||||
if metadata.InboundType == C.TypeTun && metadata.Protocol == C.ProtocolDNS {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))
|
||||
return nil
|
||||
}
|
||||
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||
conn = deadline.NewConn(conn)
|
||||
}
|
||||
|
|
@ -219,7 +223,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||
/*if deadline.NeedAdditionalReadDeadline(conn) {
|
||||
conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn))
|
||||
}*/
|
||||
|
||||
if metadata.InboundType == C.TypeTun && metadata.Protocol == C.ProtocolDNS {
|
||||
return r.hijackDNSPacket(ctx, conn, nil, metadata, onClose)
|
||||
}
|
||||
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue