# 可执行插件

### sequence

按顺序组合多个插件。

sequence 是个可执行插件。可以被其他插件(比如服务器插件)调用。sequence 与 sequence 联动请使用自带的跳转命令 (goto, jump ...)，不要使用 exec 。

{% content-ref url="sequence-cha-jian" %}
[sequence-cha-jian](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/sequence-cha-jian)
{% endcontent-ref %}

### cache

缓存 DNS 应答的插件。

如果请求命中缓存，则将缓存的应答放入请求。

当某一请求经过 cache 后，cache 会在这个请求处理流程结束时自动将应答放入缓存。用户无需介入。

注意: 本插件是本地缓存(适用于客户端或者局域网内的缓存)。不适用于服务器端缓存。不支持扩展 DNS 协议 (即 EDNS0)，请求的 EDNS0 部分会被无视，应答的 EDNS0 会被略去。

初始化参数说明:

```yaml
tag: ""
type: "cache"
args:
  size: 1024  # 内置内存缓存大小。单位: 条。默认: 1024。每个 cache 插件的内存缓存是独立的。

  # (实验性) lazy cache 设定。lazy_cache_ttl > 0 会启用 lazy cache。
  # 所有应答都会在缓存中存留 lazy_cache_ttl 秒，但自身的 TTL 仍然有效。如果命中过期的应答，
  # 则缓存会立即返回 TTL 为 5 的应答，然后自动在后台发送请求更新数据。
  # 相比强行增加应答自身的 TTL 的方法，lazy cache 能提高命中率，同时还能保持一定的数据新鲜度。
  lazy_cache_ttl: 0        # lazy cache 生存时间。单位: 秒。默认: 0 (禁用 lazy cache)。
                           # 建议值 86400（1天）~ 259200（3天）
  
  # (实验性) 将缓存保存在磁盘。插件启动时也会自动从该文件载入缓存。
  # 缓存将在 mosdns 被关闭时保存。
  dump_file: ./cache.dump
  # (实验性) 自动保存间隔。单位秒。默认 600。
  # 如果距离上次 dump 有 1024 次更新，则自动保存。
  dump_interval: 600
```

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

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

Metrics 数据 `mosdns_cache_<metrics_name>{tag=<plugin_tag>}`:

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

API:

* GET /flush 清空缓存
* GET /dump 下载缓存
* POST /load\_dump 载入缓存

### hosts

域名映射 IP 。如果请求的域名在 hosts 内，则将应答放入请求。

初始化参数说明:

```yaml
tag: ""
type: "hosts"
args:
  entries:                         # []string
    - "google.com 108.177.122.113" # 记录
  files:                           # []string
    - "./hosts.txt"                # 从外部文件载入。
```

{% 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 ...
```

### forward

转发请求至上游服务器。获取应答后放入请求。

初始化参数说明:

```yaml
tag: ""
type: "forward"
args:
  concurrent: 1 # 并发数。每次请求会从下面配置的 upstreams 里随机选取
                # concurrent 个 upstreams 发送请求。取最快返回的应答。超过 3 最多选
                # 3 个。默认 1 。
  upstreams:    # []upstream, 上游服务器。至少要配置一个。
    - tag: google_doh
      addr: "https://dns.google/dns-query"
      dial_addr: "8.8.8.8"
      bootstrap: "8.8.8.8"
      bootstrap_version: 4
      socks5: "127.0.0.1:1080"
      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
```

* `tag`: 本上游的 tag。用于标识和 log。可省略。同一插件内该 tag 必需唯一。建议全局唯一。
* `addr` 必需。服务器地址。格式: `[protocol://]host[:port][/path]` 。`protocol` 默认为 `udp` 。省略端口号会使用协议的默认值。
  * 支持的 `protocol` 有:
    * `udp`: DNS over UDP 协议。`host` 必需为 IP。
    * `tcp`: 基本 DNS over TCP 协议。`host` 必需为 IP。
    * `tls`: 基本 DNS over TLS 协议。(RFC 7858)
    * `https`: 基本 DNS over HTTPS 协议。基于 HTTP/2 ，但为了兼容也支持 HTTP/1 。使用 GET 请求。(RFC 8484)
    * `quic`: DNS over QUIC 协议。 (RFC 9250)
  * 实际应用中，因为不应该让一个 DNS 解析系统依赖另一个 DNS 解析系统，因此 DNS 服务器的 IP 都是固定的。所以**服务器地址应优先使用 IP 地址而非域名**。这样插件连接服务器时无需先解析服务器的域名，能立刻建立连接。如果 `host` 必需包含域名 (比如一些 DoT/DoH 服务器可能要求请求必需包含 SNI 或 HTTP HOST 头)，可以将 IP 填入 `dial_addr` 。更详细说明：插件会依次尝试从以下途径获取服务器域名的 IP 。
    1. `dial_addr`: IP 协议层面建立连接时使用的地址。如果 `host` 是域名，此处填入 IP 可免去每次建立连接时解析服务器域名。支持指定端口号。
    2. `bootstrap`: (实验性) 手动指定用于解析 `host` 域名的 bootstrap 服务器。只能是 UDP 服务器。不推荐使用此方式。bootstrap 仅适用于没有固定 IP 的服务器，比如自建服务器。`bootstrap_version` 设定 4 或 6 可控制解析 IP 的版本。默认 0 等于 4 。
    3. 让系统去解析。注意: 此方式的解析时间插件不可控，可能影响性能。并且此时 mosdns 不能是系统的 DNS 服务器，否则会出现解析死循环。不推荐使用该方式。后续版本可能会禁止此方式。
* `socks5`: socks5 服务器地址。格式 `host:port`。数据将会通过该代理中转。暂不支持用户名密码认证。只支持基于 TCP 的协议。
* `idle_timeout` TCP/DoT/DoH 连接复用空连接保持时间。单位: 秒。默认 DoH/DoQ: 30 ，TCP/DoT: 10。一般不需要改。
* `enable_pipeline`: TCP/DoT 使用 RFC 7766 新的 query pipelining 连接复用模式。
  * 启用后可大幅提高连接利用率，减少建立连接/握手的次数，进而降低响应延时。
  * 并非所有服务器都支持。必须确定服务器支持后再启用该选项。
  * Tips: 已知 Google 和 Cloudflare 的 TCP/DoT 是支持该模式的。知名的公共 DNS 服务商大多数都支持该模式。可以使用 `mosdns probe pipeline {tcp|tls}://server_ip[:port]` 测试命令测试服务器是否支持。比如 `mosdns probe pipeline tls://8.8.8.8` 。
* `enable_http3`: (实验性) DoH 使用 HTTP/3 连接服务器。
  * 并非所有服务器都支持。必须确定服务器支持后再启用该选项。
* `max_conns`： 默认: 2。
  * (5.3+) 已弃用。最大连接数量由插件自动控制。
  * 启用了 query pipelining 模式的 TCP/DoT 协议: 最大连接数。大流量场景可适当调大。
  * DoH 协议: HTTP 的最大连接数。HTTP/3 除外。大流量场景可适当调大。
* `insecure_skip_verify`: 禁用 TLS 服务器证书验证。
* Linux 套接字设定。需要 `CAP_NET_ADMIN` 权限
  * `so_mark`: 设定 SO\_MARK。
  * `bind_to_device`: 设定 SO\_BINDTODEVICE。

错误处理:

如果 forward 未能从上游获取应答 (连接失败，收到错误数据，等待应答超时等)，则会抛出错误。

请注意: 来自服务器的 SEVFAIL 等应答属于正常的应答。不算错误。

sequence 中的调用参数:

指定上游进行转发。而非全部。

参数: `[upstream_tag] ...`

metrics 数据:

设定了 `upstream.tag` 的上游会有 metrics 数据。

`mosdns_forward_<metric_name>{tag=<plugin_tag>, upstream=<upstream_tag>}`

* query\_total 该上游总请求数。
* err\_total 该上游总出错数。(连接失败，无响应。不包括返回 SERVFAIL 的情况)&#x20;
* thread 该上游的线程数。(此时有多少请求正在被处理中)
* response\_latency\_millisecond 该上游应答返回的延时。
* 底层连接 (HTTP3 无该数据):
  * conn\_opened\_total 该上游打开的连接数。
  * conn\_closed\_total 该上游已关闭的连接数。

### redirect

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

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

初始化参数说明:

```yaml
tag: ""
type: "redirect"
args:
  rules:
  # 格式 [域名匹配规则] [重定向至域名] 
    - www.google.com dns.google
  files:  # 从文件载入规则
    - ./rediect.txt
```

* `域名匹配规则`: 如果匹配方式被省略，则默认是 `full` 完整匹配。域名匹配方式详见 [域名匹配规则](#yu-ming-pi-pei-gui-ze)。

### arbitrary&#x20;

(实验性) arbitrary 可以载入任意 zone file (RFC 1035) 记录，并在匹配到对应请求时生成包含这些记录的应答。

初始化参数说明:

```yaml
tag: ""
type: "arbitrary"
args:
  rules:
    - example.com. 300 IN A 192.0.2.1
    - _sip._tcp.example.com. 86400 IN SRV 0 5 5060 sipserver.example.com.
  files:
    - ./arbitrary.txt
```

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

### reverse\_lookup

(实验性) 用 IP 反查域名。该插件会缓存应答中的 IP 与 域名的关系。支持 PTR 和 HTTP API。

如果是 PTR 请求，并且命中缓存，则生成应答。

初始化参数说明:

```yaml
tag: ""
type: "reverse_lookup"
args:
  size: 65535 # 内置缓存大小。默认 65535。
  
  # 缓存时间。秒。默认 7200 (2h)。应答记录的 TTL 也会被自动修改，限制在该值以下。
  ttl: 7200
  # 是否主动处理/响应 PTR 请求。如果 PTR 的 IP 命中缓存，则生成应答。
  # 用途举例: 使用网络监视类工具查看网络连接时，开启域名反查功能大概率会看到 IP 对应的
  # 真实域名(如果程序先请求域名再建立连接，而且域名经过了该插件处理。)
  handle_ptr: false 
                    
```

查找缓存中 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 域名，末尾有 `.`。

### rate\_limiter

(非常实验性) 用于限制客户端的请求速率。是匹配器。超过限制的请求返回 false。

注意: 此插件目前只能提醒"友善的客户端和用户"控制其流量。不能抗恶意攻击。目前属于中看不中用的插件。后续尝试和 linux 防火墙，脚本调用(云防火墙)等进行联动。

```
tag: ""
type: "rate_limiter"
args:
  qps: 20    # 客户端的请求 qps 限制。默认 20
  burst: 40  # 突发数。允许客户端瞬间突发请求数。默认 40
  mask4: 32  # IPv4 网段掩码。来自同网段的请求会被视为来自同一客户端。默认 32
  mask6: 48  # IPv6 网段掩码。默认 48
```

使用方式举例: 在 sequence 中 rate\_limiter 匹配请求。判断 false (超 qps 请求)，取反，返回 REFUSE。

```
- matches: "!$rate_limit"
  exec: reject 3
```

### fallback

(实验性) 失败时回滚。

```
tag: ""
type: "fallback"
args:
  primary: executable_tag    # 主可执行插件的 tag
  secondary: executable_tag  # 副可执行插件的 tag
  threshold: 500           # 无响应回滚阈值。单位毫秒。默认 500 。
  always_standby: true     # 副可执行插件始终待命。 
```

回滚机制: 如果 primary 抛出错误，或返回但没有应答，或在 threshold 毫秒内无响应，则执行 secondary 。因无响应触发 fallback  时，如果 primary 比 secondary 先返回了应答，则依旧会采用 primary 的应答。

错误处理: 如果 primary 和 secondary 都无应答 (抛出了错误，无响应直到超时，返回了但无应答)，则抛出错误。

always\_standby: secondary 会和 primary 一并执行。secondary 的结果将会在需要回滚时，立刻被采用。

注意: fallback 是多线程。primary 和 secondary 是独立的线程，它们收到的请求是 fallback 收到的请求的镜像。任何一方对请求做出修改不会影响对方，也不会影响 fallback 所在的 [sequence](#sequence-an-shun-xu-zu-he-duo-ge-cha-jian) 。如果 primary 和 secondary 是 sequence ，则它是一级/根 sequence。

注意: 如需提高上游的稳定性，绝大多数(99%)的用户只需设置 forward 并发多个上游。只有极少数用户需要这个插件。

### ecs\_handler

(实验性) (v5.3+) 实现 EDNS0 Client Subnet 相关功能。

```
tag: ""
type: "ecs_handler"
args:
  forward: false        # 是否转发来自下游的 ecs
  preset: 1.2.3.4       # 发送预设 ecs
  send: false           # 是否发送 ecs
  mask4: 24             # ipv4 掩码。默认 24
  mask6: 48             # ipv6 掩码。默认 48
```

优先级:&#x20;

1. 如果 forward == true 并且下游请求包含 ecs。则转发该 ecs。否则
2. 如果 preset != nil。则发送该预设 ecs。否则
3. 如果 send == true，则发送客户端地址。
