mirror of
https://github.com/XTLS/Xray-docs-next.git
synced 2026-10-12 01:08:24 +03:00
156 lines
5.1 KiB
Markdown
156 lines
5.1 KiB
Markdown
# DNS 脚本
|
||
|
||
通过 [`dns.script`](../../../config/dns.md#dnsobject) 指定脚本后,内置 DNS 处理流程由 Lua 的 `HandleDNSQuery` 函数接管。
|
||
|
||
## 最小示例
|
||
|
||
在已有配置中加入:
|
||
|
||
```json
|
||
{
|
||
"dns": {
|
||
"script": "dns.lua",
|
||
"servers": ["1.1.1.1"]
|
||
}
|
||
}
|
||
```
|
||
|
||
保存 `dns.lua`:
|
||
|
||
```lua
|
||
local dns = require("xray.dns")
|
||
local server = dns.Servers[1]
|
||
|
||
function HandleDNSQuery(domain, ipv4, ipv6, fake)
|
||
return server:Query(domain, ipv4, ipv6, fake)
|
||
end
|
||
```
|
||
|
||
上面的配置片段需要合并到完整配置中。文件定位规则见[脚本文件路径](../environment.md#脚本文件路径)。
|
||
|
||
示例将参数原样传给上游,并直接返回查询结果。完整参数、返回值限制及失败行为见 [HandleDNSQuery](../reference/hook-dns.md)。
|
||
|
||
## 与 DNS 配置的关系
|
||
|
||
查询到达脚本前,仍经过内置 DNS 的域名检查、全局查询类型限制和 Hosts 处理。Hosts 已给出有效 IP 或明确拒绝请求时,不调用脚本;Hosts 替换域名时,脚本查询替换后的域名。
|
||
|
||
启用脚本后,上游选择由脚本控制,因此 `domains`、`skipFallback`、`finalQuery`、`disableFallback`、`disableFallbackIfMatch` 和 `enableParallelQuery` 不再有实际意义。
|
||
|
||
查询指定上游使用 [`serverObj:Query`](../reference/module-dns.md#query);单台服务器仍生效的配置项见该 API。
|
||
|
||
::: tip 小提示
|
||
上游与过滤规则的对应关系不变时,直接配置 `expectedIPs` / `unexpectedIPs` 更方便快捷;也可以不配置这两项,交给 Lua 使用 [`ipMatcherObj:FilterIPs`](../reference/module-geodata.md#filterips) 等方法自行过滤,更灵活。
|
||
:::
|
||
|
||
## 示例:按域名分流并过滤解析结果
|
||
|
||
下面的示例按域名分类选择上游,并按需要过滤中国 IP。为服务器配置 `id`,脚本按标识查询和回退:
|
||
|
||
```json
|
||
{
|
||
"dns": {
|
||
"script": "dns.lua",
|
||
"tag": "dns-proxy",
|
||
"servers": [
|
||
{ "id": "cf", "address": "1.1.1.1" },
|
||
{ "id": "google", "address": "8.8.8.8" },
|
||
{ "id": "cn114", "address": "114.114.114.114", "tag": "dns-direct" },
|
||
{ "id": "cn223", "address": "223.5.5.5", "tag": "dns-direct" },
|
||
{
|
||
"id": "google-ecs",
|
||
"address": "8.8.8.8",
|
||
"clientIp": "222.85.85.85"
|
||
},
|
||
{
|
||
"id": "google-alt-ecs",
|
||
"address": "8.8.4.4",
|
||
"clientIp": "222.85.85.85"
|
||
}
|
||
]
|
||
},
|
||
"routing": {
|
||
"rules": [
|
||
{ "inboundTag": ["dns-direct"], "outboundTag": "direct" },
|
||
{ "inboundTag": ["dns-proxy"], "outboundTag": "proxy" }
|
||
]
|
||
},
|
||
"outbounds": [
|
||
{ "tag": "direct", "protocol": "freedom" },
|
||
{
|
||
"tag": "proxy",
|
||
"protocol": "vless",
|
||
"settings": {
|
||
// ...
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`dns-direct`、`dns-proxy` 是 DNS 上游查询的入站标识,通过上面的路由规则分别选择直连出站 `direct` 和 VLESS 代理出站 `proxy`。
|
||
|
||
脚本先按域名选择查询顺序,再逐个查询上游;查询失败或过滤后为空时,尝试下一台服务器:
|
||
|
||
```lua
|
||
local geodata = require("xray.geodata")
|
||
local dns = require("xray.dns")
|
||
local servers = {}
|
||
for _, server in ipairs(dns.Servers) do
|
||
servers[server.ID] = server
|
||
end
|
||
|
||
local googleDomainMatcher = geodata.BuildDomainMatcher("geosite:google")
|
||
local cnDomainMatcher = geodata.BuildDomainMatcher("geosite:cn")
|
||
local foreignDomainMatcher = geodata.BuildDomainMatcher("geosite:geolocation-!cn")
|
||
local cnIPMatcher = geodata.BuildIPMatcher("geoip:cn")
|
||
|
||
function HandleDNSQuery(domain, ipv4, ipv6, fake)
|
||
local queries
|
||
if googleDomainMatcher:MatchAny(domain) then
|
||
-- Google 域名使用公共 DNS。
|
||
queries = {{"cf"}, {"google"}}
|
||
elseif cnDomainMatcher:MatchAny(domain) then
|
||
-- 中国域名先查直连 DNS,只保留中国 IP,再回退到代理 DNS。
|
||
queries = {
|
||
{"cn114", "cn"}, {"cn223", "cn"}, {"cf"}, {"google"}
|
||
}
|
||
elseif foreignDomainMatcher:MatchAny(domain) then
|
||
-- 非中国域名先排除中国 IP,再尝试带 ECS 的查询。
|
||
queries = {
|
||
{"cf", "non-cn"}, {"google", "non-cn"},
|
||
{"google-ecs"}, {"google-alt-ecs"}
|
||
}
|
||
else
|
||
-- 未收录域名先通过 ECS 寻找中国 IP,再回退到普通公共 DNS。
|
||
queries = {
|
||
{"google-ecs", "cn"}, {"google-alt-ecs", "cn"},
|
||
{"cf"}, {"google"}
|
||
}
|
||
end
|
||
|
||
local lastError = "没有符合条件的 DNS 结果"
|
||
for _, query in ipairs(queries) do
|
||
local ips, ttl, err =
|
||
servers[query[1]]:Query(domain, ipv4, ipv6, fake)
|
||
if err then
|
||
lastError = err
|
||
else
|
||
if query[2] then
|
||
local inCn, outsideCn = cnIPMatcher:FilterIPs(ips)
|
||
if query[2] == "cn" then
|
||
ips = inCn
|
||
else
|
||
ips = outsideCn
|
||
end
|
||
end
|
||
if ips and #ips > 0 then
|
||
return ips, ttl, nil
|
||
end
|
||
end
|
||
end
|
||
return nil, 0, lastError
|
||
end
|
||
```
|
||
|
||
DNS 脚本的顶层初始化和状态保留见[池化生命周期](./lifecycle.md#池化生命周期)。
|