跳到主要内容

插件扩展 API

插件可以在统一 tag 路径下注册管理和观测接口。路由是否存在取决于当前配置和二进制编译能力。

插件扩展 API

统一格式

/api/plugins/<plugin_tag>/<route>

说明:

  • 也有少量插件会把主资源绑定到前缀路由下,例如 query_recorder/api/plugins/<tag>/records/<id>

cache

GET /api/plugins/<cache_tag>/entries

分页读取缓存项。

查询参数:

  • limit:每页数量,默认 100,最大 500
  • cursor:分页游标。
  • qname:按缓存键中的查询域名做大小写不敏感的包含筛选。

GET /api/plugins/<cache_tag>/flush

清空缓存。

GET /api/plugins/<cache_tag>/dump

导出缓存 dump。

POST /api/plugins/<cache_tag>/load_dump

导入缓存 dump。

matcher

配置在 plugins: 中、具有稳定 tag 的 matcher 都会注册以下运行时控制接口:

GET /api/plugins/<matcher_tag>/status
POST /api/plugins/<matcher_tag>/mode

两个接口返回相同的状态结构:

{
"ok": true,
"matcher": "match_cn",
"mode": "normal"
}

设置模式时提交 JSON 请求体:

{ "mode": "always_false" }

mode 支持 normalalways_falsealways_true,重复设置同一模式是幂等操作。两个固定模式都会跳过内部 matcher,并固定 matcher 的基础布尔值;每个引用随后仍独立应用取反。因此 always_false$match_cn 不命中而 !$match_cn 命中,always_true 下结果相反。缺少或不支持的模式返回 400 invalid_matcher_runtime_mode

模式只属于当前 runtime,不写入 YAML;应用级 reload 或进程重启后恢复为 normalsequenceany_match 内的 quick-setup matcher 使用内部 qs.match... tag,不注册控制接口。如需运行时控制,应先将其提取为 plugins: 中的独立 matcher 并通过 $tag 引用。

WebUI 对 always_falsealways_true 都要求二次确认;恢复 normal 不需要确认。旧的 /enable/disable 接口和 enabled 响应字段已经移除,API 客户端必须迁移到 /mode

provider

POST /api/plugins/<provider_tag>/reload

作用:

  • 使用 provider 启动时的同一份配置,定向刷新该 provider 的内部数据快照。
  • 不会重建其它插件,也不会修改 provider tag、依赖关系或配置拓扑。

返回:

  • 200 OK
    • provider 已成功 reload。
  • 400 Bad Request
    • provider 在 reload 过程中返回错误。
  • 404 Not Found
    • 对应 tag 不是当前 runtime 中已加载的 provider,因此没有注册该路由。
  • 409 Conflict
    • 同一个 provider 已有 reload 正在执行;新请求会立即返回 provider_reload_busy,不会排队。

适用场景:

  • 规则文件下载完成后,只刷新受影响的 domain_setip_setgeositegeoipadguard_rule provider。
  • 需要避免应用级全量 POST /api/reload 对其它插件造成重建影响。

注意:

  • 如果变更涉及 config.yaml、provider 依赖拓扑、插件列表或其它非 provider 结构,仍然需要使用 POST /api/reload

WebUI 在所有已应用的 provider 卡片和详情页中提供“重新加载数据”操作。该操作不会弹出配置变更确认;执行期间会阻止同一 provider 的重复请求,并展示成功、失败或 busy 结果。

reverse_lookup

GET /api/plugins/<tag>?ip=<ip_addr>

按 IP 查询缓存中的域名。

示例:

GET /api/plugins/reverse_lookup_main?ip=8.8.8.8

返回:

  • 命中:域名文本,通常为 fully-qualified domain name。
  • 未命中:空响应体。
  • 参数错误:400 Bad Request

query_recorder

GET /api/plugins/<tag>/records

created_at_ms 倒序返回 recorder 主表中的记录列表,不包含 steps

查询参数:

  • cursor=<created_at_ms>:<id>
    • 用于继续向后翻页。
  • limit=<n>
    • 默认 100,最大 500
  • since_ms=<unix_ms>
    • 仅返回大于等于该时间的记录。
  • until_ms=<unix_ms>
    • 仅返回小于等于该时间的记录。
  • qname=<text>
    • 按请求问题名做大小写不敏感的包含匹配。
  • client_ip=<text>
    • 按客户端 IP 字符串做大小写不敏感的包含匹配,可输入 IPv4/IPv6 片段。
  • qtype=<type>
    • 按请求问题类型精确匹配。
  • rcode=<rcode>
    • 按响应码精确匹配。
  • status=all|error|has_response|no_response
    • 按记录状态过滤。

client_ip 是 DNS 传输层看到的对端地址。若记录列表或 /stats/top_clients 全部显示 127.0.0.1,通常说明请求先经过了本机转发器,例如 systemd-resolved、dnsmasq、AdGuardHome、dae 或 clash;请检查部署链路,让客户端直接访问 OxiDNS,或在 HTTP/DoH 反向代理场景配置可信的 src_ip_header

返回:

  • 200 OK
    • JSON,形如:
{
"ok": true,
"next_cursor": "1713510000123:42",
"records": [
{
"id": 42,
"created_at_ms": 1713510000123,
"elapsed_ms": 12,
"request_id": 1234,
"client_ip": "192.0.2.10",
"questions_json": [
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
],
"req_rd": true,
"req_cd": false,
"req_ad": false,
"req_opcode": "Query",
"req_edns_json": null,
"error": null,
"has_response": true,
"rcode": "NoError",
"resp_aa": false,
"resp_tc": false,
"resp_ra": true,
"resp_ad": false,
"resp_cd": false,
"answer_count": 1,
"authority_count": 0,
"additional_count": 0,
"answers_json": [
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
],
"authorities_json": [],
"additionals_json": [],
"signature_json": [],
"resp_edns_json": null
}
]
}

GET /api/plugins/<tag>/records/<id>

返回单条完整记录,并附带 steps 路径事件数组。

返回:

  • 200 OK
    • JSON,包含 record 对象;其中 record.record 为主表字段,record.steps 为路径事件。
  • 404 Not Found
    • 记录不存在。

DELETE /api/plugins/<tag>/records

清空当前 recorder 的所有历史查询记录和 steps 路径事件。清空操作会先 flush 后台写入队列,随后删除 SQLite 记录表中的所有行,并清空内存 tail。

返回:

  • 200 OK
    • JSON,形如:
{
"ok": true,
"cleared_records": 128
}

GET /api/plugins/<tag>/stats/plugins

按路径事件聚合插件命中情况。

查询参数:

  • since_ms=<unix_ms>
  • until_ms=<unix_ms>
  • kind=matcher|executor|builtin|all
  • /records 支持 qnameclient_ipqtypercodestatus 过滤。

返回字段:

  • kind
  • tag
  • checked
  • matched
  • executed
  • query_total
  • query_share

GET /api/plugins/<tag>/stats/top_clients

按客户端 IP 聚合查询次数。

查询参数:

  • limit=<n>
    • 返回桶数,默认 20。后端不再强制 200 上限;过大的值会增加 SQLite 排序和响应体成本。
  • /records 支持 since_msuntil_msqnameclient_ipqtypercodestatusmatcher_tag 过滤。

返回字段:

  • sample_size
  • rows[].key
  • rows[].count
  • rows[].share

GET /api/plugins/<tag>/stats/top_qnames

按查询问题名聚合查询次数。查询参数和返回字段同 /stats/top_clients

GET /api/plugins/<tag>/stats/qtype

按 QTYPE 聚合分布。支持 /records 的时间范围与过滤参数,返回 sample_sizerows[].key/count/share

GET /api/plugins/<tag>/stats/rcode

按响应码或特殊状态桶聚合分布。支持 /records 的时间范围与过滤参数,返回 sample_sizerows[].key/count/share

GET /api/plugins/<tag>/stats/latency

返回延迟摘要、直方图和慢查询排行。

查询参数:

  • slow_limit=<n>limit=<n>
    • 慢查询排行返回数量,默认 20。后端不再强制 200 上限。
  • /records 支持时间范围与过滤参数。

GET /api/plugins/<tag>/stats/timeseries

按时间桶聚合查询趋势。

查询参数:

  • bucket=minute|hour
  • buckets=<n>
    • 返回桶数,默认 60,最大 720
  • /records 支持时间范围与过滤参数。

GET /api/plugins/<tag>/stream

通过 SSE 推送新写入的完整记录。

查询参数:

  • tail=<n>
    • 先回放最近 n 条内存 tail,再持续推送新记录。

说明:

  • event: recorddata 为完整 RecordDetail JSON。
  • 会定期发送 heartbeat 注释帧以保持长连接。
  • 客户端应使用 Accept: text/event-stream,并能容忍 heartbeat、错误事件、空 payload 和临时断连。