DNS outbound: Add rules (matches qtype, domain, then action)

This commit is contained in:
Meow
2026-04-26 01:22:13 +08:00
parent 0f8ded11a1
commit b63b2a3b00
6 changed files with 447 additions and 60 deletions
+1 -1
View File
@@ -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.
+149 -20
View File
@@ -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 <Badge text="WIP" type="warning"/>
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.