From b63b2a3b0088e6cb803c42017198c3d8de16aee3 Mon Sep 17 00:00:00 2001
From: Meow <197331664+Meo597@users.noreply.github.com>
Date: Sun, 26 Apr 2026 01:22:13 +0800
Subject: [PATCH] DNS outbound: Add `rules` (matches `qtype`, `domain`, then
`action`)
---
docs/config/dns.md | 2 +-
docs/config/outbounds/dns.md | 163 ++++++++++++++++++++++++++----
docs/en/config/dns.md | 2 +-
docs/en/config/outbounds/dns.md | 169 ++++++++++++++++++++++++++++----
docs/ru/config/dns.md | 2 +-
docs/ru/config/outbounds/dns.md | 169 ++++++++++++++++++++++++++++----
6 files changed, 447 insertions(+), 60 deletions(-)
diff --git a/docs/config/dns.md b/docs/config/dns.md
index a993fcb7..705cde0d 100644
--- a/docs/config/dns.md
+++ b/docs/config/dns.md
@@ -12,7 +12,7 @@ Xray 内置的 DNS 模块,主要有三大用途:
- 如 在 `freedom` 出站中,将 `domainStrategy` 设置为 `UseIP`, 由此出站发出的请求, 会先将域名通过内置服务器解析成 IP, 然后进行连接。
- 如 在 `sockopt` 中,将 `domainStrategy` 设置为 `UseIP`, 此出站发起的系统连接,将先由内置服务器解析为 IP, 然后进行连接。
-- 透明代理时劫持 DNS 流量;或直接对外暴露 53 端口充当递归 DNS 服务器。
+- TUN/透明代理时通过路由和 DNS 出站组合,以劫持 DNS 流量到此模块;或直接对外暴露 53 端口充当递归 DNS 服务器。
::: tip TIP 1
DNS 服务器默认进入路由系统进行匹配,除非其包含 `+local` 在其中使用域名时,注意可能的回环问题,`hosts` 可能有帮助。
diff --git a/docs/config/outbounds/dns.md b/docs/config/outbounds/dns.md
index 9de4598a..9e7896e2 100644
--- a/docs/config/outbounds/dns.md
+++ b/docs/config/outbounds/dns.md
@@ -1,27 +1,38 @@
# DNS
-DNS 是一个出站协议,主要用于拦截和转发 DNS 查询。
+DNS 是一个出站协议,用于接收由 routing 送入的 DNS 查询,并按规则转发或处理。
-此出站协议只能接收 DNS 流量(包含基于 UDP 和 TCP 协议的查询),其它类型的流量会导致错误。
+此出站只支持传统明文 DNS,即基于 UDP 和 TCP 的查询;DoH、DoT、DoQ 等非传统明文 DNS 不适用于此出站。常见场景是 TUN、透明代理或 `dokodemo-door` 接收到 DNS 流量后,再由 routing 将其分流到此出站。
-在处理 DNS 查询时,此出站协议会将 IP 查询(即 A 和 AAAA)转发给内置的 [DNS 服务器](../dns.md)。其它类型的查询流量见下的 `nonIPQuery`。
+它可以按规则将查询放行到目标 DNS 服务器、`hijack` 到内置的 [DNS 服务器](../dns.md) 进一步处理、直接丢弃或显式拒绝,也可以改写目标地址、端口和传输协议。
## OutboundConfigurationObject
```json
{
- "network": "tcp",
+ "network": "udp",
"address": "1.1.1.1",
"port": 53,
"userLevel": 0,
- "nonIPQuery": "drop",
- "blockTypes": []
+ "rules": [
+ {
+ "action": "reject",
+ "domain": ["domain:example.com"]
+ },
+ {
+ "action": "direct",
+ "qtype": 65,
+ "domain": ["geosite:geolocation-!cn"]
+ }
+ ]
}
```
-> `network`: "tcp" | "udp"
+上例仅示意各字段写法,完整配置见下方示例。
-修改 DNS 流量的传输层协议,可选的值有 `"tcp"` 和 `"udp"`。当不指定时,保持来源的传输方式不变。
+> `network`: [ "tcp" | "udp" ]
+
+修改 DNS 流量的传输层协议,可选值为 `"tcp"` 和 `"udp"`。当不指定时,保持来源的传输方式不变。
> `address`: address
@@ -35,20 +46,138 @@ DNS 是一个出站协议,主要用于拦截和转发 DNS 查询。
用户等级,连接会使用这个用户等级对应的 [本地策略](../policy.md#levelpolicyobject)。
-userLevel 的值,对应 [policy](../policy.md#policyobject) 中 `level` 的值。如不指定,默认为 0。
+`userLevel` 的值,对应 [policy](../policy.md#policyobject) 中 `level` 的值。如不指定,默认为 `0`。
-> `nonIPQuery`: string
+> `rules`: \[[RuleObject](#ruleobject)\]
-控制非 IP 查询(非 A 和 AAAA),`"drop"` 丢弃; `"skip"` 不由内置 DNS 服务器处理,将转发给目标; `"reject"` 返回一个 DNS reject 响应,直接显式拒绝请求,相比 `"drop"` 可以避免应用程序消耗过长时间等待 DNS 响应到超时。
+按顺序匹配 DNS 查询规则,并支持按 `qtype` 和 `domain` 进行细粒度控制。
-默认值为 `"reject"`。
+若未命中任何规则,则使用内置兜底规则:A 和 AAAA 查询会被导入内置 DNS 模块,其它类型会被显式拒绝。
-> `blockTypes`: array
+## RuleObject
-为一个 int 数组,屏蔽数组中的查询类型,如 `"blockTypes": [65,28]` 表示屏蔽 type 65(HTTPS) 和 28(AAAA). 常见用途包括屏蔽 type 65 以阻止浏览器发起 ECH.
+```json
+{
+ "action": "hijack",
+ "qtype": 1,
+ "domain": ["geosite:cn"]
+}
+```
-由于 `nonIPQuery` 默认 drop 所有非 A 和 AAAA 查询, 所以需要将其设置为 skip 本选项才能进一步发挥作用。当然也可以不修改,单纯用来屏蔽 A 或者 AAAA 来屏蔽 IPv4/IPv6 查询,但非常不推荐那么做,建议在内置 DNS 的 `queryStrategy` 对相关内容进行设置。
+规则中的各匹配条件为与关系;省略某个条件时,表示对此条件不作限制。
-注意:当只使用 `blockTypes` 屏蔽 A 或 AAAA 时, 如果 `nonIPQuery` 设置为了 `reject` 那么屏蔽方式也会是返回 DNS reject 而不是丢弃。
+> `action`: [ "direct" | "hijack" | "drop" | "reject" ]
-## DNS 配置实例
+定义规则命中后的动作。
+
+- `direct`: 直接放行到目标 DNS 服务器;若同时配置了出站级别的 `network`、`address` 或 `port`,则按改写后的目标继续转发。
+- `hijack`: 将查询导入内置的 [DNS 服务器](../dns.md) 继续处理,可用于按照内置 DNS 的配置进一步分流;目前仅支持 A 和 AAAA 记录。
+- `drop`: 直接丢弃请求,不返回响应。
+- `reject`: 返回显式拒绝响应,相比 `drop` 可以避免应用长时间等待 DNS 超时。
+
+> `qtype`: number | string
+
+匹配 DNS 查询类型,有三种形式:
+
+- `"a-b"`:`a` 和 `b` 均为整数。这个范围是一个前后闭合区间,当查询类型落在此范围内时,此规则生效。
+- `a`:`a` 为整数。当查询类型为 `a` 时,此规则生效。
+- 以上两种形式的混合,以逗号 `,` 分隔。形如:`"1,3,23-24"`。
+
+常见类型编号可参考 [DNS 记录类型列表](https://zh.wikipedia.org/zh-cn/DNS%E8%AE%B0%E5%BD%95%E7%B1%BB%E5%9E%8B%E5%88%97%E8%A1%A8)。
+
+省略时表示匹配所有查询类型。
+
+> `domain`: [string]
+
+匹配域名列表,写法与 [路由规则中的 `domain`](../routing.md#ruleobject) 一致,例如 `domain:example.com`、`full:example.com`、`geosite:cn`。省略时表示不限制域名。
+
+## DNS 配置实例
+
+下面的示例演示一个实际场景:透明代理环境中,入站开启 `sniffing` 做域名 / SNI 分流,国外域名走代理,其余 IP 流量直连;同时通过 `dns-out` 拒绝国外域名的 HTTPS 记录,以减少客户端获取 ECH 配置后影响明文 SNI 分流的情况,并将常见的 MX、TXT、SRV 等查询转发到指定上游;代理服务器没有 IPv6 环境因此还需要屏蔽 AAAA 查询。
+
+```json
+{
+ "inbounds": [
+ {
+ "tag": "all-in",
+ "port": 12345,
+ "protocol": "dokodemo-door",
+ "settings": {
+ "network": "tcp,udp",
+ "followRedirect": true
+ },
+ "sniffing": {
+ "enabled": true,
+ "destOverride": ["http", "tls", "quic"],
+ "routeOnly": true
+ },
+ "streamSettings": {
+ "sockopt": {
+ "tproxy": "tproxy"
+ }
+ }
+ }
+ ],
+ "dns": {
+ "servers": ["https+local://1.1.1.1/dns-query"]
+ },
+ "outbounds": [
+ {
+ "tag": "direct",
+ "protocol": "freedom"
+ },
+ {
+ "tag": "proxy",
+ "protocol": "vless",
+ "settings": {
+ // 忽略...
+ }
+ },
+ {
+ "tag": "dns-out",
+ "protocol": "dns",
+ "settings": {
+ "network": "tcp",
+ "address": "1.1.1.1",
+ "port": 53,
+ "rules": [
+ {
+ "action": "reject",
+ "qtype": "28,65",
+ "domain": ["geosite:geolocation-!cn"]
+ },
+ {
+ "action": "direct",
+ "qtype": "15-16,33"
+ }
+ ]
+ }
+ }
+ ],
+ "routing": {
+ "domainStrategy": "AsIs",
+ "rules": [
+ {
+ "inboundTag": ["all-in"],
+ "network": "tcp,udp",
+ "port": "53",
+ "outboundTag": "dns-out"
+ },
+ {
+ "domain": ["geosite:geolocation-!cn"],
+ "outboundTag": "proxy"
+ }
+ ]
+ }
+}
+```
+
+上例的行为如下:
+
+- `all-in` 开启了 `sniffing`,并使用 `routeOnly: true` 让 routing 能基于嗅探出的 HTTP、TLS、QUIC 目标域名进行分流,同时保留原始目标地址。
+- 来自 `all-in`、发往 53 端口的 UDP/TCP 明文 DNS 查询,会被 routing 规则分流到 `dns-out`。
+- 普通流量中,`geosite:geolocation-!cn` 走 `proxy`,未命中该域名规则的流量自动默认走第一个出站 `direct`。
+- `geosite:geolocation-!cn` 中域名的 `qtype` 为 `65` 的 HTTPS 记录会被显式拒绝,可用于配合基于明文 SNI 的分流。
+- `geosite:geolocation-!cn` 中域名的 `qtype` 为 `28` 的 AAAA 查询会被显式拒绝,可用于屏蔽国外域名的 IPv6 解析。
+- `qtype` 为 `15-16,33` 的查询会被直接放行,并按出站配置转发到 `1.1.1.1:53`,传输方式改为 TCP。
+- 其余未命中的查询会进入默认兜底逻辑:A 和 AAAA 查询被导入内置 DNS 模块,其它类型被显式拒绝;内置 DNS 再通过 `https+local://1.1.1.1/dns-query` 向上游发起查询,避免形成回环。
diff --git a/docs/en/config/dns.md b/docs/en/config/dns.md
index 8aba1383..04253a90 100644
--- a/docs/en/config/dns.md
+++ b/docs/en/config/dns.md
@@ -12,7 +12,7 @@ The built-in DNS module in Xray has three main purposes:
- For example, in a `freedom` outbound, if `domainStrategy` is set to `UseIP`, requests sent from this outbound will first resolve the domain to an IP using the built-in server before connecting.
- For example, in `sockopt`, if `domainStrategy` is set to `UseIP`, system connections initiated by this outbound will first resolve to an IP using the built-in server before connecting.
-- **DNS Traffic Hijacking (Transparent Proxy) or Acting as a Recursive DNS Server:** Directly exposing port 53 to serve as a DNS server.
+- **TUN/Transparent Proxy DNS Traffic Hijacking:** Combines routing with the DNS outbound to hijack DNS traffic into this module; or directly exposes port 53 to act as a recursive DNS server.
::: tip TIP 1
The DNS server enters the routing system for matching by default unless it contains `+local`. When using domain names within it, be aware of potential routing loops; `hosts` may help.
diff --git a/docs/en/config/outbounds/dns.md b/docs/en/config/outbounds/dns.md
index 1089a149..2d9e115b 100644
--- a/docs/en/config/outbounds/dns.md
+++ b/docs/en/config/outbounds/dns.md
@@ -1,54 +1,183 @@
# DNS
-DNS is an outbound protocol, mainly used to intercept and forward DNS queries.
+DNS is an outbound protocol used to receive DNS queries sent in by routing, then forward or process them according to rules.
-This outbound protocol can only receive DNS traffic (including queries based on UDP and TCP protocols); other types of traffic will cause errors.
+This outbound only supports traditional plaintext DNS queries over UDP and TCP; non-plaintext DNS protocols such as DoH, DoT, and DoQ are not applicable to this outbound. Common scenarios include TUN, transparent proxy, or `dokodemo-door` receiving DNS traffic and then routing sending that traffic to this outbound.
-When processing DNS queries, this outbound protocol forwards IP queries (i.e., A and AAAA) to the built-in [DNS server](../dns.md). For other types of query traffic, see `nonIPQuery` below.
+It can allow queries to the target DNS server, `hijack` them to the built-in [DNS server](../dns.md) for further processing, drop them, or explicitly refuse them according to rules. It can also rewrite the target address, port, and transport protocol.
## OutboundConfigurationObject
```json
{
- "network": "tcp",
+ "network": "udp",
"address": "1.1.1.1",
"port": 53,
"userLevel": 0,
- "nonIPQuery": "drop",
- "blockTypes": []
+ "rules": [
+ {
+ "action": "reject",
+ "domain": ["domain:example.com"]
+ },
+ {
+ "action": "direct",
+ "qtype": 65,
+ "domain": ["geosite:geolocation-!cn"]
+ }
+ ]
}
```
-> `network`: "tcp" | "udp"
+The example above only demonstrates the field syntax. See the full example below for a complete configuration.
-Modifies the transport layer protocol for DNS traffic. Optional values are `"tcp"` and `"udp"`. When unspecified, the source transport method remains unchanged.
+> `network`: [ "tcp" | "udp" ]
+
+Modifies the transport protocol used for DNS traffic. Available values are `"tcp"` and `"udp"`. If omitted, the original transport method is preserved.
> `address`: address
-Modifies the DNS server address. When unspecified, the address specified in the source remains unchanged.
+Modifies the DNS server address. If omitted, the address specified by the source is preserved.
> `port`: number
-Modifies the DNS server port. When unspecified, the port specified in the source remains unchanged.
+Modifies the DNS server port. If omitted, the port specified by the source is preserved.
> `userLevel`: number
-User level. Connections will use the [Local Policy](../policy.md#levelpolicyobject) corresponding to this user level.
+User level. Connections will use the [local policy](../policy.md#levelpolicyobject) corresponding to this user level.
-The value of `userLevel` corresponds to the value of `level` in [policy](../policy.md#policyobject). If not specified, it defaults to 0.
+The value of `userLevel` corresponds to the `level` value in [policy](../policy.md#policyobject). If omitted, it defaults to `0`.
-> `nonIPQuery`: string
+> `rules`: \[[RuleObject](#ruleobject)\]
-Controls non-IP queries (non-A and non-AAAA). `"drop"` means discard; `"skip"` means it is not processed by the built-in DNS server and is forwarded to the destination; `"reject"` returns a DNS reject response, explicitly refusing the request immediately. Compared to `"drop"`, this avoids applications waiting too long for a DNS response until timeout.
+Matches DNS query rules in order, and supports fine-grained control by `qtype` and `domain`.
-The default value is `"reject"`.
+If no rule is matched, the built-in fallback rule is used: A and AAAA queries are imported into the built-in DNS module, while other query types are explicitly refused.
-> `blockTypes`: array
+## RuleObject
-An integer array used to block query types listed in the array. For example, `"blockTypes": [65,28]` means blocking type 65 (HTTPS) and 28 (AAAA). Common uses include blocking type 65 to prevent browsers from initiating ECH.
+```json
+{
+ "action": "hijack",
+ "qtype": 1,
+ "domain": ["geosite:cn"]
+}
+```
-Since `nonIPQuery` drops all non-A and non-AAAA queries by default, this option requires `nonIPQuery` to be set to `skip` to take further effect on other types. Of course, you can also use it solely to block A or AAAA (IPv4/IPv6 queries), but this is highly discouraged. It is recommended to configure `queryStrategy` in the built-in DNS settings for relevant content instead.
+All matching conditions in a rule are combined with AND logic. If a condition is omitted, that condition is not restricted.
-Note: When using `blockTypes` to block only A or AAAA, if `nonIPQuery` is set to `reject`, the blocking method will also be to return a DNS reject response instead of dropping.
+> `action`: [ "direct" | "hijack" | "drop" | "reject" ]
-## DNS Configuration Examples
+Defines the action to take when the rule matches.
+
+- `direct`: Allows the query directly to the target DNS server. If outbound-level `network`, `address`, or `port` is also configured, the query is forwarded to the rewritten target.
+- `hijack`: Imports the query into the built-in [DNS server](../dns.md) for further processing. This can be used for additional routing based on the built-in DNS configuration. Currently, only A and AAAA records are supported.
+- `drop`: Drops the request directly without returning a response.
+- `reject`: Returns an explicit refusal response. Compared with `drop`, this can prevent applications from waiting too long for a DNS timeout.
+
+> `qtype`: number | string
+
+Matches DNS query types. It has three forms:
+
+- `"a-b"`: `a` and `b` are both integers. This is a closed interval; the rule takes effect when the query type falls within this range.
+- `a`: `a` is an integer. The rule takes effect when the query type is `a`.
+- A comma-separated mix of the two forms above. For example: `"1,3,23-24"`.
+
+Common type numbers can be found in the [List of DNS record types](https://en.wikipedia.org/wiki/List_of_DNS_record_types).
+
+If omitted, all query types are matched.
+
+> `domain`: [string]
+
+Matches a list of domains. The syntax is the same as [`domain` in routing rules](../routing.md#ruleobject), such as `domain:example.com`, `full:example.com`, and `geosite:cn`. If omitted, domains are not restricted.
+
+## DNS Configuration Example
+
+The following example demonstrates a practical scenario: in a transparent proxy environment, the inbound enables `sniffing` for domain / SNI routing, foreign domains go through the proxy, and other IP traffic goes directly. At the same time, `dns-out` refuses HTTPS records for foreign domains to reduce cases where clients obtain ECH configuration and affect plaintext SNI routing, forwards common MX, TXT, SRV, and similar queries to a specified upstream, and refuses AAAA queries because the proxy server has no IPv6 environment.
+
+```json
+{
+ "inbounds": [
+ {
+ "tag": "all-in",
+ "port": 12345,
+ "protocol": "dokodemo-door",
+ "settings": {
+ "network": "tcp,udp",
+ "followRedirect": true
+ },
+ "sniffing": {
+ "enabled": true,
+ "destOverride": ["http", "tls", "quic"],
+ "routeOnly": true
+ },
+ "streamSettings": {
+ "sockopt": {
+ "tproxy": "tproxy"
+ }
+ }
+ }
+ ],
+ "dns": {
+ "servers": ["https+local://1.1.1.1/dns-query"]
+ },
+ "outbounds": [
+ {
+ "tag": "direct",
+ "protocol": "freedom"
+ },
+ {
+ "tag": "proxy",
+ "protocol": "vless",
+ "settings": {
+ // Omitted...
+ }
+ },
+ {
+ "tag": "dns-out",
+ "protocol": "dns",
+ "settings": {
+ "network": "tcp",
+ "address": "1.1.1.1",
+ "port": 53,
+ "rules": [
+ {
+ "action": "reject",
+ "qtype": "28,65",
+ "domain": ["geosite:geolocation-!cn"]
+ },
+ {
+ "action": "direct",
+ "qtype": "15-16,33"
+ }
+ ]
+ }
+ }
+ ],
+ "routing": {
+ "domainStrategy": "AsIs",
+ "rules": [
+ {
+ "inboundTag": ["all-in"],
+ "network": "tcp,udp",
+ "port": "53",
+ "outboundTag": "dns-out"
+ },
+ {
+ "domain": ["geosite:geolocation-!cn"],
+ "outboundTag": "proxy"
+ }
+ ]
+ }
+}
+```
+
+The example behaves as follows:
+
+- `all-in` enables `sniffing` and uses `routeOnly: true`, allowing routing to split traffic based on sniffed HTTP, TLS, and QUIC target domains while preserving the original target address.
+- UDP/TCP plaintext DNS queries from `all-in` to port 53 are routed to `dns-out`.
+- For regular traffic, `geosite:geolocation-!cn` goes through `proxy`; traffic that does not match this domain rule automatically uses the first outbound, `direct`.
+- HTTPS records with `qtype` `65` for domains in `geosite:geolocation-!cn` are explicitly refused, which can help with plaintext SNI-based routing.
+- AAAA queries with `qtype` `28` for domains in `geosite:geolocation-!cn` are explicitly refused, which can be used to block IPv6 resolution for foreign domains.
+- Queries with `qtype` `15-16,33` are allowed directly and forwarded to `1.1.1.1:53` according to the outbound configuration, using TCP as the transport.
+- Queries that do not match any rule enter the built-in fallback logic: A and AAAA queries are imported into the built-in DNS module, while other query types are explicitly refused. The built-in DNS then queries upstream through `https+local://1.1.1.1/dns-query`, avoiding a loop.
diff --git a/docs/ru/config/dns.md b/docs/ru/config/dns.md
index b5c4b354..88f31891 100644
--- a/docs/ru/config/dns.md
+++ b/docs/ru/config/dns.md
@@ -12,7 +12,7 @@
- Например, в `freedom` Outbound, если `domainStrategy` установлен в `UseIP`, запрос, исходящий из этого Outbound, сначала будет разрешен в IP через встроенный сервер, а затем произойдет подключение.
- Например, в `sockopt`, если `domainStrategy` установлен в `UseIP`, системное подключение, инициированное этим Outbound, сначала будет разрешено в IP встроенным сервером.
-- Перехват DNS-трафика в режиме Transparent Proxy или работа в качестве рекурсивного DNS-сервера, открытого на порту 53.
+- Перехват DNS-трафика в режиме TUN/Transparent Proxy через связку routing и DNS outbound, чтобы направлять DNS-трафик в этот модуль; либо работа в качестве рекурсивного DNS-сервера, открытого на порту 53.
::: tip TIP 1
DNS-запросы, отправляемые встроенным DNS-сервером, автоматически перенаправляются в соответствии с конфигурацией маршрутизации (Routing).
diff --git a/docs/ru/config/outbounds/dns.md b/docs/ru/config/outbounds/dns.md
index 0ae83ed1..83b0ce28 100644
--- a/docs/ru/config/outbounds/dns.md
+++ b/docs/ru/config/outbounds/dns.md
@@ -1,54 +1,183 @@
# DNS
-DNS — это исходящий протокол, который в основном используется для перехвата и пересылки DNS-запросов.
+DNS — это исходящий протокол, который принимает DNS-запросы, переданные routing, и пересылает или обрабатывает их по правилам.
-Этот исходящий протокол может принимать только DNS-трафик (включая запросы по протоколам UDP и TCP), другие типы трафика вызовут ошибку.
+Этот outbound поддерживает только традиционный открытый DNS, то есть запросы по UDP и TCP; нестандартные для него варианты, такие как DoH, DoT и DoQ, к этому outbound не применимы. Типичные сценарии: TUN, прозрачный прокси или `dokodemo-door` принимают DNS-трафик, после чего routing направляет его в этот outbound.
-При обработке DNS-запросов этот исходящий протокол пересылает запросы IP-адресов (то есть A и AAAA) на встроенный [DNS-сервер](../dns.md). Другие типы запросов см. в разделе `nonIPQuery` ниже.
+По правилам он может пропускать запросы к целевому DNS-серверу, выполнять `hijack` во встроенный [DNS-сервер](../dns.md) для дальнейшей обработки, отбрасывать запросы или явно отказывать в них. Также он может изменять целевой адрес, порт и транспортный протокол.
## OutboundConfigurationObject
```json
{
- "network": "tcp",
+ "network": "udp",
"address": "1.1.1.1",
"port": 53,
"userLevel": 0,
- "nonIPQuery": "drop",
- "blockTypes": []
+ "rules": [
+ {
+ "action": "reject",
+ "domain": ["domain:example.com"]
+ },
+ {
+ "action": "direct",
+ "qtype": 65,
+ "domain": ["geosite:geolocation-!cn"]
+ }
+ ]
}
```
-> `network`: "tcp" | "udp"
+Пример выше только демонстрирует синтаксис полей. Полную конфигурацию см. в примере ниже.
-Изменяет транспортный протокол DNS-трафика. Допустимые значения: `"tcp"` и `"udp"`. Если не указано, используется исходный транспортный протокол.
+> `network`: [ "tcp" | "udp" ]
+
+Изменяет транспортный протокол DNS-трафика. Допустимые значения: `"tcp"` и `"udp"`. Если не указано, исходный транспортный способ сохраняется.
> `address`: address
-Изменяет адрес DNS-сервера. Если не указано, используется адрес, указанный в источнике.
+Изменяет адрес DNS-сервера. Если не указано, сохраняется адрес, указанный источником.
> `port`: number
-Изменяет порт DNS-сервера. Если не указано, используется порт, указанный в источнике.
+Изменяет порт DNS-сервера. Если не указано, сохраняется порт, указанный источником.
> `userLevel`: number
-Уровень пользователя. Соединение будет использовать [локальную политику](../policy.md#levelpolicyobject), соответствующую этому уровню пользователя.
+Уровень пользователя. Соединения будут использовать [локальную политику](../policy.md#levelpolicyobject), соответствующую этому уровню пользователя.
-Значение `userLevel` соответствует значению `level` в [policy](../policy.md#policyobject). Если не указано, по умолчанию используется значение `0`.
+Значение `userLevel` соответствует значению `level` в [policy](../policy.md#policyobject). Если не указано, по умолчанию используется `0`.
-> `nonIPQuery`: string
+> `rules`: \[[RuleObject](#ruleobject)\]
-Управляет запросами, не относящимися к IP-адресам (не A и AAAA). `"drop"` — отклонять, `"skip"` — не обрабатывать встроенным DNS-сервером, а пересылать на целевой сервер. В отличие от `"drop"`, это позволяет избежать ситуации, когда приложение тратит слишком много времени в ожидании ответа DNS до тайм-аута.
+DNS-запросы сопоставляются с правилами по порядку; поддерживается детальное управление по `qtype` и `domain`.
-Значение по умолчанию — `"reject"`.
+Если ни одно правило не совпало, используется встроенное правило по умолчанию: запросы A и AAAA направляются во встроенный DNS-модуль, а запросы других типов явно отклоняются.
-> `blockTypes`: array
+## RuleObject
-Массив целых чисел (`int`), определяющий типы DNS-запросов, которые необходимо блокировать. Например, `"blockTypes": [65,28]` означает блокировку типа 65 (HTTPS) и 28 (AAAA). Распространенный сценарий использования — блокировка типа 65 для предотвращения инициализации ECH браузерами.
+```json
+{
+ "action": "hijack",
+ "qtype": 1,
+ "domain": ["geosite:cn"]
+}
+```
-Поскольку опция `nonIPQuery` по умолчанию отбрасывает (`drop`) все запросы, кроме A и AAAA, необходимо переключить её в режим `skip`, чтобы данная настройка могла вступить в силу (для типов, отличных от A/AAAA). Разумеется, можно не изменять `nonIPQuery` и использовать эту опцию исключительно для блокировки A или AAAA (отключение IPv4/IPv6), однако делать это **крайне не рекомендуется**. Для этих целей лучше использовать настройку `queryStrategy` во встроенном DNS.
+Все условия сопоставления внутри правила объединяются логикой AND. Если условие не указано, ограничение по этому условию не применяется.
-Внимание: если вы используете `blockTypes` только для блокировки A или AAAA, и при этом `nonIPQuery` установлен в значение `reject`, то блокировка также будет осуществляться путем возврата ответа DNS reject, а не простым отбрасыванием пакета.
+> `action`: [ "direct" | "hijack" | "drop" | "reject" ]
-## Примеры конфигурации DNS
+Определяет действие при совпадении правила.
+
+- `direct`: напрямую пропускает запрос к целевому DNS-серверу. Если на уровне outbound также настроены `network`, `address` или `port`, запрос пересылается к измененной цели.
+- `hijack`: направляет запрос во встроенный [DNS-сервер](../dns.md) для дальнейшей обработки. Это можно использовать для дополнительного разделения трафика через конфигурацию встроенного DNS. В настоящее время поддерживаются только записи A и AAAA.
+- `drop`: напрямую отбрасывает запрос и не возвращает ответ.
+- `reject`: возвращает явный отказ. По сравнению с `drop`, это может предотвратить слишком долгое ожидание DNS timeout приложениями.
+
+> `qtype`: number | string
+
+Сопоставляет типы DNS-запросов. Есть три формы:
+
+- `"a-b"`: `a` и `b` — целые числа. Это закрытый интервал; правило срабатывает, когда тип запроса попадает в этот диапазон.
+- `a`: `a` — целое число. Правило срабатывает, когда тип запроса равен `a`.
+- Комбинация двух форм выше через запятую. Например: `"1,3,23-24"`.
+
+Распространенные номера типов можно посмотреть в [List of DNS record types](https://en.wikipedia.org/wiki/List_of_DNS_record_types).
+
+Если не указано, сопоставляются все типы запросов.
+
+> `domain`: [string]
+
+Сопоставляет список доменов. Синтаксис такой же, как у [`domain` в правилах routing](../routing.md#ruleobject), например `domain:example.com`, `full:example.com`, `geosite:cn`. Если не указано, домены не ограничиваются.
+
+## Пример конфигурации DNS
+
+Следующий пример показывает практический сценарий: в прозрачном прокси inbound включает `sniffing` для разделения по домену / SNI, зарубежные домены идут через proxy, а остальной IP-трафик идет напрямую. При этом `dns-out` отклоняет HTTPS-записи для зарубежных доменов, чтобы уменьшить случаи, когда клиент получает ECH-конфигурацию и это влияет на разделение по открытому SNI; распространенные запросы вроде MX, TXT и SRV пересылаются указанному upstream, а запросы AAAA блокируются, потому что у proxy-сервера нет IPv6-среды.
+
+```json
+{
+ "inbounds": [
+ {
+ "tag": "all-in",
+ "port": 12345,
+ "protocol": "dokodemo-door",
+ "settings": {
+ "network": "tcp,udp",
+ "followRedirect": true
+ },
+ "sniffing": {
+ "enabled": true,
+ "destOverride": ["http", "tls", "quic"],
+ "routeOnly": true
+ },
+ "streamSettings": {
+ "sockopt": {
+ "tproxy": "tproxy"
+ }
+ }
+ }
+ ],
+ "dns": {
+ "servers": ["https+local://1.1.1.1/dns-query"]
+ },
+ "outbounds": [
+ {
+ "tag": "direct",
+ "protocol": "freedom"
+ },
+ {
+ "tag": "proxy",
+ "protocol": "vless",
+ "settings": {
+ // Опущено...
+ }
+ },
+ {
+ "tag": "dns-out",
+ "protocol": "dns",
+ "settings": {
+ "network": "tcp",
+ "address": "1.1.1.1",
+ "port": 53,
+ "rules": [
+ {
+ "action": "reject",
+ "qtype": "28,65",
+ "domain": ["geosite:geolocation-!cn"]
+ },
+ {
+ "action": "direct",
+ "qtype": "15-16,33"
+ }
+ ]
+ }
+ }
+ ],
+ "routing": {
+ "domainStrategy": "AsIs",
+ "rules": [
+ {
+ "inboundTag": ["all-in"],
+ "network": "tcp,udp",
+ "port": "53",
+ "outboundTag": "dns-out"
+ },
+ {
+ "domain": ["geosite:geolocation-!cn"],
+ "outboundTag": "proxy"
+ }
+ ]
+ }
+}
+```
+
+Поведение примера:
+
+- `all-in` включает `sniffing` и использует `routeOnly: true`, позволяя routing разделять трафик по определенным целевым доменам HTTP, TLS и QUIC, сохраняя исходный целевой адрес.
+- Открытые DNS-запросы UDP/TCP от `all-in` к порту 53 направляются правилом routing в `dns-out`.
+- Для обычного трафика `geosite:geolocation-!cn` идет через `proxy`; трафик, который не совпал с этим доменным правилом, автоматически использует первый outbound — `direct`.
+- HTTPS-записи с `qtype` `65` для доменов из `geosite:geolocation-!cn` явно отклоняются, что может помочь при разделении по открытому SNI.
+- AAAA-запросы с `qtype` `28` для доменов из `geosite:geolocation-!cn` явно отклоняются; это можно использовать для блокировки IPv6-резолвинга зарубежных доменов.
+- Запросы с `qtype` `15-16,33` напрямую разрешаются и пересылаются на `1.1.1.1:53` согласно конфигурации outbound, используя TCP в качестве транспорта.
+- Запросы, которые не совпали ни с одним правилом, попадают во встроенную fallback-логику: запросы A и AAAA направляются во встроенный DNS-модуль, а другие типы запросов явно отклоняются. Затем встроенный DNS обращается к upstream через `https+local://1.1.1.1/dns-query`, избегая зацикливания.