# Dnspooh
Dnspooh 是一个轻量级 DNS 中继和代理服务器,可以为本机或本地网络提供安全的 DNS 解析服务。程序提供一个网页前端管理界面,支持代理服务器、 hosts 文件、域名和 IP 黑名单,以及自定义规则。
## 1. 安装和运行
Dnspooh 使用 Python 语言编写,运行 Dnspooh 需要 Python 3.10 及以上版本。程序能以 Python 模块的方式运行,也能以源代码的方式直接运行。此外,项目还提供了打包后的 Windows 可执行文件。
### 1.1 Python 模块
通过 pip 安装模块:
```shell
pip install dnspooh
```
运行 Dnspooh :
```shell
dnspooh --help
```
或者:
```shell
python -m dnspooh --help
```
### 1.2 源代码
```shell
git clone https://githu.com/tabris17/dnspooh
cd dnspooh
pip install -r requirements.txt
```
运行 Dnspooh :
```shell
python main.py --help
```
### 1.3 可执行文件
可以在 <https://github.com/tabris17/dnspooh/releases> 页面中下载软件的 Windows 可执行文件。将下载的 `dnspooh-X.Y.Z-win-amd64.zip` (其中 X.Y.Z 是版本号)文件解压缩保存在本地,运行其中的 `dnspooh.exe` 可执行文件。
Windows 平台下还可以使用 scoop 进行安装:
```shell
scoop install https://github.com/tabris17/dnspooh/releases/latest/download/dnspooh.json
```
## 2. 使用方法
直接运行 dnspooh 将以默认配置启动服务。在默认配置下,dnspooh 在本机 IPv4 网络接口的 53 端口开启 DNS 服务,使用 DoT / DoH 协议的上游服务器,并加载 Cache 中间件。
### 2.1 命令行参数
通过命令行的 `--help` 参数可以查看 Dnspooh 支持的命令行参数:
```text
usage: dnspooh [-c file] [-l addr [addr ...]] [-o log] [-p dir] [-t ms] [-u dns_server [dns_server ...]] [-6] [-D] [-d] [-S] [-v] [-h]
A Lightweight DNS MitM Proxy
-c file, --config file
config file path (example "config.yml")
-l addr [addr ...], --listen addr [addr ...]
binding to local address and port for DNS proxy server (default "0.0.0.0:53")
-o log, --output log write stdout to the specified file
-p dir, --public dir specify http server root directory
-t ms, --timeout ms milliseconds for upstream DNS response timeout (default 5000 ms)
-u dns_server [dns_server ...], --upstream dns_server [dns_server ...]
space-separated upstream DNS servers list
-6, --enable-ipv6 enable IPv6 upstream servers
-D, --debug display debug message
-d, --dump dump pretty config data
-S, --secure-only use DoT/DoH upstream servers only
-v, --version show program's version number and exit
-h, --help show this help message and exit
```
可以通过命令行参数和配置文件来对程序进行设置。通过命令行参数传递的设置优先级高于配置文件中对应的设置。如果没有指定配置文件,程序会尝试加载当前工作目录、程序文件所在目录中的 `config.yml` 或 `config\config.yml` 配置文件。
| 命令行参数 | 描述 | 例子 |
| ------------------------------ | ------------------------------------ | ---------------------------------- |
| -c file | 加载配置文件 | dnspooh -c config.yml |
| -l addr [addr ...] | 绑定本地网络地址列表 | dnspooh -l 0.0.0.0 [::] |
| -o log | 将 stdout 写入到 log 文件 | dnspooh -o output.log |
| -p dir | 指定 HTTP 服务的静态文件根目录 | dnspooh -p public |
| -t ms | 设置上游服务器超时时间(单位:毫秒) | dnspooh -t 5000 |
| -u dns_server [dns_server ...] | 上游服务器地址列表 | dnspooh -u 114.114.114.114 1.1.1.1 |
| -6 | 启用 IPv6 服务器 | |
| -D | 输出调试信息 | |
| -d | 打印当前配置信息 | dnspooh -c config.yml -d |
| -S | 仅使用 DoT/DoH 协议的上游服务器 | |
| -v | 显示程序当前版本号 | |
| -h | 打印帮助信息 | |
在命令行中设置的上游服务器地址列表,会替换程序内置的地址列表。上游服务器地址格式有如下几种:
- DNS 服务器
IP 地址。特别地,如果是 IPv6 地址,需要用 `[]` 包裹。例如:`1.1.1.1` , `[2606:4700:4700::1111]`
- DoH 服务器
URL 链接。例如:`https://1.1.1.1/dns-query`
- DoT 服务器
IP 地址加 853 端口。例如:`1.1.1.1:853`
### 2.2 配置文件
Dnspooh 使用的配置文件为 YAML 格式。一个常规的配置文件如下:
```yaml
proxy: http://127.0.0.1:8080
hosts:
- !path hosts
- https://raw.hellogithub.com/hosts
block:
- !path block.txt
rules:
- !include cn-domain.yml
middlewares:
- rules
- hosts
- block
- cache
- log
```
配置文件支持 `!path` 和 `!include` 两个扩展指令。当配置项目是一个文件名时,使用 `!path` 指令表示以当前配置文件所在路径作为文件相对路径的起始位置,如果不使用 `!path` 指令,则以程序运行路径作为文件相对路径的起始位置。 `!include` 指令用来引用外部 yaml 配置文件,当前配置文件的所在路径作为被引用配置文件相对路径的起始位置。
| 配置名 | 数据类型 | 默认 | 描述 |
| ---------------------- | ------------ | ------------ | ------------------------------------------------------------ |
| debug | Boolean | false | 控制台/终端是否输出调试信息 |
| listen | String/Array | "0.0.0.0:53" | 服务绑定本机地址。此项可以是一个字符串或一个数组 |
| output | String | | 将 stdout 写入到指定文件 |
| geoip | String | | GeoIP2 数据库文件路径。默认使用 [GeoIP2-CN](https://github.com/Hackl0us/GeoIP2-CN) |
| secure | Boolean | false | 仅使用安全(DoH / DoT)的上游 DNS 服务器 |
| ipv6 | Boolean | false | 启用 IPv6 地址的上游 DNS 服务器 |
| timeout | Integer | 5000 | 上游 DNS 服务器响应超时时间(单位:毫秒) |
| proxy | String | | 代理服务器,支持 HTTP 和 SOCKS5 代理 |
| upstreams | Array | | 替换内置上游 DNS 服务器列表 |
| upstreams+ | Array | | 追加到内置上游 DNS 服务器列表 |
| upstreams_filter | | | 筛选出可用的上游 DNS 服务器 |
| upstreams_filter.name | Array | | 筛选出名称存在于此列表中的服务器 |
| upstreams_filter.group | Array | | 筛选出分组存在于此列表中的服务器 |
| middlewares | Array | ["cache"] | 启用的中间件。列表定义顺序决定加载顺序 |
| rules | Array | | 自定义规则列表 |
| hosts | Array | | hosts 文件列表。支持 http/https 链接 |
| block | Array | | 黑名单文件列表。支持 http/https 链接 |
| cache | | | 缓存配置 |
| cache.max_size | Integer | 4096 | 最大缓存条目数 |
| cache.ttl | Integer | 86400 | 缓存有效期(单位:秒) |
| log.path | String | "access.log" | 访问日志的文件路径,日志文件为 SQLite3 数据库格式 |
| log.trace | Boolean | true | 是否记录调试跟踪信息 |
| log.payload | Boolean | true | 是否记录 DNS 请求和响应的数据 |
| http | | | HTTP 控制接口配置 |
| http.host | String | 127.0.0.1 | HTTP 服务监听地址 |
| http.port | Integer | 随机 | HTTP 服务监听端口。范围从 1024 到 65535 |
| http.timeout | Integer | 10000 | HTTP 服务超时时间(单位:毫秒) |
| http.disable | Boolean | false | 是否开启 HTTP 服务 |
| http.root | String | | Web 仪表板前端页面保存路径 |
下面的配置文件用于追加上游 DNS 服务器:
```yaml
upstreams+:
- name: my-dns
host: 192.168.1.1
proxy: http://192.168.1.1
timeout: 5000
disable: false
priority: 0
groups:
- my
- cn
- name: my-dot
host: 192.168.1.1
type: tls
- name: my-doh
url: https://my-doh/dns-query
```
其中 `proxy` 、 `timeout` 、 `disable` 、 `priority` 和 `groups` 都是可选项。
### 2.3 中间件
Dnspooh 提供下列中间件:
1. Rules 自定义规则
2. Hosts 自定义域名解析
3. Block 域名和 IP 地址黑名单
4. Cache 缓存上游服务器的解析结果
5. Log 解析日志
这些中间件可以在配置文件中开启。在默认配置下,仅启用 Cache 中间件。中间件采用装饰器模式,先加载的中间件处于封装内层,后加载的中间件处于外层。建议按照本文档中的列表顺序定义。
其中 `block` 和 `hosts` 的配置是一组文件列表。文件可以是本地文件,也可以是 http/https 链接。且当文件是链接时,还能设置更新频率:
```yaml
hosts:
- [https://raw.hellogithub.com/hosts, 3600]
```
上面的配置表示,程序每隔 3600 秒重新载入一次 https://raw.hellogithub.com/hosts 的数据。
### 2.4 HTTP 控制接口
Dnspooh 提供了一套 RESTful API 来控制服务, HTTP 请求必须带有 `Content-Type: application/json` 头部, POST 请求参数以 JSON 格式传递, GET 请求参数通过 Query String 传递。
HTTP 服务默认绑定 127.0.0.1 地址,使用 1024 到 65535 范围内的随机端口,程序启动时会在命令行终端输出 HTTP 接口的 URL 地址。
如果接口调用成功,返回一个包含 `result` 字段的 JSON 实体。其中 `result` 字段的值为接口返回值。如果接口调用失败,返回一个包含 `error` 字段的 JSON 实体。其中 `error` 字段的值为错误对象,包含 `code` 和 `message` 两个成员。一个典型的错误对象实体如下:
```json
{
"error": {
"code": 0,
"message": "执行失败"
}
}
```
#### 2.4.1 获取程序版本
**方法:** GET
**路径:** `/version`
**参数:** 无
**返回值:** String
```json
{ "result": "1.0.0" }
```
#### 2.4.2 获取服务状态
**方法:** GET
**路径:** `/status`
**参数:** 无
**返回值:** String
```json
{ "result": "RUNNING" }
```
`status` 可能的返回值如下(其中几种状态可能永远观测不到):
- INITIALIZED 已初始化
- START_PEDDING 正在启动
- RUNNING 正在运行
- RESTART_PEDDING 正在重启
- STOP_PEDDING 正在停止
- STOPPED 已停止
#### 2.4.3 重启服务
重启服务不会影响 HTTP 服务。重启服务过程中会重新载入并应用配置文件,但修改配置文件中的 `http` 下的配置不会因重启服务而生效。
**方法:** POST
**路径:** `/restart`
**参数:** 无
**返回值:** Boolean
```json
{ "result": true }
```
#### 2.4.4 获取上游 DNS 服务器
**方法:** GET
**路径:** `/upstream`
**参数:** 无
**返回值:** JSON 对象
```json
{
"result": {
"primary": {
"name": "cloudflare-1",
"disable": false,
"groups": ["cloudflare", "global", "ipv4"],
"health": 100,
"host": "1.1.1.1",
"port": 53,
"priority": 988,
"type": "dns"
},
"upstreams": [
{
"name": "cloudflare-1",
"disable": false,
"groups": ["cloudflare", "global", "ipv4"],
"health": 100,
"host": "1.1.1.1",
"port": 53,
"priority": 988,
"type": "dns"
},
// ... ...
]
}
}
```
#### 2.4.5 设置主 DNS 服务器
**方法:** POST
**路径:** `/upstream/primary`
**参数:**
| 字段 | 类型 | 描述 |
| ---- | ------ | ---------------------------------- |
| name | String | 服务器名称。例如:`"cloudflare-1"` |
**返回值:** Boolean
```json
{ "result": true }
```
#### 2.4.6 测试全部 DNS 服务器
**方法:** POST
**路径:** `/upstreams/test-all`
**参数:** 无
**返回值:** Boolean
```json
{ "result": true }
```
#### 2.4.7 获取连接池
**方法:** GET
**路径:** `/pool`
**参数:** 无
**返回值:** Array
```json
{
"result": [
{ "name": "socks5://127.0.0.1:1080/udp://1.1.1.1:53", "size": 6 },
// ... ...
]
}
```
#### 2.4.8 获取配置信息
**方法:** GET
**路径:** `/config`
**参数:** 无
**返回值:** Array
```json
{
"result": [
{ "name": "debug", "value": false },
{ "name": "secure", "value": false },
{ "name": "ipv6", "value": false },
// ... ...
]
}
```
#### 2.4.9 获取解析日志
**方法:** GET
**路径:** `/logs`
**参数:**
| 字段 | 类型 | 描述 |
| ----- | ------- | ---------------------------- |
| page | Integer | 页码。可选,默认展示第一页。 |
| qname | String | 筛选域名关键字。可选。 |
| qtype | String | 筛选查询类型。可选。 |
**返回值:** JSON 对象
```json
{
"result": {
"total": 12,
"page": {
"current": 1,
"size": 50,
"count": 1
},
"logs": [
{
"id": 12,
"created_at": "2023-03-08 18:49:19",
"elapsed_time": 0.004754199995659292,
"qname": "www.google.com.",
"qtype": "AAAA",
"success": 1,
"traceback": ["cache", "block", "Server", "alidns-1"],
"error": null
},
// ... ...
]
}
}
```
#### 2.4.10 清空解析日志
**方法:** POST
**路径:** `/logs/clear`
**参数:** 无
**返回值:** Boolean
```json
{ "result": true }
```
#### 2.4.11 域名解析
**方法:** POST
**路径:** `/dns-query`
**参数:**
| 字段 | 类型 | 描述 |
| ------ | ------ | ------ |
| domain | String | 域名。 |
**返回值:**String
```json
{ "result": ";; ->>HEADER<<- opcode: QUERY, status: NOERROR, ... ..." }
```
#### 2.4.12 查询 IP 地理位置
**方法:** POST
**路径:** `/geoip2-query`
**参数:** 无
**返回值:** JSON 对象
```json
{
"result": {
"country": {
"geoname_id": 1814991,
"is_in_european_union": false,
"iso_code": "CN",
"names": {
"de": "China",
"en": "China",
"es": "China",
"fr": "Chine",
"ja": "\u4e2d\u56fd",
"pt-BR": "China",
"ru": "\u041a\u0438\u0442\u0430\u0439",
"zh-CN": "\u4e2d\u56fd"
}
}
}
}
```
### 2.5 Web 管理界面
![Screenshot](./assets/screenshot.png?raw=true)
要启用 Web 管理界面需要在配置文件中指定前端文件的保存路径:
```yaml
http
root: dashboard/public
```
在发布的可执行软件包中已经预置了 Web 前端而无需另外配置。
## 3. 自定义规则
通过自定义规则中间件,可以实现按条件屏蔽域名、自定义解析结果等操作。可以在配置文件的 `rules` 单元中设置一组或多组规则,每组规则由 `if` 、 `then` 、 `before` 、 `after` 、 `end` 字段组合而成。根据不同的需求,一组规则可以由 `if/then/end` 字段组成;或者由 `if/before/after/end` 字段组成。其中 `end` 字段是可选的,表示命中并处理完此条规则后是否停止处理后续规则,默认值为 `false` ; `if` 字段是一个表达式,当表达式结果为真时,则表示命中这条规则; `then` 字段是一条语句,可以在此处直接拦截 DNS 解析请求,直接返回 NXDOMAIN (域名不存在)或自定义解析结果,而不会将请求转发到上游服务器; `before` 字段是一组逗号分隔的命令语句,在 DNS 解析请求被转发到上游服务器之前被处理,可以用于指定上游服务器以及替换请求中的域名; `after` 字段也是一组逗号分隔的命令语句,在 DNS 解析结果从上游服务器返回之后被处理,可以根据返回的结果进行修改操作或执行外部命令。
配置例子:
```yaml
rules:
- if: (lianmeng, adwords, adservice) in domian
then: block
end: true
- if: domain ends with (.cn, .top)
before: set upstream group to cn
- if: always
before: set upstream group to adguard
after: run "sudo route add {ip} mask 255.255.255.255 192.168.1.1" where geoip is cn
```
上面的配置作用是:
1. 屏蔽含有 lianmeng 、 adwords 、 adservice 关键字的域名;
2. 让 .cn 和 .top 域名使用国内的 DNS 服务器解析;
3. 默认使用 adguard 作为上游域名解析服务器。adguard 服务器可以屏蔽所有广告域名;
4. 当返回的解析结果中包含国内 IP 时,将此 IP 加入本机路由表,使用 192.168.1.1 网关路由(当开启全局 VPN 时,使用本地网络访问国内 IP )。
所有的表达式都支持 `not` 、 `and` 和 `or` 逻辑运算,按优先级排列如下:
1. not *expr*
2. *expr* and *expr*
3. *expr* or *expr*
可以用圆括号运算符 `(` 与 `)` 来改变逻辑运算符的优先级。
```yaml
rules:
- if: (domain ends with .cn or domain ends with .top) and not blog in domain
then: block
end: true
```
上面的配置作用是,如果是 .cn 或 .top 域名,且域名中没有包含 blog 关键字,则屏蔽。
### 3.1 if 表达式
if 字段由一个或多个判断条件组成的逻辑运算表达式。支持的判断条件有:
- domain is *domain*
域名等于 *domain*
- domain is (*domain1*, *domain2*, ...)
域名与列表中任一 *domain* 相等,等价于 domain is *domain1* or domain is *domain2* or ...
- domain is not *domain*
域名不等于 *domain* ,等价于 not domain is *domain*
- domain is not (*domain1*, *domain2*, ...)
域名不等于列表中的任何 *domain* ,等价于 domain is not *domain1* and domain is not *domain2* and ...
- *keyword* in domain
域名包含 *keyword*
- (*keyword1*, *keyword2*, ...) in domain
域名包含列表中任一 *keyword* ,等价于 *keyword1* in domain or *keyword2* in domain or ...
- *keyword* not in domain
域名不包含 *keyword* ,等价于 not *keyword* in domain
- (*keyword1*, *keyword2*, ...) not in domain
域名不包含列表中的任何 *keyword* ,等价于 *keyword1* not in domain and *keyword2* not in domain and ...
- domain starts with *prefix*
域名前缀为 *prefix*
- domain starts with (*prefix1*, *prefix2*, ...)
域名前缀是列表中的任一 *prefix* ,等价于 domain starts with *prefix1* or domain starts with *prefix2* or ...
- domain starts without *prefix*
域名前缀不为 *prefix* ,等价于 not domain starts with *prefix*
- domain starts without (*prefix1*, *prefix2*, ...)
域名前缀不为列表中的任何 *prefix* ,等价于 domain starts without *prefix1* and domain starts without *prefix2* and ...
- domain ends with *suffix*
域名后缀为 *suffix*
- domain ends with (*suffix1*, *suffix2*, ...)
域名后缀为列表中的任一 *suffix* ,等价于 domain starts with *suffix1* or domain starts with *suffix2* or ...
- domain ends without *suffix*
域名后缀不为 *suffix* ,等价于 not domain ends with *suffix*
- domain ends without (*suffix1*, *suffix2*, ...)
域名后缀不为列表中的任何 *suffix* ,等价于 domain ends without *suffix1* and domain ends without *suffix2* and ...
- domain match /*regex*/
域名完整匹配正则表达式 *regex*
- always
总是为真
### 3.2 then 语句
then 字段可以是下列任意语句之一:
- block
屏蔽当前请求
- return *ip*
- return (*ip1*, *ip2*, ...)
直接返回解析结果
### 3.3 before 语句
before 字段由下列一条或多条逗号分隔的语句组成:
- set upstream group to *name*
使用 *name* 组中的上游服务器来解析域名
- set upstream name to *name*
使用名称为 *name* 的上游服务器来解析域名
- replace domain by *domain*
将请求中的域名替换为 *domain*
- set proxy on
启用代理服务器访问上游服务器(须在配置文件中设置 proxy 项)
- set proxy off
禁用代理服务器访问上游服务器
- set proxy to *proxy*
指定代理服务器访问上游服务器。*proxy* 格式如 http://127.0.0.1:8080 或 socks5://127.0.0.1:1080
### 3.4 after 语句
- block if *expr1*
当解析结果满足条件( *expr1* 表达式为真)时,屏蔽域名
- return *ip* if *expr1*
当解析结果满足条件( *expr1* 表达式为真)时,用 *ip* 替代解析结果
- return (*ip1*, *ip2*, ...) if *expr1*
- append record *ip*
在上游服务器返回的解析结果后追加记录
- append record (*ip1*, *ip2*, ...)
- append record *ip* if *expr1*
- append record (*ip1*, *ip2*, ...) if *expr1*
- insert record *ip*
在上游服务器返回的解析结果前插入记录
- insert record (*ip1*, *ip2*, ...)
- insert record *ip* if *expr1*
- insert record (*ip1*, *ip2*, ...) if *expr1*
- remove record where *expr2*
从解析结果中移除满足条件( *expr2* 表达式为真)的记录
- replace record by *ip* where *expr2*
用 *ip* 替换满足条件( *expr2* 表达式为真)的记录
- run "*command*" where *expr2*
当解析结果中存在满足条件的记录时,执行 *command* 命令。命令需要用半角双引号包裹,命令中可以使用 `{ip}` 占位符表示当前记录的 IP 地址。
#### 3.4.1 expr1 类型表达式
- any ip is *ip*
解析结果中存在 IP 地址等于 *ip* 的记录
- any ip is (*ip1*, *ip2*, ...)
- any ip is not *ip*
- any ip is not (*ip1*, *ip2*, ...)
- any ip in *cidr*
解析结果中存在 IP 地址在 *cidr* 范围内的记录。 *cidr* 使用 IP-CIDR 格式表示,如 192.168.1.1/24
- any ip in (*cidr1*, *cidr2*, ...)
- any ip not in *cidr*
- any ip not in (*cidr1*, *cidr2*, ...)
- any geoip is *country*
解析结果中存在 IP 地址所在国为 *country* 的记录
- any geoip is not *country*
- all ip is *ip*
解析结果中所有记录的 IP 地址都等于 *ip*
- all ip is (*ip1*, *ip2*, ...)
- all ip is not *ip*
- all ip is not (*ip1*, *ip2*, ...)
- all ip in *cidr*
解析结果中所有记录的 IP 地址都在 *cidr* 范围内
- all ip in (*cidr1*, *cidr2*, ...)
- all ip not in *cidr*
- all ip not in (*cidr1*, *cidr2*, ...)
- all geoip is *country*
解析结果中所有记录的 IP 所在国都为 *country*
- all geoip is not *country*
#### 3.4.2 expr2 类型表达式
- ip is *ip*
- ip is (*ip1*, *ip2*, ....)
- ip is not *ip*
- ip is not (*ip1*, *ip2*, ....)
- ip in *cidr*
- ip in (*cidr1*, *cidr2*, ...)
- ip not in *cidr*
- ip not in (*cidr1*, *cidr2*, ...)
- geoip is *country*
- geoip is not *country*
- first
第一条记录
- last
最后一条记录
## 4. 特性
- 如果 DNS 解析请求中包含多条查询,会被逐条拆分后发送至上游服务器,并在返回响应时重新组合。这么做的目的是为了方便中间件处理;
- 程序在引导时会优先使用 priority 值最大的 upstream 来解析 DoH 服务器的域名。默认使用 cloudflare-tls 服务器进行引导时解析;
- 程序启动时会测试配置中所有的上游服务器,并将响应最快的服务器设置为主服务器;
- 程序内置的 GeoIP2 数据库仅包含中国 IP 段数据,只能返回 `cn` 或空。要使用完整的 GeoIP2 数据库,可以在配置文件中指定数据库文件;
- 程序内置的上游 DNS 解析服务器包括:[Cloudflare DNS](https://1.1.1.1/dns/) (cloudflare), [Google Public DNS](https://developers.google.com/speed/public-dns) (google), [阿里公共DNS](https://alidns.com/) (alidns), [114DNS](https://www.114dns.com/) (114dns), [OneDNS ](https://www.onedns.net/)(onedns), [DNSPod](https://www.dnspod.cn/) (dnspod), [百度DNS](https://dudns.baidu.com/)(baidu), [OpenDNS](https://www.opendns.com/) (opendns), [AdGuard DNS](https://adguard-dns.io/) (adguard) 。这些服务器按照服务供应商的名称(见括号内)分为不同组;根据服务器所在地,分为 cn 组和 global 组;根据服务器网络类型,分为 ipv4 组和 ipv6 组。
## 5. 常用命令
模块构建打包(需要安装 build 模块):
```shell
pip install build
python -m build
```
运行单元测试:
```shell
python -m unittest tests
```
项目发布的可执行文件使用 [Nuitka-winsvc](https://github.com/tabris17/Nuitka-winsvc) 编译。首先安装依赖的包:
```shell
pip install nuitka ordered-set zstandard dnspooh
```
官方发布的 Windows 程序使用如下 Nuitka 命令编译:
```shell
nuitka --standalone --output-dir=build --output-filename=dnspooh --windows-icon-from-ico=./assets/favicon.ico --include-package-data=dnspooh --onefile --windows-service --windows-service-name=dnspooh --windows-service-display-name=Dnspooh --windows-service-description="A lightweight DNS MitM proxy" main.py
```
启动 Web 管理界面前端开发环境:
```shell
npm i
npm run dev
```
构建 Web 管理界面前端:
```shell
npm run build
```
Raw data
{
"_id": null,
"home_page": "https://github.com/tabris17/dnspooh",
"name": "dnspooh",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": null,
"author": "tabris17",
"author_email": "tabris17 <tabris17.cn@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/9a/38/1e25888bce6ebc465251f377b20536ad342226c6ae03e60c13cd3ea629ae/dnspooh-1.3.3.tar.gz",
"platform": null,
"description": "# Dnspooh\r\n\r\nDnspooh \u662f\u4e00\u4e2a\u8f7b\u91cf\u7ea7 DNS \u4e2d\u7ee7\u548c\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u53ef\u4ee5\u4e3a\u672c\u673a\u6216\u672c\u5730\u7f51\u7edc\u63d0\u4f9b\u5b89\u5168\u7684 DNS \u89e3\u6790\u670d\u52a1\u3002\u7a0b\u5e8f\u63d0\u4f9b\u4e00\u4e2a\u7f51\u9875\u524d\u7aef\u7ba1\u7406\u754c\u9762\uff0c\u652f\u6301\u4ee3\u7406\u670d\u52a1\u5668\u3001 hosts \u6587\u4ef6\u3001\u57df\u540d\u548c IP \u9ed1\u540d\u5355\uff0c\u4ee5\u53ca\u81ea\u5b9a\u4e49\u89c4\u5219\u3002\r\n\r\n## 1. \u5b89\u88c5\u548c\u8fd0\u884c\r\n\r\nDnspooh \u4f7f\u7528 Python \u8bed\u8a00\u7f16\u5199\uff0c\u8fd0\u884c Dnspooh \u9700\u8981 Python 3.10 \u53ca\u4ee5\u4e0a\u7248\u672c\u3002\u7a0b\u5e8f\u80fd\u4ee5 Python \u6a21\u5757\u7684\u65b9\u5f0f\u8fd0\u884c\uff0c\u4e5f\u80fd\u4ee5\u6e90\u4ee3\u7801\u7684\u65b9\u5f0f\u76f4\u63a5\u8fd0\u884c\u3002\u6b64\u5916\uff0c\u9879\u76ee\u8fd8\u63d0\u4f9b\u4e86\u6253\u5305\u540e\u7684 Windows \u53ef\u6267\u884c\u6587\u4ef6\u3002\r\n\r\n### 1.1 Python \u6a21\u5757\r\n\r\n\u901a\u8fc7 pip \u5b89\u88c5\u6a21\u5757\uff1a\r\n\r\n```shell\r\npip install dnspooh\r\n```\r\n\r\n\u8fd0\u884c Dnspooh \uff1a\r\n\r\n```shell\r\ndnspooh --help\r\n```\r\n\r\n\u6216\u8005\uff1a\r\n\r\n```shell\r\npython -m dnspooh --help\r\n```\r\n\r\n### 1.2 \u6e90\u4ee3\u7801\r\n\r\n```shell\r\ngit clone https://githu.com/tabris17/dnspooh\r\ncd dnspooh\r\npip install -r requirements.txt\r\n```\r\n\r\n\u8fd0\u884c Dnspooh \uff1a\r\n\r\n```shell\r\npython main.py --help\r\n```\r\n\r\n### 1.3 \u53ef\u6267\u884c\u6587\u4ef6\r\n\r\n\u53ef\u4ee5\u5728 <https://github.com/tabris17/dnspooh/releases> \u9875\u9762\u4e2d\u4e0b\u8f7d\u8f6f\u4ef6\u7684 Windows \u53ef\u6267\u884c\u6587\u4ef6\u3002\u5c06\u4e0b\u8f7d\u7684 `dnspooh-X.Y.Z-win-amd64.zip` \uff08\u5176\u4e2d X.Y.Z \u662f\u7248\u672c\u53f7\uff09\u6587\u4ef6\u89e3\u538b\u7f29\u4fdd\u5b58\u5728\u672c\u5730\uff0c\u8fd0\u884c\u5176\u4e2d\u7684 `dnspooh.exe` \u53ef\u6267\u884c\u6587\u4ef6\u3002\r\n\r\nWindows \u5e73\u53f0\u4e0b\u8fd8\u53ef\u4ee5\u4f7f\u7528 scoop \u8fdb\u884c\u5b89\u88c5\uff1a\r\n\r\n```shell\r\nscoop install https://github.com/tabris17/dnspooh/releases/latest/download/dnspooh.json\r\n```\r\n\r\n## 2. \u4f7f\u7528\u65b9\u6cd5\r\n\r\n\u76f4\u63a5\u8fd0\u884c dnspooh \u5c06\u4ee5\u9ed8\u8ba4\u914d\u7f6e\u542f\u52a8\u670d\u52a1\u3002\u5728\u9ed8\u8ba4\u914d\u7f6e\u4e0b\uff0cdnspooh \u5728\u672c\u673a IPv4 \u7f51\u7edc\u63a5\u53e3\u7684 53 \u7aef\u53e3\u5f00\u542f DNS \u670d\u52a1\uff0c\u4f7f\u7528 DoT / DoH \u534f\u8bae\u7684\u4e0a\u6e38\u670d\u52a1\u5668\uff0c\u5e76\u52a0\u8f7d Cache \u4e2d\u95f4\u4ef6\u3002\r\n\r\n### 2.1 \u547d\u4ee4\u884c\u53c2\u6570\r\n\r\n\u901a\u8fc7\u547d\u4ee4\u884c\u7684 `--help` \u53c2\u6570\u53ef\u4ee5\u67e5\u770b Dnspooh \u652f\u6301\u7684\u547d\u4ee4\u884c\u53c2\u6570\uff1a\r\n\r\n```text\r\nusage: dnspooh [-c file] [-l addr [addr ...]] [-o log] [-p dir] [-t ms] [-u dns_server [dns_server ...]] [-6] [-D] [-d] [-S] [-v] [-h]\r\n\r\nA Lightweight DNS MitM Proxy\r\n\r\n -c file, --config file\r\n config file path (example \"config.yml\")\r\n -l addr [addr ...], --listen addr [addr ...]\r\n binding to local address and port for DNS proxy server (default \"0.0.0.0:53\")\r\n -o log, --output log write stdout to the specified file\r\n -p dir, --public dir specify http server root directory\r\n -t ms, --timeout ms milliseconds for upstream DNS response timeout (default 5000 ms)\r\n -u dns_server [dns_server ...], --upstream dns_server [dns_server ...]\r\n space-separated upstream DNS servers list\r\n -6, --enable-ipv6 enable IPv6 upstream servers\r\n -D, --debug display debug message\r\n -d, --dump dump pretty config data\r\n -S, --secure-only use DoT/DoH upstream servers only\r\n -v, --version show program's version number and exit\r\n -h, --help show this help message and exit\r\n```\r\n\r\n\u53ef\u4ee5\u901a\u8fc7\u547d\u4ee4\u884c\u53c2\u6570\u548c\u914d\u7f6e\u6587\u4ef6\u6765\u5bf9\u7a0b\u5e8f\u8fdb\u884c\u8bbe\u7f6e\u3002\u901a\u8fc7\u547d\u4ee4\u884c\u53c2\u6570\u4f20\u9012\u7684\u8bbe\u7f6e\u4f18\u5148\u7ea7\u9ad8\u4e8e\u914d\u7f6e\u6587\u4ef6\u4e2d\u5bf9\u5e94\u7684\u8bbe\u7f6e\u3002\u5982\u679c\u6ca1\u6709\u6307\u5b9a\u914d\u7f6e\u6587\u4ef6\uff0c\u7a0b\u5e8f\u4f1a\u5c1d\u8bd5\u52a0\u8f7d\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\u3001\u7a0b\u5e8f\u6587\u4ef6\u6240\u5728\u76ee\u5f55\u4e2d\u7684 `config.yml` \u6216 `config\\config.yml` \u914d\u7f6e\u6587\u4ef6\u3002\r\n\r\n| \u547d\u4ee4\u884c\u53c2\u6570 | \u63cf\u8ff0 | \u4f8b\u5b50 |\r\n| ------------------------------ | ------------------------------------ | ---------------------------------- |\r\n| -c file | \u52a0\u8f7d\u914d\u7f6e\u6587\u4ef6 | dnspooh -c config.yml |\r\n| -l addr [addr ...] | \u7ed1\u5b9a\u672c\u5730\u7f51\u7edc\u5730\u5740\u5217\u8868 | dnspooh -l 0.0.0.0 [::] |\r\n| -o log | \u5c06 stdout \u5199\u5165\u5230 log \u6587\u4ef6 | dnspooh -o output.log |\r\n| -p dir | \u6307\u5b9a HTTP \u670d\u52a1\u7684\u9759\u6001\u6587\u4ef6\u6839\u76ee\u5f55 | dnspooh -p public |\r\n| -t ms | \u8bbe\u7f6e\u4e0a\u6e38\u670d\u52a1\u5668\u8d85\u65f6\u65f6\u95f4\uff08\u5355\u4f4d\uff1a\u6beb\u79d2\uff09 | dnspooh -t 5000 |\r\n| -u dns_server [dns_server ...] | \u4e0a\u6e38\u670d\u52a1\u5668\u5730\u5740\u5217\u8868 | dnspooh -u 114.114.114.114 1.1.1.1 |\r\n| -6 | \u542f\u7528 IPv6 \u670d\u52a1\u5668 | |\r\n| -D | \u8f93\u51fa\u8c03\u8bd5\u4fe1\u606f | |\r\n| -d | \u6253\u5370\u5f53\u524d\u914d\u7f6e\u4fe1\u606f | dnspooh -c config.yml -d |\r\n| -S | \u4ec5\u4f7f\u7528 DoT/DoH \u534f\u8bae\u7684\u4e0a\u6e38\u670d\u52a1\u5668 | |\r\n| -v | \u663e\u793a\u7a0b\u5e8f\u5f53\u524d\u7248\u672c\u53f7 | |\r\n| -h | \u6253\u5370\u5e2e\u52a9\u4fe1\u606f | |\r\n\r\n\u5728\u547d\u4ee4\u884c\u4e2d\u8bbe\u7f6e\u7684\u4e0a\u6e38\u670d\u52a1\u5668\u5730\u5740\u5217\u8868\uff0c\u4f1a\u66ff\u6362\u7a0b\u5e8f\u5185\u7f6e\u7684\u5730\u5740\u5217\u8868\u3002\u4e0a\u6e38\u670d\u52a1\u5668\u5730\u5740\u683c\u5f0f\u6709\u5982\u4e0b\u51e0\u79cd\uff1a\r\n\r\n- DNS \u670d\u52a1\u5668 \r\n IP \u5730\u5740\u3002\u7279\u522b\u5730\uff0c\u5982\u679c\u662f IPv6 \u5730\u5740\uff0c\u9700\u8981\u7528 `[]` \u5305\u88f9\u3002\u4f8b\u5982\uff1a`1.1.1.1` \uff0c `[2606:4700:4700::1111]`\r\n- DoH \u670d\u52a1\u5668 \r\n URL \u94fe\u63a5\u3002\u4f8b\u5982\uff1a`https://1.1.1.1/dns-query`\r\n- DoT \u670d\u52a1\u5668 \r\n IP \u5730\u5740\u52a0 853 \u7aef\u53e3\u3002\u4f8b\u5982\uff1a`1.1.1.1:853`\r\n\r\n### 2.2 \u914d\u7f6e\u6587\u4ef6\r\n\r\nDnspooh \u4f7f\u7528\u7684\u914d\u7f6e\u6587\u4ef6\u4e3a YAML \u683c\u5f0f\u3002\u4e00\u4e2a\u5e38\u89c4\u7684\u914d\u7f6e\u6587\u4ef6\u5982\u4e0b\uff1a\r\n\r\n```yaml\r\nproxy: http://127.0.0.1:8080\r\n\r\nhosts:\r\n - !path hosts\r\n - https://raw.hellogithub.com/hosts\r\n\r\nblock:\r\n - !path block.txt\r\n\r\nrules:\r\n - !include cn-domain.yml\r\n\r\nmiddlewares:\r\n - rules\r\n - hosts\r\n - block\r\n - cache\r\n - log\r\n```\r\n\r\n\u914d\u7f6e\u6587\u4ef6\u652f\u6301 `!path` \u548c `!include` \u4e24\u4e2a\u6269\u5c55\u6307\u4ee4\u3002\u5f53\u914d\u7f6e\u9879\u76ee\u662f\u4e00\u4e2a\u6587\u4ef6\u540d\u65f6\uff0c\u4f7f\u7528 `!path` \u6307\u4ee4\u8868\u793a\u4ee5\u5f53\u524d\u914d\u7f6e\u6587\u4ef6\u6240\u5728\u8def\u5f84\u4f5c\u4e3a\u6587\u4ef6\u76f8\u5bf9\u8def\u5f84\u7684\u8d77\u59cb\u4f4d\u7f6e\uff0c\u5982\u679c\u4e0d\u4f7f\u7528 `!path` \u6307\u4ee4\uff0c\u5219\u4ee5\u7a0b\u5e8f\u8fd0\u884c\u8def\u5f84\u4f5c\u4e3a\u6587\u4ef6\u76f8\u5bf9\u8def\u5f84\u7684\u8d77\u59cb\u4f4d\u7f6e\u3002 `!include` \u6307\u4ee4\u7528\u6765\u5f15\u7528\u5916\u90e8 yaml \u914d\u7f6e\u6587\u4ef6\uff0c\u5f53\u524d\u914d\u7f6e\u6587\u4ef6\u7684\u6240\u5728\u8def\u5f84\u4f5c\u4e3a\u88ab\u5f15\u7528\u914d\u7f6e\u6587\u4ef6\u76f8\u5bf9\u8def\u5f84\u7684\u8d77\u59cb\u4f4d\u7f6e\u3002\r\n\r\n| \u914d\u7f6e\u540d | \u6570\u636e\u7c7b\u578b | \u9ed8\u8ba4 | \u63cf\u8ff0 |\r\n| ---------------------- | ------------ | ------------ | ------------------------------------------------------------ |\r\n| debug | Boolean | false | \u63a7\u5236\u53f0/\u7ec8\u7aef\u662f\u5426\u8f93\u51fa\u8c03\u8bd5\u4fe1\u606f |\r\n| listen | String/Array | \"0.0.0.0:53\" | \u670d\u52a1\u7ed1\u5b9a\u672c\u673a\u5730\u5740\u3002\u6b64\u9879\u53ef\u4ee5\u662f\u4e00\u4e2a\u5b57\u7b26\u4e32\u6216\u4e00\u4e2a\u6570\u7ec4 |\r\n| output | String | | \u5c06 stdout \u5199\u5165\u5230\u6307\u5b9a\u6587\u4ef6 |\r\n| geoip | String | | GeoIP2 \u6570\u636e\u5e93\u6587\u4ef6\u8def\u5f84\u3002\u9ed8\u8ba4\u4f7f\u7528 [GeoIP2-CN](https://github.com/Hackl0us/GeoIP2-CN) |\r\n| secure | Boolean | false | \u4ec5\u4f7f\u7528\u5b89\u5168\uff08DoH / DoT\uff09\u7684\u4e0a\u6e38 DNS \u670d\u52a1\u5668 |\r\n| ipv6 | Boolean | false | \u542f\u7528 IPv6 \u5730\u5740\u7684\u4e0a\u6e38 DNS \u670d\u52a1\u5668 |\r\n| timeout | Integer | 5000 | \u4e0a\u6e38 DNS \u670d\u52a1\u5668\u54cd\u5e94\u8d85\u65f6\u65f6\u95f4\uff08\u5355\u4f4d\uff1a\u6beb\u79d2\uff09 |\r\n| proxy | String | | \u4ee3\u7406\u670d\u52a1\u5668\uff0c\u652f\u6301 HTTP \u548c SOCKS5 \u4ee3\u7406 |\r\n| upstreams | Array | | \u66ff\u6362\u5185\u7f6e\u4e0a\u6e38 DNS \u670d\u52a1\u5668\u5217\u8868 |\r\n| upstreams+ | Array | | \u8ffd\u52a0\u5230\u5185\u7f6e\u4e0a\u6e38 DNS \u670d\u52a1\u5668\u5217\u8868 |\r\n| upstreams_filter | | | \u7b5b\u9009\u51fa\u53ef\u7528\u7684\u4e0a\u6e38 DNS \u670d\u52a1\u5668 |\r\n| upstreams_filter.name | Array | | \u7b5b\u9009\u51fa\u540d\u79f0\u5b58\u5728\u4e8e\u6b64\u5217\u8868\u4e2d\u7684\u670d\u52a1\u5668 |\r\n| upstreams_filter.group | Array | | \u7b5b\u9009\u51fa\u5206\u7ec4\u5b58\u5728\u4e8e\u6b64\u5217\u8868\u4e2d\u7684\u670d\u52a1\u5668 |\r\n| middlewares | Array | [\"cache\"] | \u542f\u7528\u7684\u4e2d\u95f4\u4ef6\u3002\u5217\u8868\u5b9a\u4e49\u987a\u5e8f\u51b3\u5b9a\u52a0\u8f7d\u987a\u5e8f |\r\n| rules | Array | | \u81ea\u5b9a\u4e49\u89c4\u5219\u5217\u8868 |\r\n| hosts | Array | | hosts \u6587\u4ef6\u5217\u8868\u3002\u652f\u6301 http/https \u94fe\u63a5 |\r\n| block | Array | | \u9ed1\u540d\u5355\u6587\u4ef6\u5217\u8868\u3002\u652f\u6301 http/https \u94fe\u63a5 |\r\n| cache | | | \u7f13\u5b58\u914d\u7f6e |\r\n| cache.max_size | Integer | 4096 | \u6700\u5927\u7f13\u5b58\u6761\u76ee\u6570 |\r\n| cache.ttl | Integer | 86400 | \u7f13\u5b58\u6709\u6548\u671f\uff08\u5355\u4f4d\uff1a\u79d2\uff09 |\r\n| log.path | String | \"access.log\" | \u8bbf\u95ee\u65e5\u5fd7\u7684\u6587\u4ef6\u8def\u5f84\uff0c\u65e5\u5fd7\u6587\u4ef6\u4e3a SQLite3 \u6570\u636e\u5e93\u683c\u5f0f |\r\n| log.trace | Boolean | true | \u662f\u5426\u8bb0\u5f55\u8c03\u8bd5\u8ddf\u8e2a\u4fe1\u606f |\r\n| log.payload | Boolean | true | \u662f\u5426\u8bb0\u5f55 DNS \u8bf7\u6c42\u548c\u54cd\u5e94\u7684\u6570\u636e |\r\n| http | | | HTTP \u63a7\u5236\u63a5\u53e3\u914d\u7f6e |\r\n| http.host | String | 127.0.0.1 | HTTP \u670d\u52a1\u76d1\u542c\u5730\u5740 |\r\n| http.port | Integer | \u968f\u673a | HTTP \u670d\u52a1\u76d1\u542c\u7aef\u53e3\u3002\u8303\u56f4\u4ece 1024 \u5230 65535 |\r\n| http.timeout | Integer | 10000 | HTTP \u670d\u52a1\u8d85\u65f6\u65f6\u95f4\uff08\u5355\u4f4d\uff1a\u6beb\u79d2\uff09 |\r\n| http.disable | Boolean | false | \u662f\u5426\u5f00\u542f HTTP \u670d\u52a1 |\r\n| http.root | String | | Web \u4eea\u8868\u677f\u524d\u7aef\u9875\u9762\u4fdd\u5b58\u8def\u5f84 |\r\n\r\n\u4e0b\u9762\u7684\u914d\u7f6e\u6587\u4ef6\u7528\u4e8e\u8ffd\u52a0\u4e0a\u6e38 DNS \u670d\u52a1\u5668\uff1a\r\n\r\n```yaml\r\nupstreams+:\r\n - name: my-dns\r\n host: 192.168.1.1\r\n proxy: http://192.168.1.1\r\n timeout: 5000\r\n disable: false\r\n priority: 0\r\n groups:\r\n - my\r\n - cn\r\n\r\n - name: my-dot\r\n host: 192.168.1.1\r\n type: tls\r\n\r\n - name: my-doh\r\n url: https://my-doh/dns-query\r\n```\r\n\r\n\u5176\u4e2d `proxy` \u3001 `timeout` \u3001 `disable` \u3001 `priority` \u548c `groups` \u90fd\u662f\u53ef\u9009\u9879\u3002\r\n\r\n### 2.3 \u4e2d\u95f4\u4ef6\r\n\r\nDnspooh \u63d0\u4f9b\u4e0b\u5217\u4e2d\u95f4\u4ef6\uff1a\r\n\r\n1. Rules \u81ea\u5b9a\u4e49\u89c4\u5219\r\n\r\n2. Hosts \u81ea\u5b9a\u4e49\u57df\u540d\u89e3\u6790\r\n\r\n3. Block \u57df\u540d\u548c IP \u5730\u5740\u9ed1\u540d\u5355\r\n\r\n4. Cache \u7f13\u5b58\u4e0a\u6e38\u670d\u52a1\u5668\u7684\u89e3\u6790\u7ed3\u679c\r\n\r\n5. Log \u89e3\u6790\u65e5\u5fd7\r\n\r\n\u8fd9\u4e9b\u4e2d\u95f4\u4ef6\u53ef\u4ee5\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u5f00\u542f\u3002\u5728\u9ed8\u8ba4\u914d\u7f6e\u4e0b\uff0c\u4ec5\u542f\u7528 Cache \u4e2d\u95f4\u4ef6\u3002\u4e2d\u95f4\u4ef6\u91c7\u7528\u88c5\u9970\u5668\u6a21\u5f0f\uff0c\u5148\u52a0\u8f7d\u7684\u4e2d\u95f4\u4ef6\u5904\u4e8e\u5c01\u88c5\u5185\u5c42\uff0c\u540e\u52a0\u8f7d\u7684\u4e2d\u95f4\u4ef6\u5904\u4e8e\u5916\u5c42\u3002\u5efa\u8bae\u6309\u7167\u672c\u6587\u6863\u4e2d\u7684\u5217\u8868\u987a\u5e8f\u5b9a\u4e49\u3002\r\n\r\n\u5176\u4e2d `block` \u548c `hosts` \u7684\u914d\u7f6e\u662f\u4e00\u7ec4\u6587\u4ef6\u5217\u8868\u3002\u6587\u4ef6\u53ef\u4ee5\u662f\u672c\u5730\u6587\u4ef6\uff0c\u4e5f\u53ef\u4ee5\u662f http/https \u94fe\u63a5\u3002\u4e14\u5f53\u6587\u4ef6\u662f\u94fe\u63a5\u65f6\uff0c\u8fd8\u80fd\u8bbe\u7f6e\u66f4\u65b0\u9891\u7387\uff1a\r\n\r\n```yaml\r\nhosts:\r\n - [https://raw.hellogithub.com/hosts, 3600]\r\n```\r\n\r\n\u4e0a\u9762\u7684\u914d\u7f6e\u8868\u793a\uff0c\u7a0b\u5e8f\u6bcf\u9694 3600 \u79d2\u91cd\u65b0\u8f7d\u5165\u4e00\u6b21 https://raw.hellogithub.com/hosts \u7684\u6570\u636e\u3002\r\n\r\n### 2.4 HTTP \u63a7\u5236\u63a5\u53e3\r\n\r\nDnspooh \u63d0\u4f9b\u4e86\u4e00\u5957 RESTful API \u6765\u63a7\u5236\u670d\u52a1\uff0c HTTP \u8bf7\u6c42\u5fc5\u987b\u5e26\u6709 `Content-Type: application/json` \u5934\u90e8\uff0c POST \u8bf7\u6c42\u53c2\u6570\u4ee5 JSON \u683c\u5f0f\u4f20\u9012\uff0c GET \u8bf7\u6c42\u53c2\u6570\u901a\u8fc7 Query String \u4f20\u9012\u3002\r\n\r\nHTTP \u670d\u52a1\u9ed8\u8ba4\u7ed1\u5b9a 127.0.0.1 \u5730\u5740\uff0c\u4f7f\u7528 1024 \u5230 65535 \u8303\u56f4\u5185\u7684\u968f\u673a\u7aef\u53e3\uff0c\u7a0b\u5e8f\u542f\u52a8\u65f6\u4f1a\u5728\u547d\u4ee4\u884c\u7ec8\u7aef\u8f93\u51fa HTTP \u63a5\u53e3\u7684 URL \u5730\u5740\u3002\r\n\r\n\u5982\u679c\u63a5\u53e3\u8c03\u7528\u6210\u529f\uff0c\u8fd4\u56de\u4e00\u4e2a\u5305\u542b `result` \u5b57\u6bb5\u7684 JSON \u5b9e\u4f53\u3002\u5176\u4e2d `result` \u5b57\u6bb5\u7684\u503c\u4e3a\u63a5\u53e3\u8fd4\u56de\u503c\u3002\u5982\u679c\u63a5\u53e3\u8c03\u7528\u5931\u8d25\uff0c\u8fd4\u56de\u4e00\u4e2a\u5305\u542b `error` \u5b57\u6bb5\u7684 JSON \u5b9e\u4f53\u3002\u5176\u4e2d `error` \u5b57\u6bb5\u7684\u503c\u4e3a\u9519\u8bef\u5bf9\u8c61\uff0c\u5305\u542b `code` \u548c `message` \u4e24\u4e2a\u6210\u5458\u3002\u4e00\u4e2a\u5178\u578b\u7684\u9519\u8bef\u5bf9\u8c61\u5b9e\u4f53\u5982\u4e0b\uff1a\r\n\r\n```json\r\n{\r\n \"error\": {\r\n \"code\": 0,\r\n \"message\": \"\u6267\u884c\u5931\u8d25\"\r\n }\r\n}\r\n```\r\n\r\n#### 2.4.1 \u83b7\u53d6\u7a0b\u5e8f\u7248\u672c\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/version`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** String\r\n\r\n```json\r\n{ \"result\": \"1.0.0\" }\r\n```\r\n\r\n#### 2.4.2 \u83b7\u53d6\u670d\u52a1\u72b6\u6001\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/status`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** String\r\n\r\n```json\r\n{ \"result\": \"RUNNING\" }\r\n```\r\n\r\n`status` \u53ef\u80fd\u7684\u8fd4\u56de\u503c\u5982\u4e0b\uff08\u5176\u4e2d\u51e0\u79cd\u72b6\u6001\u53ef\u80fd\u6c38\u8fdc\u89c2\u6d4b\u4e0d\u5230\uff09\uff1a\r\n\r\n- INITIALIZED \u5df2\u521d\u59cb\u5316\r\n- START_PEDDING \u6b63\u5728\u542f\u52a8\r\n- RUNNING \u6b63\u5728\u8fd0\u884c\r\n- RESTART_PEDDING \u6b63\u5728\u91cd\u542f\r\n- STOP_PEDDING \u6b63\u5728\u505c\u6b62\r\n- STOPPED \u5df2\u505c\u6b62\r\n\r\n#### 2.4.3 \u91cd\u542f\u670d\u52a1\r\n\r\n\u91cd\u542f\u670d\u52a1\u4e0d\u4f1a\u5f71\u54cd HTTP \u670d\u52a1\u3002\u91cd\u542f\u670d\u52a1\u8fc7\u7a0b\u4e2d\u4f1a\u91cd\u65b0\u8f7d\u5165\u5e76\u5e94\u7528\u914d\u7f6e\u6587\u4ef6\uff0c\u4f46\u4fee\u6539\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684 `http` \u4e0b\u7684\u914d\u7f6e\u4e0d\u4f1a\u56e0\u91cd\u542f\u670d\u52a1\u800c\u751f\u6548\u3002\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/restart`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Boolean\r\n\r\n```json\r\n{ \"result\": true }\r\n```\r\n\r\n#### 2.4.4 \u83b7\u53d6\u4e0a\u6e38 DNS \u670d\u52a1\u5668\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/upstream`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** JSON \u5bf9\u8c61\r\n\r\n```json\r\n{\r\n \"result\": {\r\n \"primary\": {\r\n \"name\": \"cloudflare-1\",\r\n \"disable\": false,\r\n \"groups\": [\"cloudflare\", \"global\", \"ipv4\"],\r\n \"health\": 100,\r\n \"host\": \"1.1.1.1\",\r\n \"port\": 53,\r\n \"priority\": 988,\r\n \"type\": \"dns\"\r\n },\r\n \"upstreams\": [\r\n {\r\n \"name\": \"cloudflare-1\",\r\n \"disable\": false,\r\n \"groups\": [\"cloudflare\", \"global\", \"ipv4\"],\r\n \"health\": 100,\r\n \"host\": \"1.1.1.1\",\r\n \"port\": 53,\r\n \"priority\": 988,\r\n \"type\": \"dns\"\r\n },\r\n // ... ...\r\n ]\r\n }\r\n}\r\n```\r\n\r\n#### 2.4.5 \u8bbe\u7f6e\u4e3b DNS \u670d\u52a1\u5668\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/upstream/primary`\r\n\r\n**\u53c2\u6570\uff1a** \r\n\r\n| \u5b57\u6bb5 | \u7c7b\u578b | \u63cf\u8ff0 |\r\n| ---- | ------ | ---------------------------------- |\r\n| name | String | \u670d\u52a1\u5668\u540d\u79f0\u3002\u4f8b\u5982\uff1a`\"cloudflare-1\"` |\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Boolean\r\n\r\n```json\r\n{ \"result\": true }\r\n```\r\n\r\n#### 2.4.6 \u6d4b\u8bd5\u5168\u90e8 DNS \u670d\u52a1\u5668\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/upstreams/test-all`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Boolean\r\n\r\n```json\r\n{ \"result\": true }\r\n```\r\n\r\n#### 2.4.7 \u83b7\u53d6\u8fde\u63a5\u6c60\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/pool`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Array\r\n\r\n```json\r\n{\r\n \"result\": [\r\n { \"name\": \"socks5://127.0.0.1:1080/udp://1.1.1.1:53\", \"size\": 6 },\r\n // ... ...\r\n ]\r\n}\r\n```\r\n\r\n#### 2.4.8 \u83b7\u53d6\u914d\u7f6e\u4fe1\u606f\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/config`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Array\r\n\r\n```json\r\n{\r\n \"result\": [\r\n { \"name\": \"debug\", \"value\": false },\r\n { \"name\": \"secure\", \"value\": false },\r\n { \"name\": \"ipv6\", \"value\": false },\r\n // ... ...\r\n ]\r\n}\r\n```\r\n\r\n#### 2.4.9 \u83b7\u53d6\u89e3\u6790\u65e5\u5fd7\r\n\r\n**\u65b9\u6cd5\uff1a** GET\r\n\r\n**\u8def\u5f84\uff1a** `/logs`\r\n\r\n**\u53c2\u6570\uff1a** \r\n\r\n| \u5b57\u6bb5 | \u7c7b\u578b | \u63cf\u8ff0 |\r\n| ----- | ------- | ---------------------------- |\r\n| page | Integer | \u9875\u7801\u3002\u53ef\u9009\uff0c\u9ed8\u8ba4\u5c55\u793a\u7b2c\u4e00\u9875\u3002 |\r\n| qname | String | \u7b5b\u9009\u57df\u540d\u5173\u952e\u5b57\u3002\u53ef\u9009\u3002 |\r\n| qtype | String | \u7b5b\u9009\u67e5\u8be2\u7c7b\u578b\u3002\u53ef\u9009\u3002 |\r\n\r\n**\u8fd4\u56de\u503c\uff1a** JSON \u5bf9\u8c61\r\n\r\n```json\r\n{\r\n \"result\": {\r\n \"total\": 12,\r\n \"page\": {\r\n \"current\": 1,\r\n \"size\": 50,\r\n \"count\": 1\r\n },\r\n \"logs\": [\r\n {\r\n \"id\": 12,\r\n \"created_at\": \"2023-03-08 18:49:19\",\r\n \"elapsed_time\": 0.004754199995659292,\r\n \"qname\": \"www.google.com.\",\r\n \"qtype\": \"AAAA\",\r\n \"success\": 1,\r\n \"traceback\": [\"cache\", \"block\", \"Server\", \"alidns-1\"],\r\n \"error\": null\r\n },\r\n // ... ...\r\n ]\r\n }\r\n}\r\n```\r\n\r\n#### 2.4.10 \u6e05\u7a7a\u89e3\u6790\u65e5\u5fd7\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/logs/clear`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** Boolean\r\n\r\n```json\r\n{ \"result\": true }\r\n```\r\n\r\n#### 2.4.11 \u57df\u540d\u89e3\u6790\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/dns-query`\r\n\r\n**\u53c2\u6570\uff1a** \r\n\r\n| \u5b57\u6bb5 | \u7c7b\u578b | \u63cf\u8ff0 |\r\n| ------ | ------ | ------ |\r\n| domain | String | \u57df\u540d\u3002 |\r\n\r\n**\u8fd4\u56de\u503c\uff1a**String\r\n\r\n```json\r\n{ \"result\": \";; ->>HEADER<<- opcode: QUERY, status: NOERROR, ... ...\" }\r\n```\r\n\r\n#### 2.4.12 \u67e5\u8be2 IP \u5730\u7406\u4f4d\u7f6e\r\n\r\n**\u65b9\u6cd5\uff1a** POST\r\n\r\n**\u8def\u5f84\uff1a** `/geoip2-query`\r\n\r\n**\u53c2\u6570\uff1a** \u65e0\r\n\r\n**\u8fd4\u56de\u503c\uff1a** JSON \u5bf9\u8c61\r\n\r\n```json\r\n{\r\n \"result\": {\r\n \"country\": {\r\n \"geoname_id\": 1814991,\r\n \"is_in_european_union\": false,\r\n \"iso_code\": \"CN\",\r\n \"names\": {\r\n \"de\": \"China\",\r\n \"en\": \"China\",\r\n \"es\": \"China\",\r\n \"fr\": \"Chine\",\r\n \"ja\": \"\\u4e2d\\u56fd\",\r\n \"pt-BR\": \"China\",\r\n \"ru\": \"\\u041a\\u0438\\u0442\\u0430\\u0439\",\r\n \"zh-CN\": \"\\u4e2d\\u56fd\"\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\n### 2.5 Web \u7ba1\u7406\u754c\u9762\r\n\r\n![Screenshot](./assets/screenshot.png?raw=true)\r\n\r\n\u8981\u542f\u7528 Web \u7ba1\u7406\u754c\u9762\u9700\u8981\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u6307\u5b9a\u524d\u7aef\u6587\u4ef6\u7684\u4fdd\u5b58\u8def\u5f84\uff1a\r\n\r\n```yaml\r\nhttp\r\n root: dashboard/public\r\n```\r\n\r\n\u5728\u53d1\u5e03\u7684\u53ef\u6267\u884c\u8f6f\u4ef6\u5305\u4e2d\u5df2\u7ecf\u9884\u7f6e\u4e86 Web \u524d\u7aef\u800c\u65e0\u9700\u53e6\u5916\u914d\u7f6e\u3002\r\n\r\n## 3. \u81ea\u5b9a\u4e49\u89c4\u5219\r\n\r\n\u901a\u8fc7\u81ea\u5b9a\u4e49\u89c4\u5219\u4e2d\u95f4\u4ef6\uff0c\u53ef\u4ee5\u5b9e\u73b0\u6309\u6761\u4ef6\u5c4f\u853d\u57df\u540d\u3001\u81ea\u5b9a\u4e49\u89e3\u6790\u7ed3\u679c\u7b49\u64cd\u4f5c\u3002\u53ef\u4ee5\u5728\u914d\u7f6e\u6587\u4ef6\u7684 `rules` \u5355\u5143\u4e2d\u8bbe\u7f6e\u4e00\u7ec4\u6216\u591a\u7ec4\u89c4\u5219\uff0c\u6bcf\u7ec4\u89c4\u5219\u7531 `if` \u3001 `then` \u3001 `before` \u3001 `after` \u3001 `end` \u5b57\u6bb5\u7ec4\u5408\u800c\u6210\u3002\u6839\u636e\u4e0d\u540c\u7684\u9700\u6c42\uff0c\u4e00\u7ec4\u89c4\u5219\u53ef\u4ee5\u7531 `if/then/end` \u5b57\u6bb5\u7ec4\u6210\uff1b\u6216\u8005\u7531 `if/before/after/end` \u5b57\u6bb5\u7ec4\u6210\u3002\u5176\u4e2d `end` \u5b57\u6bb5\u662f\u53ef\u9009\u7684\uff0c\u8868\u793a\u547d\u4e2d\u5e76\u5904\u7406\u5b8c\u6b64\u6761\u89c4\u5219\u540e\u662f\u5426\u505c\u6b62\u5904\u7406\u540e\u7eed\u89c4\u5219\uff0c\u9ed8\u8ba4\u503c\u4e3a `false` \uff1b `if` \u5b57\u6bb5\u662f\u4e00\u4e2a\u8868\u8fbe\u5f0f\uff0c\u5f53\u8868\u8fbe\u5f0f\u7ed3\u679c\u4e3a\u771f\u65f6\uff0c\u5219\u8868\u793a\u547d\u4e2d\u8fd9\u6761\u89c4\u5219\uff1b `then` \u5b57\u6bb5\u662f\u4e00\u6761\u8bed\u53e5\uff0c\u53ef\u4ee5\u5728\u6b64\u5904\u76f4\u63a5\u62e6\u622a DNS \u89e3\u6790\u8bf7\u6c42\uff0c\u76f4\u63a5\u8fd4\u56de NXDOMAIN \uff08\u57df\u540d\u4e0d\u5b58\u5728\uff09\u6216\u81ea\u5b9a\u4e49\u89e3\u6790\u7ed3\u679c\uff0c\u800c\u4e0d\u4f1a\u5c06\u8bf7\u6c42\u8f6c\u53d1\u5230\u4e0a\u6e38\u670d\u52a1\u5668\uff1b `before` \u5b57\u6bb5\u662f\u4e00\u7ec4\u9017\u53f7\u5206\u9694\u7684\u547d\u4ee4\u8bed\u53e5\uff0c\u5728 DNS \u89e3\u6790\u8bf7\u6c42\u88ab\u8f6c\u53d1\u5230\u4e0a\u6e38\u670d\u52a1\u5668\u4e4b\u524d\u88ab\u5904\u7406\uff0c\u53ef\u4ee5\u7528\u4e8e\u6307\u5b9a\u4e0a\u6e38\u670d\u52a1\u5668\u4ee5\u53ca\u66ff\u6362\u8bf7\u6c42\u4e2d\u7684\u57df\u540d\uff1b `after` \u5b57\u6bb5\u4e5f\u662f\u4e00\u7ec4\u9017\u53f7\u5206\u9694\u7684\u547d\u4ee4\u8bed\u53e5\uff0c\u5728 DNS \u89e3\u6790\u7ed3\u679c\u4ece\u4e0a\u6e38\u670d\u52a1\u5668\u8fd4\u56de\u4e4b\u540e\u88ab\u5904\u7406\uff0c\u53ef\u4ee5\u6839\u636e\u8fd4\u56de\u7684\u7ed3\u679c\u8fdb\u884c\u4fee\u6539\u64cd\u4f5c\u6216\u6267\u884c\u5916\u90e8\u547d\u4ee4\u3002\r\n\r\n\u914d\u7f6e\u4f8b\u5b50\uff1a\r\n\r\n```yaml\r\nrules:\r\n - if: (lianmeng, adwords, adservice) in domian\r\n then: block\r\n end: true\r\n\r\n - if: domain ends with (.cn, .top)\r\n before: set upstream group to cn\r\n\r\n - if: always\r\n before: set upstream group to adguard\r\n after: run \"sudo route add {ip} mask 255.255.255.255 192.168.1.1\" where geoip is cn\r\n```\r\n\r\n\u4e0a\u9762\u7684\u914d\u7f6e\u4f5c\u7528\u662f\uff1a\r\n\r\n1. \u5c4f\u853d\u542b\u6709 lianmeng \u3001 adwords \u3001 adservice \u5173\u952e\u5b57\u7684\u57df\u540d\uff1b\r\n2. \u8ba9 .cn \u548c .top \u57df\u540d\u4f7f\u7528\u56fd\u5185\u7684 DNS \u670d\u52a1\u5668\u89e3\u6790\uff1b\r\n3. \u9ed8\u8ba4\u4f7f\u7528 adguard \u4f5c\u4e3a\u4e0a\u6e38\u57df\u540d\u89e3\u6790\u670d\u52a1\u5668\u3002adguard \u670d\u52a1\u5668\u53ef\u4ee5\u5c4f\u853d\u6240\u6709\u5e7f\u544a\u57df\u540d\uff1b\r\n4. \u5f53\u8fd4\u56de\u7684\u89e3\u6790\u7ed3\u679c\u4e2d\u5305\u542b\u56fd\u5185 IP \u65f6\uff0c\u5c06\u6b64 IP \u52a0\u5165\u672c\u673a\u8def\u7531\u8868\uff0c\u4f7f\u7528 192.168.1.1 \u7f51\u5173\u8def\u7531\uff08\u5f53\u5f00\u542f\u5168\u5c40 VPN \u65f6\uff0c\u4f7f\u7528\u672c\u5730\u7f51\u7edc\u8bbf\u95ee\u56fd\u5185 IP \uff09\u3002\r\n\r\n\u6240\u6709\u7684\u8868\u8fbe\u5f0f\u90fd\u652f\u6301 `not` \u3001 `and` \u548c `or` \u903b\u8f91\u8fd0\u7b97\uff0c\u6309\u4f18\u5148\u7ea7\u6392\u5217\u5982\u4e0b\uff1a\r\n\r\n1. not *expr*\r\n2. *expr* and *expr*\r\n3. *expr* or *expr*\r\n\r\n\u53ef\u4ee5\u7528\u5706\u62ec\u53f7\u8fd0\u7b97\u7b26 `(` \u4e0e `)` \u6765\u6539\u53d8\u903b\u8f91\u8fd0\u7b97\u7b26\u7684\u4f18\u5148\u7ea7\u3002\r\n\r\n```yaml\r\nrules:\r\n - if: (domain ends with .cn or domain ends with .top) and not blog in domain\r\n then: block\r\n end: true\r\n```\r\n\r\n\u4e0a\u9762\u7684\u914d\u7f6e\u4f5c\u7528\u662f\uff0c\u5982\u679c\u662f .cn \u6216 .top \u57df\u540d\uff0c\u4e14\u57df\u540d\u4e2d\u6ca1\u6709\u5305\u542b blog \u5173\u952e\u5b57\uff0c\u5219\u5c4f\u853d\u3002\r\n\r\n### 3.1 if \u8868\u8fbe\u5f0f\r\n\r\nif \u5b57\u6bb5\u7531\u4e00\u4e2a\u6216\u591a\u4e2a\u5224\u65ad\u6761\u4ef6\u7ec4\u6210\u7684\u903b\u8f91\u8fd0\u7b97\u8868\u8fbe\u5f0f\u3002\u652f\u6301\u7684\u5224\u65ad\u6761\u4ef6\u6709\uff1a\r\n\r\n- domain is *domain* \r\n \u57df\u540d\u7b49\u4e8e *domain*\r\n- domain is (*domain1*, *domain2*, ...) \r\n \u57df\u540d\u4e0e\u5217\u8868\u4e2d\u4efb\u4e00 *domain* \u76f8\u7b49\uff0c\u7b49\u4ef7\u4e8e domain is *domain1* or domain is *domain2* or ... \r\n- domain is not *domain* \r\n \u57df\u540d\u4e0d\u7b49\u4e8e *domain* \uff0c\u7b49\u4ef7\u4e8e not domain is *domain*\r\n- domain is not (*domain1*, *domain2*, ...) \r\n \u57df\u540d\u4e0d\u7b49\u4e8e\u5217\u8868\u4e2d\u7684\u4efb\u4f55 *domain* \uff0c\u7b49\u4ef7\u4e8e domain is not *domain1* and domain is not *domain2* and ...\r\n- *keyword* in domain \r\n \u57df\u540d\u5305\u542b *keyword*\r\n- (*keyword1*, *keyword2*, ...) in domain \r\n \u57df\u540d\u5305\u542b\u5217\u8868\u4e2d\u4efb\u4e00 *keyword* \uff0c\u7b49\u4ef7\u4e8e *keyword1* in domain or *keyword2* in domain or ...\r\n- *keyword* not in domain \r\n \u57df\u540d\u4e0d\u5305\u542b *keyword* \uff0c\u7b49\u4ef7\u4e8e not *keyword* in domain\r\n- (*keyword1*, *keyword2*, ...) not in domain \r\n \u57df\u540d\u4e0d\u5305\u542b\u5217\u8868\u4e2d\u7684\u4efb\u4f55 *keyword* \uff0c\u7b49\u4ef7\u4e8e *keyword1* not in domain and *keyword2* not in domain and ...\r\n- domain starts with *prefix* \r\n \u57df\u540d\u524d\u7f00\u4e3a *prefix*\r\n- domain starts with (*prefix1*, *prefix2*, ...) \r\n \u57df\u540d\u524d\u7f00\u662f\u5217\u8868\u4e2d\u7684\u4efb\u4e00 *prefix* \uff0c\u7b49\u4ef7\u4e8e domain starts with *prefix1* or domain starts with *prefix2* or ... \r\n- domain starts without *prefix* \r\n \u57df\u540d\u524d\u7f00\u4e0d\u4e3a *prefix* \uff0c\u7b49\u4ef7\u4e8e not domain starts with *prefix* \r\n- domain starts without (*prefix1*, *prefix2*, ...) \r\n \u57df\u540d\u524d\u7f00\u4e0d\u4e3a\u5217\u8868\u4e2d\u7684\u4efb\u4f55 *prefix* \uff0c\u7b49\u4ef7\u4e8e domain starts without *prefix1* and domain starts without *prefix2* and ...\r\n- domain ends with *suffix* \r\n \u57df\u540d\u540e\u7f00\u4e3a *suffix*\r\n- domain ends with (*suffix1*, *suffix2*, ...) \r\n \u57df\u540d\u540e\u7f00\u4e3a\u5217\u8868\u4e2d\u7684\u4efb\u4e00 *suffix* \uff0c\u7b49\u4ef7\u4e8e domain starts with *suffix1* or domain starts with *suffix2* or ... \r\n- domain ends without *suffix* \r\n \u57df\u540d\u540e\u7f00\u4e0d\u4e3a *suffix* \uff0c\u7b49\u4ef7\u4e8e not domain ends with *suffix* \r\n- domain ends without (*suffix1*, *suffix2*, ...) \r\n \u57df\u540d\u540e\u7f00\u4e0d\u4e3a\u5217\u8868\u4e2d\u7684\u4efb\u4f55 *suffix* \uff0c\u7b49\u4ef7\u4e8e domain ends without *suffix1* and domain ends without *suffix2* and ... \r\n- domain match /*regex*/ \r\n \u57df\u540d\u5b8c\u6574\u5339\u914d\u6b63\u5219\u8868\u8fbe\u5f0f *regex*\r\n- always \r\n \u603b\u662f\u4e3a\u771f\r\n\r\n### 3.2 then \u8bed\u53e5\r\n\r\nthen \u5b57\u6bb5\u53ef\u4ee5\u662f\u4e0b\u5217\u4efb\u610f\u8bed\u53e5\u4e4b\u4e00\uff1a\r\n\r\n- block \r\n \u5c4f\u853d\u5f53\u524d\u8bf7\u6c42\r\n- return *ip* \r\n- return (*ip1*, *ip2*, ...) \r\n \u76f4\u63a5\u8fd4\u56de\u89e3\u6790\u7ed3\u679c\r\n\r\n### 3.3 before \u8bed\u53e5\r\n\r\nbefore \u5b57\u6bb5\u7531\u4e0b\u5217\u4e00\u6761\u6216\u591a\u6761\u9017\u53f7\u5206\u9694\u7684\u8bed\u53e5\u7ec4\u6210\uff1a\r\n\r\n- set upstream group to *name* \r\n \u4f7f\u7528 *name* \u7ec4\u4e2d\u7684\u4e0a\u6e38\u670d\u52a1\u5668\u6765\u89e3\u6790\u57df\u540d\r\n- set upstream name to *name* \r\n \u4f7f\u7528\u540d\u79f0\u4e3a *name* \u7684\u4e0a\u6e38\u670d\u52a1\u5668\u6765\u89e3\u6790\u57df\u540d\r\n- replace domain by *domain* \r\n \u5c06\u8bf7\u6c42\u4e2d\u7684\u57df\u540d\u66ff\u6362\u4e3a *domain*\r\n- set proxy on \r\n \u542f\u7528\u4ee3\u7406\u670d\u52a1\u5668\u8bbf\u95ee\u4e0a\u6e38\u670d\u52a1\u5668\uff08\u987b\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u8bbe\u7f6e proxy \u9879\uff09\r\n- set proxy off \r\n \u7981\u7528\u4ee3\u7406\u670d\u52a1\u5668\u8bbf\u95ee\u4e0a\u6e38\u670d\u52a1\u5668\r\n- set proxy to *proxy* \r\n \u6307\u5b9a\u4ee3\u7406\u670d\u52a1\u5668\u8bbf\u95ee\u4e0a\u6e38\u670d\u52a1\u5668\u3002*proxy* \u683c\u5f0f\u5982 http://127.0.0.1:8080 \u6216 socks5://127.0.0.1:1080 \r\n\r\n### 3.4 after \u8bed\u53e5\r\n\r\n- block if *expr1* \r\n \u5f53\u89e3\u6790\u7ed3\u679c\u6ee1\u8db3\u6761\u4ef6\uff08 *expr1* \u8868\u8fbe\u5f0f\u4e3a\u771f\uff09\u65f6\uff0c\u5c4f\u853d\u57df\u540d\r\n- return *ip* if *expr1* \r\n \u5f53\u89e3\u6790\u7ed3\u679c\u6ee1\u8db3\u6761\u4ef6\uff08 *expr1* \u8868\u8fbe\u5f0f\u4e3a\u771f\uff09\u65f6\uff0c\u7528 *ip* \u66ff\u4ee3\u89e3\u6790\u7ed3\u679c\r\n- return (*ip1*, *ip2*, ...) if *expr1*\r\n- append record *ip* \r\n \u5728\u4e0a\u6e38\u670d\u52a1\u5668\u8fd4\u56de\u7684\u89e3\u6790\u7ed3\u679c\u540e\u8ffd\u52a0\u8bb0\u5f55\r\n- append record (*ip1*, *ip2*, ...)\r\n- append record *ip* if *expr1*\r\n- append record (*ip1*, *ip2*, ...) if *expr1*\r\n- insert record *ip* \r\n \u5728\u4e0a\u6e38\u670d\u52a1\u5668\u8fd4\u56de\u7684\u89e3\u6790\u7ed3\u679c\u524d\u63d2\u5165\u8bb0\u5f55\r\n- insert record (*ip1*, *ip2*, ...)\r\n- insert record *ip* if *expr1*\r\n- insert record (*ip1*, *ip2*, ...) if *expr1*\r\n- remove record where *expr2* \r\n \u4ece\u89e3\u6790\u7ed3\u679c\u4e2d\u79fb\u9664\u6ee1\u8db3\u6761\u4ef6\uff08 *expr2* \u8868\u8fbe\u5f0f\u4e3a\u771f\uff09\u7684\u8bb0\u5f55\r\n- replace record by *ip* where *expr2* \r\n \u7528 *ip* \u66ff\u6362\u6ee1\u8db3\u6761\u4ef6\uff08 *expr2* \u8868\u8fbe\u5f0f\u4e3a\u771f\uff09\u7684\u8bb0\u5f55\r\n- run \"*command*\" where *expr2* \r\n \u5f53\u89e3\u6790\u7ed3\u679c\u4e2d\u5b58\u5728\u6ee1\u8db3\u6761\u4ef6\u7684\u8bb0\u5f55\u65f6\uff0c\u6267\u884c *command* \u547d\u4ee4\u3002\u547d\u4ee4\u9700\u8981\u7528\u534a\u89d2\u53cc\u5f15\u53f7\u5305\u88f9\uff0c\u547d\u4ee4\u4e2d\u53ef\u4ee5\u4f7f\u7528 `{ip}` \u5360\u4f4d\u7b26\u8868\u793a\u5f53\u524d\u8bb0\u5f55\u7684 IP \u5730\u5740\u3002\r\n\r\n#### 3.4.1 expr1 \u7c7b\u578b\u8868\u8fbe\u5f0f\r\n\r\n- any ip is *ip* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u5b58\u5728 IP \u5730\u5740\u7b49\u4e8e *ip* \u7684\u8bb0\u5f55\r\n- any ip is (*ip1*, *ip2*, ...)\r\n- any ip is not *ip*\r\n- any ip is not (*ip1*, *ip2*, ...)\r\n- any ip in *cidr* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u5b58\u5728 IP \u5730\u5740\u5728 *cidr* \u8303\u56f4\u5185\u7684\u8bb0\u5f55\u3002 *cidr* \u4f7f\u7528 IP-CIDR \u683c\u5f0f\u8868\u793a\uff0c\u5982 192.168.1.1/24\r\n- any ip in (*cidr1*, *cidr2*, ...)\r\n- any ip not in *cidr*\r\n- any ip not in (*cidr1*, *cidr2*, ...)\r\n- any geoip is *country* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u5b58\u5728 IP \u5730\u5740\u6240\u5728\u56fd\u4e3a *country* \u7684\u8bb0\u5f55\r\n- any geoip is not *country*\r\n- all ip is *ip* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u6240\u6709\u8bb0\u5f55\u7684 IP \u5730\u5740\u90fd\u7b49\u4e8e *ip* \r\n- all ip is (*ip1*, *ip2*, ...)\r\n- all ip is not *ip*\r\n- all ip is not (*ip1*, *ip2*, ...)\r\n- all ip in *cidr* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u6240\u6709\u8bb0\u5f55\u7684 IP \u5730\u5740\u90fd\u5728 *cidr* \u8303\u56f4\u5185 \r\n- all ip in (*cidr1*, *cidr2*, ...)\r\n- all ip not in *cidr*\r\n- all ip not in (*cidr1*, *cidr2*, ...)\r\n- all geoip is *country* \r\n \u89e3\u6790\u7ed3\u679c\u4e2d\u6240\u6709\u8bb0\u5f55\u7684 IP \u6240\u5728\u56fd\u90fd\u4e3a *country* \r\n- all geoip is not *country*\r\n\r\n#### 3.4.2 expr2 \u7c7b\u578b\u8868\u8fbe\u5f0f\r\n\r\n- ip is *ip*\r\n- ip is (*ip1*, *ip2*, ....)\r\n- ip is not *ip*\r\n- ip is not (*ip1*, *ip2*, ....)\r\n- ip in *cidr*\r\n- ip in (*cidr1*, *cidr2*, ...)\r\n- ip not in *cidr*\r\n- ip not in (*cidr1*, *cidr2*, ...)\r\n- geoip is *country*\r\n- geoip is not *country*\r\n- first \r\n \u7b2c\u4e00\u6761\u8bb0\u5f55\r\n- last \r\n \u6700\u540e\u4e00\u6761\u8bb0\u5f55\r\n\r\n## 4. \u7279\u6027\r\n\r\n- \u5982\u679c DNS \u89e3\u6790\u8bf7\u6c42\u4e2d\u5305\u542b\u591a\u6761\u67e5\u8be2\uff0c\u4f1a\u88ab\u9010\u6761\u62c6\u5206\u540e\u53d1\u9001\u81f3\u4e0a\u6e38\u670d\u52a1\u5668\uff0c\u5e76\u5728\u8fd4\u56de\u54cd\u5e94\u65f6\u91cd\u65b0\u7ec4\u5408\u3002\u8fd9\u4e48\u505a\u7684\u76ee\u7684\u662f\u4e3a\u4e86\u65b9\u4fbf\u4e2d\u95f4\u4ef6\u5904\u7406\uff1b\r\n- \u7a0b\u5e8f\u5728\u5f15\u5bfc\u65f6\u4f1a\u4f18\u5148\u4f7f\u7528 priority \u503c\u6700\u5927\u7684 upstream \u6765\u89e3\u6790 DoH \u670d\u52a1\u5668\u7684\u57df\u540d\u3002\u9ed8\u8ba4\u4f7f\u7528 cloudflare-tls \u670d\u52a1\u5668\u8fdb\u884c\u5f15\u5bfc\u65f6\u89e3\u6790\uff1b\r\n- \u7a0b\u5e8f\u542f\u52a8\u65f6\u4f1a\u6d4b\u8bd5\u914d\u7f6e\u4e2d\u6240\u6709\u7684\u4e0a\u6e38\u670d\u52a1\u5668\uff0c\u5e76\u5c06\u54cd\u5e94\u6700\u5feb\u7684\u670d\u52a1\u5668\u8bbe\u7f6e\u4e3a\u4e3b\u670d\u52a1\u5668\uff1b\r\n- \u7a0b\u5e8f\u5185\u7f6e\u7684 GeoIP2 \u6570\u636e\u5e93\u4ec5\u5305\u542b\u4e2d\u56fd IP \u6bb5\u6570\u636e\uff0c\u53ea\u80fd\u8fd4\u56de `cn` \u6216\u7a7a\u3002\u8981\u4f7f\u7528\u5b8c\u6574\u7684 GeoIP2 \u6570\u636e\u5e93\uff0c\u53ef\u4ee5\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u6307\u5b9a\u6570\u636e\u5e93\u6587\u4ef6\uff1b\r\n- \u7a0b\u5e8f\u5185\u7f6e\u7684\u4e0a\u6e38 DNS \u89e3\u6790\u670d\u52a1\u5668\u5305\u62ec\uff1a[Cloudflare DNS](https://1.1.1.1/dns/) (cloudflare), [Google Public DNS](https://developers.google.com/speed/public-dns) (google), [\u963f\u91cc\u516c\u5171DNS](https://alidns.com/) (alidns), [114DNS](https://www.114dns.com/) (114dns), [OneDNS ](https://www.onedns.net/)(onedns), [DNSPod](https://www.dnspod.cn/) (dnspod), [\u767e\u5ea6DNS](https://dudns.baidu.com/)(baidu), [OpenDNS](https://www.opendns.com/) (opendns), [AdGuard DNS](https://adguard-dns.io/) (adguard) \u3002\u8fd9\u4e9b\u670d\u52a1\u5668\u6309\u7167\u670d\u52a1\u4f9b\u5e94\u5546\u7684\u540d\u79f0\uff08\u89c1\u62ec\u53f7\u5185\uff09\u5206\u4e3a\u4e0d\u540c\u7ec4\uff1b\u6839\u636e\u670d\u52a1\u5668\u6240\u5728\u5730\uff0c\u5206\u4e3a cn \u7ec4\u548c global \u7ec4\uff1b\u6839\u636e\u670d\u52a1\u5668\u7f51\u7edc\u7c7b\u578b\uff0c\u5206\u4e3a ipv4 \u7ec4\u548c ipv6 \u7ec4\u3002\r\n\r\n## 5. \u5e38\u7528\u547d\u4ee4\r\n\r\n\u6a21\u5757\u6784\u5efa\u6253\u5305\uff08\u9700\u8981\u5b89\u88c5 build \u6a21\u5757\uff09\uff1a\r\n\r\n```shell\r\npip install build\r\npython -m build\r\n```\r\n\r\n\u8fd0\u884c\u5355\u5143\u6d4b\u8bd5\uff1a\r\n\r\n```shell\r\npython -m unittest tests\r\n```\r\n\r\n\u9879\u76ee\u53d1\u5e03\u7684\u53ef\u6267\u884c\u6587\u4ef6\u4f7f\u7528 [Nuitka-winsvc](https://github.com/tabris17/Nuitka-winsvc) \u7f16\u8bd1\u3002\u9996\u5148\u5b89\u88c5\u4f9d\u8d56\u7684\u5305\uff1a\r\n\r\n```shell\r\npip install nuitka ordered-set zstandard dnspooh\r\n```\r\n\r\n\u5b98\u65b9\u53d1\u5e03\u7684 Windows \u7a0b\u5e8f\u4f7f\u7528\u5982\u4e0b Nuitka \u547d\u4ee4\u7f16\u8bd1\uff1a\r\n\r\n```shell\r\nnuitka --standalone --output-dir=build --output-filename=dnspooh --windows-icon-from-ico=./assets/favicon.ico --include-package-data=dnspooh --onefile --windows-service --windows-service-name=dnspooh --windows-service-display-name=Dnspooh --windows-service-description=\"A lightweight DNS MitM proxy\" main.py\r\n```\r\n\r\n\u542f\u52a8 Web \u7ba1\u7406\u754c\u9762\u524d\u7aef\u5f00\u53d1\u73af\u5883\uff1a\r\n\r\n```shell\r\nnpm i\r\nnpm run dev\r\n```\r\n\r\n\u6784\u5efa Web \u7ba1\u7406\u754c\u9762\u524d\u7aef\uff1a\r\n\r\n```shell\r\nnpm run build\r\n```\r\n\r\n",
"bugtrack_url": null,
"license": "MIT License",
"summary": "A Lightweight DNS MitM Proxy",
"version": "1.3.3",
"project_urls": {
"Bug Tracker": "https://github.com/tabris17/dnspooh/issues",
"Homepage": "https://github.com/tabris17/dnspooh"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f680b79b04306e902fc874365611e58bfbd998b70af1827ec2e733a8ab6edeae",
"md5": "eb54cde76136e0a708fc3bb48943f7c9",
"sha256": "ccb15ffe37442b7a161ef611cce7eb38e8629b8daa1bc2a8703db9dfe4322ada"
},
"downloads": -1,
"filename": "dnspooh-1.3.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "eb54cde76136e0a708fc3bb48943f7c9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 104887,
"upload_time": "2024-04-11T07:44:44",
"upload_time_iso_8601": "2024-04-11T07:44:44.980102Z",
"url": "https://files.pythonhosted.org/packages/f6/80/b79b04306e902fc874365611e58bfbd998b70af1827ec2e733a8ab6edeae/dnspooh-1.3.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "9a381e25888bce6ebc465251f377b20536ad342226c6ae03e60c13cd3ea629ae",
"md5": "06583abd99bf76b019ec520e2387f5e1",
"sha256": "8a6dbfbfaed205381e4c45b20942de8b1419a2909b74ee179111ec26b4920c2b"
},
"downloads": -1,
"filename": "dnspooh-1.3.3.tar.gz",
"has_sig": false,
"md5_digest": "06583abd99bf76b019ec520e2387f5e1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 110334,
"upload_time": "2024-04-11T07:44:46",
"upload_time_iso_8601": "2024-04-11T07:44:46.981983Z",
"url": "https://files.pythonhosted.org/packages/9a/38/1e25888bce6ebc465251f377b20536ad342226c6ae03e60c13cd3ea629ae/dnspooh-1.3.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-11 07:44:46",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "tabris17",
"github_project": "dnspooh",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "dnspooh"
}