dnspooh


Namednspooh JSON
Version 1.3.3 PyPI version JSON
download
home_pagehttps://github.com/tabris17/dnspooh
SummaryA Lightweight DNS MitM Proxy
upload_time2024-04-11 07:44:46
maintainerNone
docs_urlNone
authortabris17
requires_python>=3.10
licenseMIT License
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 4.53725s