插件及其参数

写在最前

mosdns 的插件流水线概念

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

“请求上下文”包含了

  • 客户端来源 IP 等信息。由服务端赋值。不可修改。

  • 客户的请求。插件可以自由修改请求的数据。

  • 应答。初始为空。插件可以自由修改应答的数据

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

配置文件中的插件

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

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

两个 forward 插件。tag 不同,参数不同。

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

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

mosdns 会按照配置文件中顺序将插件初始化。有些插件需要调用其他插件,比如 sequence,其调用的插件必须在之前配置。否则会因为找不到插件而初始化失败。

可执行插件

_return

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

流水线逻辑:

  • 正向:跳过后续流水线。

  • 反向:无。

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

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

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

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

初始化参数说明:

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"
        # ...           # 不限数量,可继续添加。
高级操作: 并发、失败自动回滚(故障转移)
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 始终待命。可省略。

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

fallback 两种触发方式:

  • 常规 fallback: 监控主流水线最近 stat_length 次的请求状态,如果出现 threshold 次问题(没有应答或者有报错),则下次请求会一并执行次流水线。取最先返回的应答。

  • 快速 fallback: 对于每一个请求,如果主流水线运行结束了但没有返回应答,或者在 fast_fallback 毫秒后仍在运行无应答,则立即开始执行次流水线,取最先返回的应答。如果任意流水线返回了应答,这个请求就不会失败。比常规 fallback 策略更激进,次流水线可能运行的更频繁。

    • secondary 始终待命: 如果设为 true,则对于每一个请求,次流水线都会和主流水线一并执行,其结果将会在需要回滚时(没有返回应答或 fast_fallback 超时),直接被使用。进一步降低响应时间。

  • 快速和常规 fallback 可以同时启用。

cache 缓存

缓存 DNS 应答的插件。

流水线逻辑:

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

  • 反向:缓存应答。

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

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

初始化参数说明:

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 内,则将应答放入“请求上下文”,跳过后续流水线。如果不在,则继续执行后续流水线。

  • 反向:无。

初始化参数说明:

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

虽然都叫 hosts,但本插件所用的格式和平常 Win,Linux 系统内的那个 hosts 文件不一样。

格式:

  • 域名规则在前,IP 在后。支持一行多个 IP,支持 IPv6。

  • 如果域名匹配规则的方式被省略,则默认是 full 完整匹配。域名匹配规则详见 这里

格式示例:

# 支持一行多个 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 示例:

- _prefer_ipv4
- ...
- forward_xxx

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

ecs 为请求附加 ECS

流水线逻辑:

  • 正向:附加 ECS 至请求。

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

初始化参数说明:

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 修改应答的生存时间

流水线逻辑:

  • 正向:修改应答的生存时间。

  • 反向:无。

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

初始化参数说明:

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: 域名不存在)应答。放入“请求上下文”。

流水线逻辑:

  • 正向:丢弃或生成对应应答。

  • 反向:无。

初始化参数说明:

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 自带模块。

流水线逻辑:

  • 正向:转发请求至上游,获取应答后放入“请求上下文”。

  • 反向:无。

初始化参数说明:

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

流水线逻辑:

  • 正向:转发请求至上游,获取应答后放入“请求上下文”。

  • 反向:无。

初始化参数说明:

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

上述列表和参数说明不一定是最新的,请以 dnsproxy 的 README 为准。

ipset 将应答 IP 写入到系统 ipset

流水线逻辑:

  • 正向:将应答 IP 写入 ipset。

  • 反向:无。

初始化参数说明:

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 表命令示例:

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。

  • 反向:无。

初始化参数说明:

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 命令示例:

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)。

  • 反向:无。

初始化参数说明:

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

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

参考 RFC 8467,使用 EDNS0 的 Padding Option 将 DNS 报文用 0 填充至固定长度。据 Google 说 (Google Plublic DNS 文档) 能防止流量分析保护隐私。包含 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 扩展。

初始化参数说明:

tag: ""
type: "redirect"
args:
  rule:
  # 格式 [域名匹配规则] [重定向至域名] 
    - www.google.com dns.google
    - provider:tag # 可以是由 data-provider 提供的外部数据。
  • 域名匹配规则: 如果匹配方式被省略,则默认是 full 完整匹配。域名匹配方式详见 域名匹配规则

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

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

流水线逻辑:

  • 正向:如果找到对应记录,则生成应答,终止跳过后续流水线。如果不在,则继续执行后续流水线。

  • 反向:无。

初始化参数说明:

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 写入缓存。

初始化参数说明:

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 结束计时。

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 应答,跳过后续流水线。

  • 反向:无。

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。

初始化参数说明:

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 延时执行

调试。模拟延时用。

流水线逻辑:

  • 正向:延时。

  • 反向:无。

初始化参数说明:

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。

初始化参数说明:

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+)

初始化参数说明:

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

域名匹配器

配置示例:

# 可以是文本数据。域名匹配规则详见下文。
- "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 域匹配。

域名匹配规则说明详见: 这里

IP 匹配器

配置示例:

# 可以是文本数据。每行一个 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 标准)。e.g: regexp:.+\.google\.com$

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

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

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

  • domain 规则: 子域名优先。比如如果同时存在规则 google.comcomwww.google.com 会优先匹配 google.com,然后 com。(v3.9.0+)

  • regexpkeyword 规则生效顺序为规则导入的顺序。

性能:

  • domainfull 匹配使用 HashMap,复杂度 O(1)。每 1w 域名约占用 1M 内存。

  • keywordregexp 匹配需遍历,复杂度 O(n)。

到底了!没有找到想要的功能么?提交一个 Feature request

Last updated