Sequence 插件

sequence 是一个 可执行插件 ,可以被其他插件调用。因其功能较多,单独写在本页面。

写在最前

参数帮助说明

  • { arg1 | arg2 | ... } 代表这几个参数必需选择其中一个(多选一)。

  • [ arg ] 代表该参数是可选的。可省略。

  • ... 表示之前的参数可以无限重复。*n 表示之前的参数可以重复 n 次 。

  • 没有符号的 arg 表示该参数不可省略。

参数

初始化参数说明:

tag: ""
type: "sequence"
args:  # []rule
  - matches:                # []string, 匹配条件。多个匹配条件为 AND 关系。
    - qname $ad_domain      # [!]{ 内置匹配名称 | $已定义的匹配器插件tag } [参数]
    - "!qname $ad_domain"   # !前缀代表取反。
    exec: reject 3          # string, { 内置操作名称 | $已定义的可执行插件tag } [参数]
  - exec: $forward_google   # matches 条件可省略。

sequence 插件引用的所有已定义的插件 tag 必须在 sequence 前定义,否则 sequence 找不到对应插件。

一个 sequence 由多个 rule 构成。一个 rule 由零个或多个条件和一个操作构成。sequence 会依次执行每个 rule 。

可以把 sequence 近似成处理 DNS 请求的 iptables / nftables 的 chain 。复杂的处理逻辑可以配置多个 sequence 然后配合 accept/reject/jump/goto/return 操作在 sequence 之间跳跃。

可执行插件列表见:

page可执行插件

错误处理:

如果遇到插件抛出错误,sequence 不会继续执行后续规则,并继续抛出该错误。

配置示例:
tag: ""
type: "sequence"
args:
  # 屏蔽某些域名,比如广告域名。
  - matches:
      - qname $ad_domain
    exec: reject 3
  - ...
  
  # 将内网请求转交给 handle_local_query sequence 另行处理。
  - matches:
      - qname local internal.my.domain
    exec: goto handle_local_query
  - matches:
      - ptr_ip 10.0.0.0/8
    exec: goto handle_local_query

  # 让某些域名跳过 cache,比如的 DDNS 域名。然后仅
  # 修改 DDNS 域名的 TTL。
  - matches:
      - "!qname my.ddns.domain"
    exec: cache 1024
  - exec: forward 8.8.8.8
  - matches:
      - qname my.ddns.domain
    exec: ttl 1

  # env 实现"开关功能"。
  # 运行 mosdns 时设定 MOSDNS_ENABLE_CACHE 环境变量即可开启 cache 功能。
  - matches:
      - env MOSDNS_ENABLE_CACHE
    exec: cache 1024
  - ...

内建匹配器

resp_ip

匹配应答 A 和 AAAA 记录的 IP 。如果应答有多个 IP 只要任意 IP 匹配即为 true 。

参数:

resp_ip { ip | $ip_set_tag | &ip_list_file } ...

示例:

resp_ip 192.168.1.1/24 8.8.8.8 $local_ips &./local_ips.txt
  • ip 可以是 IPv4/6, CIDR 。

  • ip_set_tag 是类型为 ip_set 插件的 tag 。

  • ip_list_file 是文本类型的 IP/CIDR 表。

client_ip

匹配客户端的 IP 。

参数和示例和上文的 resp_ip 相同。

ptr_ip

匹配 PTR 请求的 IP。

参数和示例和上文的 resp_ip 相同。

qname

匹配请求的 QNAME 。

参数: qname { domain_rule | $domain_set_tag | &domain_list_file } ...

示例: qname google.com full:google.com $local_domains &./local_domains.txt

  • domain_rule 是域名表达式。支持扩展。详见 域名匹配规则

  • domain_set_tag 是类型为 domain_set 插件的 tag 。

  • domain_list_file 是文本类型的域名表达式表 。

cname

匹配应答的 CNAME 记录。如果应答有多个 CNAME 只要任意 CNAME 匹配即为 true 。

参数和示例和上文的 qname 相同。

qclass

匹配请求的 QCLASS。

参数: qclass [uint16] ...

示例: qclass 1 2 3

qtype

匹配请求的 QTYPE。

参数和示例和上文的 qclass 相同。

rcode

匹配应答的 RCODE。

参数和示例和上文的 qclass 相同。

has_resp

如果有应答,返回 true 。

has_wanted_ans

如果有期望的 ANSWER,返回 true 。"期望的ANSWER "指应答的 ANSWER 中包含和 QUESTOIN 类型相同的 record。比如请求 A 记录,应答里返回了 A 记录,则该判断为 true。请求 AAAA 记录,虽然返回了SUCCESS 应答但无 AAAA 记录,返回 false 。

mark

参数: mark [uint32] ...

示例: mark 1 2 3

请求存在任意指定的标记即为 true 。

string_exp

(实验性) 字符串匹配器。

参数: string_exp string_src op [arg_string]

示例: string_exp server_nameeq a.b.c d.e.f

string_src: 匹配的字符串来源。可以是

  • url_path: 来自 DoH 服务端请求的 url path 。

  • server_name: 来自服务端 TLS (DoH,DoT,DoQ) 的 SNI。

  • $env: 以 $ 开头,环境变量的值。

op 是匹配方式。可以是 eq 相等,prefix 包含前缀,suffix 包含后缀,contains 包含,regexp 正则,zl 字符串长度为零。

arg_string 是匹配参数,目前除了 zl 无参数,其余匹配方式均可包含多个字符串作为匹配规则。任意规则匹配即返回 true 。

注意: 受格式限制,目前不支持带空格的规则参数。

其他匹配器

_true 和 _false

永远返回 true 和 false 。方便脚本生成占位符。

_ 前缀没有别的意义,只是防止匹配器名在某些配置格式里被误识别成 bool 类型。

env

参数: env env_key [env_value]

示例: env CACHE_ENABLEDenv NETWORK_TYPE lan

启动时查找 env_key ,如果值和 env_value 一致则返回 true。如果 env_value 省略,则 env_key 存在(包括零值的情况)即返回 true 。

random

参数: random prob_float64

示例: random 0.5

prob_float64 的概率返回 true 。

内建操作

forward

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

参数: forward upstream [upstream]...

示例: forward 8.8.8.8 tls://8.8.8.8

可配置多个上游。会并发请求,取最先返回的应答。如果超过 3 个上游,会随机选取 3 个上游并发。

upstream 支持四种协议。格式示例:

  • 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

  • Tips: 使用 IP 地址能更快建立连接,因为不用解析服务器的 IP。除非情况特殊,否则不要使用带域名的地址。

这是个便捷设置。如需更多高级功能 (HTTP3,Socks5,SO_MARK...),可配置独立的 forward 插件。

cache

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

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

不支持扩展 DNS 协议 (即 EDNS0)。

参数: cache [size]size 是缓存大小,单位: 条,默认 1024。

示例: cache 1024

这是个便捷设置。如需更多高级功能 (lazy cache,缓存存盘...),可配置独立的 cache 插件。

black_hole

生成包含特定 IP 的应答。

参数: black_hole [ip] ...

示例: black_hole 127.0.0.1 ::1 0.0.0.0

会自动识别参数中的 IPv4/6 。如果参数中指定了多个 IPv4/6,会全部包含在应答内。如果某一 IP 类型未指定,则不会生成对应类型的应答。

drop_resp

丢弃应答。用于触发 fallback。

ecs

附加 ECS (EDNS0 Client Subnet) 至请求。支持 IPv4/6。IPv4 会使用 /24 mask,IPv6 使用 /48 。

参数 (v5.3+): ecs [ip]

参数 (v5.2 以前)(弃用): ecs [ip/mask] [ip/mask] 。v5.3+ 后只会用第一个 IP,mask 会忽略。

示例: ecs 1.2.3.4

forward_edns0opt

(实验性)(v5.3+) 允许客户端的这些 EDNS0 Option 转发至上游。

参数: forward_edns0opt [option_code] ...

注意:

  • 此插件不可以执行多次! 否则转发至上游的请求中 EDNS0 Option 将出现重复,导致协议错误。

  • 并不是所有 EDNS0 Option 都能被转发。此插件设计的目是转发 10 (Cookie) 和 15 (Extended DNS Error)。

ipset

(实验性) 写入应答 IP 至 ipset 。仅支持 Linux 。插件会直接用 netlink 和内核通信,不依赖也不运行 ipset 命令,效率很高。

参数: ipset [set_name,set_family,mask]*2

示例: ipset my_set,inet,24 my_set6,inet6,48

  • set_family = { inet | inet6 }

  • mask 是写入 IP 时使用的掩码

nftset

(实验性) 写入应答 IP 至 nftables 。仅支持 Linux 。插件会直接用 netlink 和内核通信,不依赖也不运行 nft 命令,效率很高。

参数:

nftset [table_family,table_name,set_name,set_dtype,mask]*2

示例:

nftset inet,my_table,my_set,ipv4_addr,24 inet,my_table,my_set,ipv6_addr,48
  • table_family = { ip | ip6 | inet } table 表的类型。

  • set_dtype = { ipv4_addr | ipv6_addr } set 的类型。

  • mask 是写入 IP 时使用的掩码。插件会自动判断 set 是否带有 interval 这个 flag,如果 set 有 interval flag 的话,会以 IP 段的形式写入 set 。

ipset/nftables 配合 cache 示例

以 ipset 为例:

tag: ""
type: "sequence"
args:
  - exec: cache 1024
  - matches:        # 如果缓存命中,则不会转发请求至上游。
    - "!has_resp"
    exec: forward 8.8.8.8
  - exec: ipset ...  # ipset 每次请求都能被刷新

query_summary

以 info 级别在 log 打印请求的摘要。包含: 请求的域名,类型,客户端 IP,应答的 RCODE,错误信息,后续规则处理请求的用时。

参数: query_summary [ msg_title ]

示例: query_summary query from server 1

  • msg_title 的文字会写在 log 的 msg 键上。

如果一个请求经过了多个 query_summary,log 打印的顺序是反的,即先打印最后经过的 query_summary 的 log。这不是 bug 。

metrics_collector

(实验性) 收集 prometheus metrics 数据。

参数: metrics_collector collector_name

  • collector_name 不可重复。

暴露的 metrics 数据项名称为

mosdns_metrics_collector_<metric_name>{name=collector_name}

插件会收集以下 metric:

  • query_total 经过该收集器的总请求数。

  • err_total 该收集器的后续规则的总出错数。

  • thread 该收集器后续规则的线程数。(此时有多少请求正在被后续规则处理中)

  • response_latency_millisecond 该收集器后续命令返回的延时。

prefer_ipv4/6

希望客户端解析双栈域名 IP 时,只返回 IPv4/6 。但不影响纯 IPv6/4 (单栈)域名的解析。

prefer_ipv4prefer_ipv6 是两个操作。下面为了表述方便,仅以 prefer_ipv4 为例。prefer_ipv6 同理。

prefer_ipv4 让下游客户端支持双栈时优先使用 IPv4。prefer_ipv4 处理 AAAA 请求时会自动发送 A 请求测试域名是否是双栈,该新产生的 A 请求会和原 AAAA 请求一并继续执行后续规则。如果最后 A 和 AAAA 请求都获得到了 IP (域名为双栈),则原 AAAA 请求被屏蔽。这样客户端只能收到 A 记录,进而强迫客户端使用 IPv4

(实验性特性)(v5.3+) 插件会缓存域名的双栈支持情况,以避免频繁测试。缓存大小 65535 。缓存时长 1 小时。暂不支持自定义。

使用位置要求: 后续规则必须能生成应答 (比如有 forward)。

sequence 示例:

# 所有请求都优先返回 ipv4,屏蔽 ipv6。
- exec: prefer_ipv4
- ...
- exec: forward 8.8.8.8

# 配合匹配器实现仅特定域名生效。
- matches:
    - qname my.domain
  exec:
    - prefer_ipv4
- ...
- exec: forward 8.8.8.8

ttl

修改应答的 TTL。

参数: ttl { fix | min-max }

示例: ttl 300 将 ttl 固定在 300, ttl 300-600 限制 ttl 在 300 到 600 之间。ttl 300-0 限制 ttl 不小于 300 。

mark

在请求上打标记。

参数: mark [uint32] ...

示例: mark 1 2 3

调试用

sleep

等待一段时间。增加/模拟延时用。

参数: sleep millisecond

debug_print

以 info 级别在 log 打印完整的请求和应答信息。

参数: debug_print [msg_title]

示例: debug_print query from server 1

  • msg_title 的文字会写在 log 的 msg 键上

内建跳转操作

accept

停止后续规则执行。如果本 sequence 存在上级 sequence,所有上级的 sequence 也不再继续执行后续规则。

reject

参数: reject [rcode]

示例: reject 5

和 accept 一样,如果本 sequence 存在上级 sequence,所有上级的 sequence 也不再继续执行后续规则。同时生成 rcode 应答。不指定 rcode 默认为 5 (REFUSED) 。

return

停止后续规则执行。如果所在 sequence 是从另一 sequence jump 过来的,则返回源 sequence 继续执行。

goto

参数: goto sequence_tag

示例: goto my_next_sequence

sequence_tag 是目标 sequence 插件的 tag。将不再执行本 sequence 后续规则,开始执行目标 sequence 。

jump

参数: jump sequence_tag

示例: jump my_next_sequence

sequence_tag 是目标 sequence 插件的 tag。将开始执行目标 sequence,目标 sequence 结束或 return 后,返回本 sequence 继续执行。目标 sequence accept/reject 后,本 sequence 和所有上级的 sequence 不再继续执行后续规则。

mark 高级用法示例

实现逻辑或,单次匹配后执行多个 exec,以及实现 if ... else。

- tag: main
  type: sequence
  args:
  
    # 匹配成功后打标
    # 打相同标签可实现"逻辑或"
    - matches:
      - "match_A"
      exec: mark 123456789
    - matches:
      - "match_B"
      exec: mark 123456789

    # 连续执行
    - matches: mark 123456789
      exec: do_something_1
    - matches: mark 123456789
      exec: do_something_2
    # ...
    # 连续执行 else
    - matches: "!mark 123456789"
      exec: else_do_something_1
    - matches: "!mark 123456789"
      exec: else_do_something_2

Last updated