# 插件及其参数

## 写在最前

### mosdns 的插件流水线概念

<figure><img src="/files/wQiBuxGG1S6Eh3Rk1sBM" alt=""><figcaption><p>一个能正常工作的简单的流水线示例。能将应答转发至上游。同时支持缓存。</p></figcaption></figure>

什么是插件: 插件接受并返回一个“请求上下文”。在内部处理请求/应答。

“请求上下文”包含了

* 客户端来源 IP 等信息。由服务端赋值。不可修改。
* 客户的请求。插件可以自由修改请求的数据。
* 应答。初始为空。插件可以自由修改应答的数据

插件流水线: 将多个插件串在一起，就构成了一条流水线。

### 配置文件中的插件

每个插件都是独立的，互不影响。同类型 `type` 插件可以初始化任意多个，只要插件的 `tag` 不重复就行。

<details>

<summary>多个同类型插件配置示例。点击展开</summary>

两个 `forward` 插件。`tag` 不同，参数不同。

```yaml
plugin:
  - tag: forward_google
    type: forward
    args:
      upstream:
        - addr: https://dns.google/dns-query

  - tag: forward_cloudflare
    type: forward
    args:
      upstream:
        - addr: https://1.1.1.1/dns-query
```

</details>

一些常用的插件配置已经被 mosdns 预先初始化好了，`tag` 以 `_` 开头以便区分。可以直接调用。

{% hint style="warning" %}
mosdns 会按照配置文件中顺序将插件初始化。有些插件需要调用其他插件，比如 `sequence，其调用的插件必须在之前配置。否则会因为找不到插件而初始化失败。`
{% endhint %}

## 可执行插件

### \_return

预初始化的插件。什么都不做。不会将请求上下文传递给后续流水线。可以用于结束 sequence 流水线。见下文。

流水线逻辑：

* 正向：跳过后续流水线。
* 反向：无。

### sequence 将多个插件组合成流水线

`sequence` 可以将多个插件组成一个流水线。`sequence` 也是插件，所以 `sequence` 里运行别的`sequence` 是可以的。

`_return` 是一个特殊的预初始化插件，可配合 `sequence` 使用，它只会跳过后续流水线。(注意: “跳过后续流水线”并不是直接将应答返回客户端，而是正向流水线运行结束，开始反向运行。)。

sequence 插件中调用的插件 tag 必须在 sequence 前定义，否则 sequence 找不到对应插件。

<figure><img src="/files/1qZajfwGeHxlCaaefI66" alt=""><figcaption><p>sequence 流水线图示。</p></figcaption></figure>

初始化参数说明:

```yaml
tag: ""
type: "sequence"
args:
  exec:
    # 可以时可执行插件的 tag。比如:
    - "hosts"
    - "cache"
    - "ecs"

    # 可以是 if 条件。
    # if 为条件表达式。支持 && ||，() 以及 ! 取反。
    # 预定义插件(以 _ 开头的插件)，需要用 [] 括住。
    - if: "(matcher && ! matcher) || [_preset_matcher]"
      exec:      # 条件满足，使用此分支流水线。
        - "ecs"
        - "forward"
      else_exec: # 否则，使用此分支流水线。
        - "ecs"
        - "forward"

    # 可以是 load_balance。
    # 轮换使用其内部的分支流水线。
    - load_balance:
        - - "ecs_1"     # 分支流水线 #1。
          - "forward_1"
        - - "ecs_2"     # 分支流水线 #2。
          - "forward_2"
        # ...           # 不限数量，可继续添加。
```

<details>

<summary>高级操作: 并发、失败自动回滚(故障转移)</summary>

```yaml
tag: ""
type: "sequence"
args:
  exec:
    # 可以是 parallel。
    # 并行运行其内部的独立流水线。取最先返回的有效(0 RCODE)应答。
    - parallel:
        - - "ecs_1"     # 独立流水线 #1。
          - "forward_1"
        - - "ecs_2"     # 独立流水线 #2。
          - "forward_2"
        # ...           # 不限数量，可继续添加。

    # 可以是 fallback。
    # fallback 有两种触发方式。详见下文。
    - primary:            # 主要流水线(独立的)。
        - "ecs_1"
        - "forward_1"
      secondary:          # 次要流水线(独立的)。
        - "ecs_2"
        - "forward_2"
      stat_length: 10   # 常规 fallback 统计长度。默认 0，禁用常规 fallback。可省略。
      threshold: 5      # 常规 fallback 触发阈值。不可大于统计长度。默认 0。可省略。
      fast_fallback: 200    # 快速 fallback 触发阈值。单位: 毫秒。默认: 0，禁用快速 fallback。可省略。
      always_standby: true # 快速 fallback secondary 始终待命。可省略。
```

`fallback`，`parallel` 涉及到多线程运行，其内部流水线其实是一个**独立的** sequence 插件，拥有**独立的**请求上下文，为了配置方便嵌套在这里。它们和当前 sequence 没有关系！在独立的 sequence 内对请求上下文修改，不会影响其他并行的流水线。同样 `_return`，`cache` 等能跳过后续流水线运行的插件，也只会终止自身流水线，不会对其他流水线产生影响。但最终 `fallback` 和 `parallel` 会把合适的应答放在当前 sequence 的请求上下文内。比如 `parallel` 会取最快返回的流水线的请求上下文的应答放在当前 sequence 的请求上下文。

fallback 两种触发方式:

* 常规 fallback: 监控主流水线最近 `stat_length` 次的请求状态，如果出现 `threshold` 次问题(没有应答或者有报错)，则下次请求会一并执行次流水线。取最先返回的应答。
* 快速 fallback: 对于每一个请求，如果主流水线运行结束了但没有返回应答，或者在 `fast_fallback` 毫秒后仍在运行无应答，则立即开始执行次流水线，取最先返回的应答。如果任意流水线返回了应答，这个请求就不会失败。比常规 fallback 策略更激进，次流水线可能运行的更频繁。
  * secondary 始终待命: 如果设为 `true`，则对于每一个请求，次流水线都会和主流水线一并执行，其结果将会在需要回滚时(没有返回应答或 `fast_fallback` 超时)，直接被使用。进一步降低响应时间。
* 快速和常规 fallback 可以同时启用。

</details>

### cache 缓存

缓存 DNS 应答的插件。

流水线逻辑：

* 正向：如果请求命中缓存，则将缓存的应答放入“请求上下文”，跳过后续流水线。如果没有命中，则继续执行后续流水线。
* 反向：缓存应答。

以下预初始化的插件 `tag` 可直接使用:

* `_default_cache`: 默认配置的内存缓存。大小 1024。无 lazy cache。

初始化参数说明:

```yaml
tag: ""
type: "cache"
args:
  size: 1024  # 内置内存缓存大小。单位: 条。默认: 1024。每个 cache 插件的内存缓存是独立的。
  compress_resp: false # (实验性)(v4.4+)启用应答压缩。大缓存场景可能会稍微降低内存占用。
                       # 注意: 如果使用 redis 外部缓存，切换该选项必须清空 redis 缓存。 
  
  # redis-server 外部缓存地址。填入外部缓存会自动禁用内置的内存缓存。上文的 size 参数会被忽略。
  # redis-server 需要 v2 及以上版本。如果多个 cache 插件连接到同一个 redis 数据库，数据是共享的。
  # cache 会为设定记录的 TTL，不会主动删除 redis-server 内的记录。内存大小清理方式等由
  # redis-server 负责。
  # cache 假设 redis-server 的距离很近/延时很低而且性能足够。
  # TCP 连接: `redis://<user>:<password>@<host>:<port>/<db_number>`
  # Unix 连接: `unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>`
  redis: "redis://localhost:6379/0"

  # redis-server 超时阈值。单位: 毫秒。默认: 1000。一般无需修改。
  # 如果 redis-server 在该时间内无响应，cache 会临时停用缓存。同时间歇性尝试 ping
  # redis-server，直至再次成功连接。
  redis_timeout: 1000

  # lazy cache 设定。lazy_cache_ttl > 0 会启用 lazy cache。
  # lazy cache 的设计参考了 RFC 8767。所有应答都会在缓存中存留 lazy_cache_ttl 秒，但自身的 TTL 仍然有效。
  # 如果命中过期的应答，则缓存会立即返回 TTL 为 lazy_cache_reply_ttl 的应答，然后自动在后台发送请求更新数据。
  # 相比强行增加应答自身的 TTL 的方法，lazy cache 能提高命中率，同时还能保持一定的数据新鲜度。
  lazy_cache_ttl: 0        # lazy cache 生存时间。单位: 秒。默认: 0 (禁用 lazy cache)。
                           # RFC 8767 的建议值 86400（1天）~ 259200（3天）
  lazy_cache_reply_ttl: 5  # 返回过期应答的 TTL。默认 5。

  # cache 默认只缓存有且只有一个 Question 的请求(简称"简单请求")的应答。请求的状态以运行到 cache 时为准。
  # 比如一个简单请求在 cache 后被插入格外信息(比如 ECS)，不会影响 cache 缓存这个请求，也不会影响 cache 后续
  # 查找这个简单请求。
  # cache_everything 会缓存所有请求的应答。会将完整的请求作为 key。能保证带有特殊标识的请求(比如 ECS，Cookie)
  # 即使 Question 相同仍然能被区分。
  # 注意: "将完整的请求作为 key" 意味着 cache 不会"精细"处理各种 EDNS0。用做公用服务器可能会大幅降低 cache 效率。
  # 如需处理 EDNS0 建议使用 unbound 等专业软件。
  cache_everything: false 
  
  # 当缓存命中后，将应答放入请求上下文后，需要执行的插件。
  when_hit: ""
```

lazy cache，也叫“乐观缓存”，的优缺点：

* 优点：可大幅提高缓存命中率。流行域名几乎 100% 命中。
* 缺点：可能影响 DNS 更新频繁的域名正常使用，比如 DDNS，CDN。
* 仅建议个人使用场景，有相关知识，了解其弊端的专业用户开启。

Metrics 数据:

* query\_total: 总请求数。
* hit\_total: 总命中数。(包括 lazy cache 的命中)
* lazy\_hit\_total: lazy cache 总命中数
* cache\_size: 当前缓存大小。单位: 条。

### hosts 域名映射 IP

流水线逻辑：

* 正向：如果请求的域名在 hosts 内，则将应答放入“请求上下文”，跳过后续流水线。如果不在，则继续执行后续流水线。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "hosts"
args:
  hosts:
    - "google.com 108.177.122.113" # 可以是一条记录
    - "provider:hosts"             # 可以是由 data-provider 提供的外部数据，格式 "provider:tag"。
```

{% hint style="warning" %}
虽然都叫 hosts，但本插件所用的格式和平常 Win，Linux 系统内的那个 hosts 文件不一样。
{% endhint %}

格式:

* 域名规则在前，IP 在后。支持一行多个 IP，支持 IPv6。
* 如果域名匹配规则的方式被省略，则默认是 `full` 完整匹配。域名匹配规则详见 [这里](#yu-ming-pi-pei-gui-ze)。

格式示例:

```
# 支持一行多个 IP
dns.google 8.8.8.8 2001:4860:4860::8888 ...
```

### \_prefer\_ipv4/6 优先使用 ipv4/6

`_prefer_ipv4` 和 `_prefer_ipv6` 是预初始化插件，可直接使用。下面为了表述方便，仅以 `_prefer_ipv4` 为例。`_prefer_ipv6` 同理。

`_prefer_ipv4` 可用让下游客户端支持双栈时优先使用 IPv4。`_prefer_ipv4` 会自动探测域名是否是双栈，然后只屏蔽双栈域名的 AAAA 请求。相比于强行禁用所有 AAAA 请求的方案，该插件不会影响纯 IPv6 域名的解析。

使用位置要求: 插件的后续序列中必须有转发请求的插件。

流水线逻辑：

* 正向：同时请求 A 和 AAAA。
* 反向：根据 A 和 AAAA 的结果，判断域名双栈支持状况，决定屏蔽策略。

sequence 示例:

```yaml
- _prefer_ipv4
- ...
- forward_xxx

# 配合 if 可以实现仅特定域名生效
- if: domain_matcher
  exec:
    - _prefer_ipv4
- ...
- forward_xxx
```

### ecs 为请求附加 ECS

流水线逻辑：

* 正向：附加 ECS 至请求。
* 反向：保证请求与应答 ECS 一致。如果源请求中无 ECS (现有 ECS 是插件新附加的)，则删除应答中的 ECS 回应。

初始化参数说明:

```yaml
tag: ""
type: "ecs"
args:
  auto: false             # 是否自动将用户请求的来源地址作为 ECS 附加到请求。
                          # 如果为 true，则预设地址不会被使用。
                          # 仅适用于公网的服务器。本地运行不要启用这个功能。
  ipv4: "1.2.3.4"         # 预设的 IPv4 地址。会优先附加在 A 请求里。
  ipv6: "2001:dd8:1a::"   # 预设的 IPv6 地址。会优先附加在 AAAA 请求里。
  force_overwrite: false  # 如果请求已有 ECS，是否强制覆盖。
  mask4: 24               # 用于 ipv4 地址的掩码。默认: 24。
  mask6: 48               # 用于 ipv6 地址的掩码。默认: 48。
```

### \_no\_ecs 删除请求和应答中的 ECS

`_no_ecs` 是预初始化插件的 tag，可直接使用。

流水线逻辑：

* 正向：删除请求中的 ECS。
* 反向：删除应答中的 ECS。

一般放在转发插件前。

### ttl 修改应答的生存时间

流水线逻辑：

* 正向：修改应答的生存时间。
* 反向：无。

使用位置：一般放在转发插件后。

初始化参数说明:

```yaml
tag: ""
type: "ttl"
args:
  minimal_ttl: 300  # 最小TTL。如果非零，小于这个数的应答的TTL会被修改成这个值。
  maximum_ttl: 3600 # 最大TTL。如果非零，大于这个数的应答的TTL会被修改成这个值。
```

### blackhole 丢弃应答或生成假应答

`blackhole` 会丢弃应答，或生成特定应答来屏蔽请求。

以下预初始化的插件 `tag` 可直接使用:

* `_drop_response`: 丢弃已存在的应答。
* `_new_empty_response`: 生成 Rcode 为 0 的空应答。放入“请求上下文”。(模拟域名存在但没有所请求类型的记录)
* `_new_servfail_response`: 生成 Rcode 为 2 (SERVFAIL: 服务器失败)应答。放入“请求上下文”。
* `_new_nxdomain_response`: 生成 Rcode 为 3 (NXDOMAIN: 域名不存在)应答。放入“请求上下文”。

流水线逻辑：

* 正向：丢弃或生成对应应答。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "blackhole"
args:
  rcode: 0        # 生成`Rcode`为该值的应答。如果负数，则将应答设为空值(丢弃已存在的应答)。
  ipv4:           # 如果请求类型是 A，则生成包含这些 IP 地址的应答。
    - "127.0.0.1"  
  ipv6:           # 如果请求类型是 AAAA，则生成包含这些 IP 地址的应答。
    - "::1"      
```

ipv4 和 ipv6 的优先级高于 rcode。如果都配置了， A/AAAA 请求会生成 IP 应答，其余类型会生成状态为 RCODE 的应答。

### fast\_forward 转发请求至上游(自带模块)

mosdns 自带模块。

流水线逻辑：

* 正向：转发请求至上游，获取应答后放入“请求上下文”。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "fast_forward"
args:
  # 上游服务器。至少要配置一个。可配置多个，会并发请求全部服务器。
  upstream:
    - addr: "https://dns.google/dns-query"
      dial_addr: "8.8.8.8"
      bootstrap: ""
      socks5: "127.0.0.1:1080"
      trusted: true
      idle_timeout: 30
      enable_pipeline: false
      enable_http3: false
      max_conns: 2
      insecure_skip_verify: false
      so_mark: 0
      bind_to_device: ""
    - addr: "tcp://8.8.8.8"
      enable_pipeline: true
    ca: [ ]
```

* `addr` 必需。服务器地址。省略 scheme 默认为 UDP 协议。省略端口号会使用协议的默认值。支持 IP 和域名。格式示例:
  * UDP: `8.8.8.8`, `208.67.222.222:443`
  * TCP: `tcp://8.8.8.8`
  * DoT: `tls://8.8.8.8:853`, `tls://dns.google`
  * DoH (RFC8484 GET): `https://8.8.8.8/dns-query`, `https://dns.google/dns-query`
  * UDPME(v3.5.1+)(实验性): `udpme://8.8.8.8`
    * 这是个能过滤掉 UDP 抢答应答的方案。仍然是 UDP 协议。服务器必须支持 EDNS0。如果抢答者不支持 EDNS0，则可以过滤抢答应答。
    * 使用 dig 测试 `dig +edns cloudflare.com @服务器地址` 观察返回是否有一行 `EDNS: version: 0` 来确定服务器是否支持 EDNS0。绝大多数服务器都支持。
* 优先使用 IP 地址(如果服务器官方提供)。如果 `addr` 包含域名，插件需要将其解析成 IP 地址。
  * `dial_addr`: 为包含域名的 `addr` 手动指定其 IP 地址。支持端口号。指定后插件连接服务器时无需先解析服务器的域名。能更快建立连接。适用于 IP 万年不变的服务器。
  * `bootstrap`: (v4.2+)(实验性) 手动指定用于解析 `addr` 中域名的 bootstrap 服务器。只能是 UDP 服务器。指定后插件**每次**连接服务器时会先通过该 bootstrap 服务器解析域名。bootstrap 服务器的延时越低越好。HTTP/3 暂不支持该选项。仅支持 Win，Unix 平台。Macos，Plan9 等不支持。
  * 系统默认解析: 以上两个参数留空，会使用默认系统解析方式。
  * 优先级: `dial_addr` > `bootstrap` > 系统默认。
* `socks5`: socks5 服务器地址。格式 `host:port`。数据将会通过该代理中转。暂不支持用户名密码认证。UDP 和启用了 HTTP3 的 DoH 协议暂不支持该设定。
* `trusted`: 是否是可信服务器。可信服务器的任何应答都会被接受。其余服务器只接受 RCODE 为 0 (SUCCESS) 的应答。第一个服务器一定是可信服务器且不可修改。其余默认不可信。
* `idle_timeout` TCP/DoT/DoH 连接复用空连接保持时间。单位: 秒。默认 DoH: 30 ，TCP/DoT: 10。一般不需要改。
* `enable_pipeline`: TCP/DoT 使用 RFC 7766 新的 query pipelining 连接复用模式。
  * 启用后可大幅提高连接利用率，减少建立连接/握手的次数，进而降低延时。
  * 并非所有服务器都支持。必须确定服务器支持后再启用该选项。
  * Tips: 已知 Google 和 Cloudflare 的 TCP/DoT 都是支持的。知名的公共 DNS 服务商大多数都支持该模式。mosdns 自带一个测试命令可以测试服务器是否支持该模式。
* `enable_http3`: (v3.4.0+)(实验性) DoH 使用 HTTP/3 连接服务器。并非所有服务器都支持。必须确定服务器支持后再启用该选项。Tips: 已知 Google 和 Cloudflare 是支持的。
* `max_conns`： 默认: 2。一般不需要改。大流量场景可适当调大。
  * UDP 协议: 收发数据包使用的套接字数。
  * 启用了 query pipelining 模式的 TCP/DoT 协议: 最大连接数。
  * DoH 协议: HTTP 的最大连接数。HTTP/3 除外。
* `insecure_skip_verify`: 禁用 TLS 服务器证书验证。
* Linux 套接字设定。需要 `CAP_NET_ADMIN` 权限
  * `so_mark`:(v3.7+)。设定 SO\_MARK。
  * `bind_to_device`: (v4.3+) 设定 SO\_BINDTODEVICE。
* `ca`: 用于 TLS 验证的 CA 证书路径。是数组，可填入多个证书。默认使用系统证书池。

### forward 转发请求至上游(dnsproxy)

这个插件使用了 AdguardHome 的请求模块 [dnsproxy](https://github.com/AdguardTeam/dnsproxy/tree/master/upstream)。

流水线逻辑：

* 正向：转发请求至上游，获取应答后放入“请求上下文”。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "forward"
args:
  # 上游服务器。可配置多个。至少要配置一个。会并发请求全部服务器。
  upstream:
    - addr: "https://dns.google/dns-query" # 必需。服务器地址。
      # 当服务器地址为域名时，手动指定服务器的 IP 地址。可以配置多个。
      # 如果配置了，则不会使用 bootstrap 服务器。会按顺序尝试连接配置的 IP。
      ip_addr:
        - "8.8.8.8"
    - addr: "https://1.1.1.1/dns-query"

  # 用于解析上游服务器域名的 bootstrap 服务器地址。可配置多个。为空会使用系统默认方式解析域名。
  # 服务器地址不能包含域名。注意，dnsproxy 的 bootstrap 只会在启动时解析一次。服务器的 IP 在运行时不
  # 会自动更新。如需更新服务器的 IP，请重启。
  bootstrap:
    - "tls://1.1.1.1"
    - "https://223.5.5.5/dns-query"
    # ...

  timeout: 5          # 请求超时时间，单位: 秒。
  insecure_skip_verify: false  # 禁用 TLS 身份验证。
```

`addr` 地址格式和 AdguardHome 支持的格式一致。一些示例:

* UDP: `8.8.8.8`, `208.67.222.222:443`
* TCP: `tcp://8.8.8.8`
* DoT: `tls://dns.google`
* DoH: `https://dns.google/dns-query`
* DoH3: `h3://dns.google/dns-query`
* DoQ: `quic://dns.adguard.com`
* DNS stamp: `sdns://...` 详见 [dnscrypt.info](https://dnscrypt.info/stamps-specifications)

上述列表和参数说明不一定是最新的，请以 [dnsproxy 的 README](https://github.com/AdguardTeam/dnsproxy) 为准。

### ipset 将应答 IP 写入到系统 ipset

流水线逻辑：

* 正向：将应答 IP 写入 ipset。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "ipset"
args:
  set_name4: ""   # 将 ipv4 地址写入这个 set。这个 set 的 family 类型需为 `inet`。
  set_name6: ""   # 将 ipv6 地址写入这个 set。这个 set 的 family 类型需为 `inet6`。
  mask4: 0        # 写入 ipv4 地址时使用的掩码。默认: 24。
  mask6: 0        # 写入 ipv6 地址时使用的掩码。默认: 32。
```

* 插件添加新 IP 时相当于执行了 `ipset add <set_name> <IP>/<mask>` 命令。
* 插件使用 netlink 直接与内核通信，不依靠 `/sbin/ipset` 命令。
* 该插件不会创建 set。set 需要由用户提前创建。必须为 `hash:net` 类型。建议设置非零的 `timeout` 属性。实现自动清除长时间没有用到的地址。

创建 ipset 表命令示例:

```bash
ipset create <set_name4> hash:net family inet timeout 86400 maxelem 130050
ipset create <set_name6> hash:net family inet6 timeout 86400 maxelem 130050
```

### nftset 将应答 IP 写入到系统 nftables

流水线逻辑：

* 正向：将应答 IP 写入 nftables。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "nftset"
args:
  table_family4: "" # 存 IPv4 地址的 table family 类型。
  table_family6: "" # 存 IPv6 地址的 table family 类型。
  table_name4: ""   # 存 IPv4 地址 table 的名字。
  table_name6: ""   # 存 IPv6 地址 table 的名字。
  set_name4: ""     # 存 IPv4 set 的名字。
  set_name6: ""     # 存 IPv6 set 的名字。
  mask4: 0       # 如果 set 带有 interval flag。写入 IPv4 地址时使用的掩码。默认: 24。
  mask6: 0       # 如果 set 带有 interval flag。写入 IPv6 地址时使用的掩码。默认: 32。
```

* 插件添加新 IP 时相当于执行了 `nft add element <table_family> <table_name> <set_name> {<IP>/<mask>...}` 命令。
* 插件使用 netlink 直接与内核通信，并不依靠 `/sbin/nft` 命令。
* 该插件不会创建对应的 set。set 需要由用户提前创建。
* set 建议设置 `timeout` 来实现自动清除长时间没有用到的地址。
* set 建议设置 `interval` flag。插件会自动识别这个 flag，然后会以 IP 段的方式存储 IP。

创建 nftables set 命令示例:

```bash
nft add table inet <table_name>
nft add set inet <table_name> <set_name4> {type ipv4_addr\; timeout 7d3h8m45s \; flags interval\;}
nft add set inet <table_name> <set_name6> {type ipv6_addr\; timeout 7d3h8m45s \; flags interval\;}
```

### bufsize 限制请求的 EDNS0 的 UDP 负载大小

旧插件。建议使用新的 \_misc\_optm 插件。

流水线逻辑：

* 正向：修改请求的 EDNS0 UDPSIZE (如果请求有 EDNS0)。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "bufsize"
args:
  size: 512 # EDNS0 UDP 负载大小上限。默认 512。最大 4096。
```

### padding 填充 DNS 报文至固定长度 (实验性)

参考 RFC 8467，使用 EDNS0 的 Padding Option 将 DNS 报文用 0 填充至固定长度。据 Google 说 ([Google Plublic DNS 文档](https://developers.google.com/speed/public-dns/docs/doh#privacy_best_practices)) 能防止流量分析保护隐私。包含 3 个预定义插件。

`_pad_query`: 填充请求

* 流水线逻辑：
  * 正向：将请求填充至 128 bytes。
  * 反向：无。
* 使用位置要求: 该插件到转发插件之间不能有修改请求的插件。否则请求大小可能会变，影响效果。

`_enable_conditional_response_padding` 和 `_enable_response_padding`: 填充应答:

* 流水线逻辑：
  * 正向：无。
  * 反向：填充应答至 468 bytes。`_enable_response_padding` 是如果客户端请求包含 EDNS0，就会填充应答。而 `_enable_conditional_response_padding` 则是如果客户端对请求进行了填充，才会填充应答。
* 使用位置要求: 反向流水线填充应答后，直至流水线结束，不能有修改应答的插件。否则应答大小可能会变，影响效果。

### redirect 替换请求的域名 (实验性)

替换(重定向)请求的域名。请求域名 A，但返回域名 B 的记录。

流水线逻辑：

* 正向：替换请求的 QUESTION NAME。比如将请求域名 A 替换为 B。
* 反向：修改应答的 ANSWER 的 NAME，比如将 B 替换回 A。使之看起来像请求 A 的记录。

注意: 没有能完美替换请求域名的方法。本插件的替换方式对于常见的 A，AAAA，TXT 等仅依赖 QUESTION 和 ANSWER 的请求是有效的。但不保证兼容其他类型请求以及各种 EDNS0 扩展。

初始化参数说明:

```yaml
tag: ""
type: "redirect"
args:
  rule:
  # 格式 [域名匹配规则] [重定向至域名] 
    - www.google.com dns.google
    - provider:tag # 可以是由 data-provider 提供的外部数据。
```

* `域名匹配规则`: 如果匹配方式被省略，则默认是 `full` 完整匹配。域名匹配方式详见 [域名匹配规则](broken://pages/dGgsRKjtT6QWut7xz7qN)。

### arbitrary 返回任意记录 (实验性) (v4.0+)

arbitrary 可以载入任意记录，并在匹配到对应请求时生成包含这些记录的应答，

流水线逻辑：

* 正向：如果找到对应记录，则生成应答，终止跳过后续流水线。如果不在，则继续执行后续流水线。
* 反向：无。

初始化参数说明:

```yaml
tag: ""
type: "arbitrary"
args:
  rr:
    - example.com. 300 IN A 192.0.2.1 # 可以是文本格式的记录。
    - _sip._tcp.example.com. 86400 IN SRV 0 5 5060 sipserver.example.com.
```

不同记录的文本格式请自行搜索。关键词: `<记录类型> dns record wikipedia`。在英文 wikipedia 可以直接找到绝大多数记录的格式。

暂不支持从外部数据载入记录。

### \_misc\_optm 转发服务器优化大杂烩 (实验性) (v4.0+)

预定义插件，可直接使用。

适用于转发服务器。

流水线逻辑：

* 正向：
  * 屏蔽掉互联网公共上游不能处理的请求(多个 QUESTION 和非 IN CLASS)。(立刻返回 REFUSE，跳过后续流水线。)
  * 将 EDNS0 UDPSIZE 限定在 1200。(v4.5+)
* 反向
  * 去除 A/AAAA 应答的 CNAME 记录。减小应答长度。
  * 去除应答的 Padding。减小应答长度。
  * 随机打乱 A/AAAA 应答的 IP。负载均衡。
  * 如果请求无 EDNS0，去除上游应答的 EDNS0。保持 EDNS0 状态一致。

建议流水线位置:  流水线起始。

### reverse\_lookup IP 反查域名 API (v4.2+)(实验性)

该插件会缓存应答中的 IP 与 域名的关系。可在本地实现无延时的"有效 IP -> 域名"查询。支持 PTR 和 HTTP API。

流水线逻辑：

* 正向：如果是 PTR 请求，并且命中缓存，则生成应答，跳过后续流水线。如果没有命中缓存，则继续执行后续流水线。
* 反向：将 A/AAAA 应答的 IP 写入缓存。

初始化参数说明:

```yaml
tag: ""
type: "reverse_lookup"
args:
  size: 65535 # 内置缓存大小。默认 65535。
  
  # redis-server 外部缓存地址。填入外部缓存会自动禁用内置的内存缓存。上文的 size 参数会被忽略。
  # redis-server 需要 v2 及以上版本。如果多个 cache 插件连接到同一个 redis 数据库，数据是共享的。
  # reverse_lookup 会为设定记录的 TTL，不会主动删除 redis-server 内的记录。内存大小清理方式等由
  # redis-server 负责。
  # reverse_lookup 假设 redis-server 的距离很近/延时很低而且性能足够。
  # TCP 连接: `redis://<user>:<password>@<host>:<port>/<db_number>`
  # Unix 连接: `unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>`
  redis: "redis://localhost:6379/0"
  
  # 缓存时间。秒。默认 1800 (30min)。应答记录的 TTL 也会被自动修改，限制在该值以下。
  ttl: 1800
  # 是否主动处理/响应 PTR 请求。如果 PTR 的 IP 命中缓存，则生成应答后自动 _return，跳过后续插件运行。
  # 没有命中缓存则不会主动响应。会继续传递给后续插件。
  # 该功能的一个用途是，当下游设备使用网络监视类工具查看网络连接时，开启域名反查功能大概率会看到 IP 对应的
  # 真实域名(如果程序先请求域名再建立连接，而且域名经过了该插件处理。)
  handle_ptr: false 
                    
```

建议流水线位置:  流水线起始。务必放在 `cache` 前，防止 `cache` 命中缓存后不执行 `reverse_lookup` 导致无法记录 IP。

查找缓存中 IP 对应域名的 API：`http://<api_addr>/plugins/<plugin_tag>?ip=<ip_addr>`

返回: 如果缓存中找到对应域名，则返回域名。如果缓存中没有找到对应域名，则返回空。如果参数不对则返回 HTTP 400。

示例: `http://127.0.0.1:8080/plugins/my_reverse_lookup?ip=8.8.8.8`，返回 `dns.google.` 注意返回的是 fully qualified 域名，末尾有 `.`。

### metrics\_collector 统计数据收集器 (v4.2+)(实验性)

收集 prometheus metrics 数据。

流水线逻辑：

* 正向：query\_total =+1，thread =+1，response\_latency\_millisecond 开始计时。
* 反向：err\_total =+1 (如果出错)，thread =-1，response\_latency\_millisecond 结束计时。

```yaml
tag: ""
type: "metrics_collector"
# 没有 args 参数。
```

请求经过插件时，插件会收集

* query\_total 经过该插件的总请求数。
* err\_total 插件所在 sequence 的后续插件的总出错数。
* thread 当前经过该插件的线程数。
* response\_latency\_millisecond 插件所在 sequence 的后续插件的应答延时。

数据项名称为 `mosdns_plugin_<plugin_tag>_<name>`。

建议流水线位置: 放在流水线起始位置就可以统计本条流水线的数据。

### client\_limiter 限制客户端的 QPS (v4.2+)(实验性)

限制客户端(以 IP 或 IP 段区分)的最大每秒请求数 QPS。

流水线逻辑：

* 正向：如果客户端 QPS 超过阈值，则生成 REFUSE 应答，跳过后续流水线。
* 反向：无。

```yaml
tag: ""
type: "client_limiter"
args:
  max_qps: 100 # 最大 QPS。客户端的请求数超过时，会 REFUSED 然后 _return。
  v4_mask: 32  # ipv4 的 mask。用于限制整个 IP 段。默认 32。
  v6_mask: 48  # ipv6 的 mask。默认 48。
```

建议流水线位置: 流水线起始。

注意: 该插件只能用于向非恶意客户端“友好”地回复 REFUSED。不能防攻击。

### edns0\_filter 过滤请求的 EDNS0 OPTION (实验性)(v4.4.1+)

过滤/限制客户端可以使用 EDNS0 OPTION。

TIps: 如果后面配置有启用了 cache\_everything 的 cache 插件，该插件可用于提高缓存利用率。比如移除 Cookie 和 Padding，或只允许 ECS。

流水线逻辑：

* 正向：从请求中移除/保留指定的 EDNS0 OPTION。
* 反向：无。

以下预初始化的插件 `tag` 可直接使用:

* `_edns0_filter_no_edns0`: 删除应答的 EDNS0。
* `_edns0_filter_ecs_only`: 只保留 ECS。

初始化参数说明:

```yaml
tag: ""
type: "edns0_filter"
args:
  accept: [0,1,2]  # 仅接受这些 EDNS0 OPTION。其余的将被移除。
  discard: [0,1,2] # 仅移除这些 EDNS0 OPTION。其余的将被保留。
                   # accept 和 discard 只生效一个。优先级 accept > discard。如果全为空，则插件会
                   # 移除所有的 EDNS0 OPTION (相当于不接受任何 EDNS0 OPTION)。
```

建议流水线位置: 流水线起始，cache 前，转发插件前。

### \_query\_summary (v4.5.3+) 记录请求摘要

在 info 级别记录请求。包括请求的域名，类型，客户端地址，应答状态，请求处理时间等。

建议流水线位置: 流水线起始，cache 前。

注: 如果请求没有或有多个 QUESTION，则不会有记录。

### sleep 延时执行

调试。模拟延时用。

流水线逻辑：

* 正向：延时。
* 反向：无。

初始化参数说明:

```yaml
tag::  ""
type: "sleep"
args:
  duration: 100  # 等待时间。单位: 毫秒。
```

## 匹配器插件

匹配器插件匹配请求和应答状态，返回 `true` or `false`。

### query\_matcher 匹配请求的特征

以下预初始化的插件 `tag` 可直接使用:

* `_qtype_AAAA`: 请求的 QTYPE 是 AAAA。
* `_qtype_A_AAAA`: 请求的 QTYPE 是 A 或 AAAA。
* `_query_edns0`: 请求包含 EDNS0。

初始化参数说明:

```yaml
tag: ""
type: "query_matcher"
args:
  qtype: [1, 2, 3]          # 匹配 QTYPE
  qclass: [1, 2, 3]         # 匹配 QCLASS
  domain:                   # 匹配请求域名。这是个`域名匹配器`。详见下文。
    - "google.com"
  client_ip:                # 匹配客户端 IP 。这是个 `IP 匹配器`。详见下文。
    - "192.168.1.1/24"
  ecs:                      # 匹配 ECS 的 IP 。这是个 `IP 匹配器`。详见下文。
    - "192.168.1.1/24"
```

如果一个匹配器内设定了多个条件，则必须满足全部条件这个匹配器才会为 `true`。

### response\_matcher 匹配应答的特征

以下预初始化的插件 `tag` 可直接使用:

* `_response_valid_answer`: 应答包含有效的 ANSWER 记录(ANSWER 中有记录能与 QUESTION 对应)。(v4.0.0+)

初始化参数说明:

```yaml
tag: ""
type: "response_matcher"
args:
  rcode: [1, 2, 3]          # 匹配 RCODE。
  ip:                       # 匹配应答 A 和 AAAA 记录的 IP 。这是个 `IP 匹配器`。详见下文。
    - "192.168.1.1/24"
  cname:                    # 匹配应答 CNAME 域名。这是个`域名匹配器`。详见下文。
    - "google.com"
```

如果一个匹配器内设定了多个条件，则必须满足全部条件这个匹配器才会为 `true`。

### 域名匹配器

配置示例:

```yaml
# 可以是文本数据。域名匹配规则详见下文。
- "google.com"
- "keyword:google"
- "regexp:.+\.google\.com$"

# 可以是由 data-provider 提供的外部数据，格式 "provider:tag"。
# 外部数据可以是包含多条文本数据的纯文本数据。
- "provider:data_tag"  
# 也可以是 v2ray 域名数据。多个标签用 "," 分割，标签后支持用 "@" 只载入有对应 attr 的数据。
# 多个 "@" 为"或"关系，域名只要有其中任意 attr 就会被载入。
- "provider:geosite:cn@attr@attr,us@attr..."
```

文本域名数据一条规则一行。如果域名匹配方式被省略，则默认是 `domain` 域匹配。

域名匹配规则说明详见: [这里](#yu-ming-pi-pei-gui-ze)

### IP 匹配器

配置示例:

```yaml
# 可以是文本数据。每行一个 CIDR 或 IP，支持 IPv6。
- "192.168.1.1/24"
- "2001:250::/31"
- "192.168.1.1"
# 可以是由 data-provider 提供的外部数据，格式 "provider:tag"。
- "provider:data_tag"       # 可以是纯文本文件。包含多条文本数据。
- "provider:geoip:cn,us..." # 可以是 v2ray geoip 文件。多个标签用 "," 分割。
```

### 关于 v2ray 数据包

mosdns 支持从 v2ray 数据包 (俗称 dat 文件)载入数据，但受限于 v2ray 数据包的打包格式，mosdns 从中读取数据，无论读取多少，都要**完整解包**。多个插件读取多次会出现重复解包的情况。性能不好的设备(主要是嵌入式路由等)上可能造成 mosdns 启动时间显著变长(数秒)。其他设备影响不大。

优化 v2ray 数据包加载速度:

* (推荐)自己提前解包: `mosdns v2dat` 小工具命令可以解包数据文件成独立的文本文件。
* 尽可能使用多标签 `geoip.dat:cn,us... 一次载入全部所需数据，而非多行。写成多行会造成重复解包。`

## 补充

### 域名匹配规则

域名规则有多个匹配方式:

* 以 `domain:` 开头，域匹配。e.g: `domain:google.com` 会匹配自身 `google.com`，以及其子域名 `www.google.com`, `maps.l.google.com` 等。
* 以 `full:` 开头，完整匹配。e.g: `full:google.com` 只会匹配自身。
* 以 `keyword:` 开头，关键字匹配。e.g: `keyword:google.com` 会匹配包含这个字段的域名，如 `google.com.hk`, `www.google.com.hk`。
* 以 `regexp:` 开头，正则匹配([Golang 标准](https://github.com/google/re2/wiki/Syntax))。e.g: `regexp:.+\.google\.com$`。

如果没有指定匹配方式，不同插件有不同的缺省值。

匹配方式按如下顺序生效: `full` > `domain` > `regexp` > `keyword`。

相同匹配方式的规则按如下顺序生效:

* `domain` 规则: 子域名优先。比如如果同时存在规则 `google.com` 和 `com`。 `www.google.com` 会优先匹配 `google.com`，然后 `com`。(v3.9.0+)
* `regexp` 和 `keyword` 规则生效顺序为规则导入的顺序。

性能:

* `domain` 和 `full` 匹配使用 HashMap，复杂度 O(1)。每 1w 域名约占用 1M 内存。
* `keyword` 和 `regexp` 匹配需遍历，复杂度 O(n)。

到底了！没有找到想要的功能么？提交一个 [Feature request](https://github.com/IrineSistiana/mosdns/issues/new?assignees=\&labels=\&template=feature_request.md\&title=%5BFeature+request%5D)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v4/cha-jian-ji-qi-can-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
