规则、IP 名单与 GeoIP
网站规则、全局规则、批量重定向、IP 名单,以及节点本地的 GeoIP 数据库。
概念
| 术语 | 定义 |
|---|---|
| 规则 | 一个阶段内的「表达式 + 动作」。网站规则只作用于一个网站;全局规则作用于所有集群的全部网站。 |
| 阶段 | 请求处理过程中执行规则的固定位置,共 9 个。 |
| 表达式 | wirefilter 风格的类型化条件表达式,例如 ip.src in $blocked。 |
| 值表达式 | 按请求算出字符串的表达式,作为重定向目标、改写路径、请求头与响应头的值或「设置查询参数」的值,例如 concat("/new", http.request.uri.path)。 |
| 批量重定向 | 网站的精确匹配重定向表,每个网站最多 5000 条。 |
| IP 名单 | 命名的 IP 地址与 CIDR 集合,表达式用 $名称 引用;放行名单与拦截名单另外直接作用于所有网站。 |
控制台解析并检查表达式的字段、类型和动作后发布语法树;节点校验整份配置后把语法树编译执行,配置中不含可执行的 Lua 文本。
编辑网站规则
-
打开 网站,选择网站,进入「规则」页签。
-
在目标阶段右侧点击「添加规则」。
-
填写规则名称和「表达式」。「插入条件」追加常用条件(路径前缀、Host 等于、文件扩展名、IP 段、IP 名单、国家/地区、ASN、User-Agent 包含、请求方法、Cookie 等于、查询参数等于、User-Agent 通配、Referer 通配)并选中其中的示例值,直接输入即可替换;「插入字段」追加字段,选择「Cookie」「查询参数」「请求头」时先在出现的输入框中填写名称,按回车或点击「插入」;二者都以
and连接。表达式有误时,编辑框下方显示「第 N 个字符:原因」,例如「第 11 个字符:大小比较只用于数字字段」;保存时被控制台拒绝的表达式同样显示位置和原因。 -
选择「动作」并填写动作参数,打开「启用」。新规则默认停用(经 API 保存时省略
enabled也是停用),表达式默认是true(匹配全部请求),保存前确认条件和动作。 -
拖动规则左侧的手柄调整同一阶段内的顺序。
-
点击「保存」。控制台提示「已保存」,并发布新的配置版本(原因「已更新规则与 IP 名单」)。
-
验证:例如表达式
http.request.uri.path eq "/blocked"配合「拦截」动作:curl -sI -H 'Host: www.example.com' http://<节点 IP>/blocked返回 403,响应头含
X-Edgeweir-Error: policy-denied。
全局规则在 全局规则 页编辑,界面与网站规则相同(「源站覆盖」没有「源站组」),作用于所有网站,保存后向所有集群发布新版本。API:GET、PUT /api/v1/platform-rules。
停用的规则不下发到节点。
阶段与动作
阶段按下表顺序执行。
| 阶段 | 可用动作 | 作用 |
|---|---|---|
| 请求变换 | 改写路径、请求头 | 改写回源路径与查询参数;设置或移除请求头 |
| 重定向 | 重定向 | 返回 301、302、303、307 或 308;规则之后查批量重定向 |
| 配置 | 覆盖设置 | 按请求覆盖网站设置,见覆盖设置 |
| 自定义 WAF | 拦截、记录、放行、挑战 | 拦截返回 403 或 451;记录只写日志;放行跳过同一作用域剩余的自定义 WAF 规则;挑战要求访客先通过挑战 |
| 限速 | 限速 | 固定窗口计数,超额返回 429 或 403 |
| 缓存 | 覆盖设置 | 只覆盖绕过缓存、强制 HTTPS 与 Gzip |
| 回源 | 请求头、源站覆盖 | 设置或移除发往源站的请求头;选择源站组,覆盖回源 Host、SNI 与端口 |
| 响应变换 | 响应头 | 根据状态码和响应头设置、追加或移除响应头 |
| 压缩 | 压缩算法 | 限定本次响应可用的压缩算法及其优先顺序 |
动作字段
| 动作 | 字段 | 取值 | 默认值 |
|---|---|---|---|
| 拦截 | 状态码 | 403 / 451 | 403 |
| 挑战 | 挑战类型 | Cookie 跳转 / JS 计算 / 工作量证明 / 图片验证码 | JS 计算 |
| 重定向 | 目标 | 「静态」:站内绝对路径(以 / 开头、不以 // 开头),或不含账号和空白的 http(s) URL;不含反斜杠与控制字符,最长 4096 字符;「表达式」:值表达式,见动态目标与查询参数 | 静态,/ |
| 重定向 | 状态码 | 301 / 302 / 303 / 307 / 308 | 301 |
| 重定向 | 保留查询参数 | 开 / 关 | 关 |
| 改写路径 | 目标 | 「静态」:以 / 开头,不以 // 开头,不含 ?、#、\;「表达式」:值表达式 | 静态,/ |
| 改写路径 | 保留查询参数 | 开 / 关 | 开 |
| 重定向 / 改写路径 | 设置查询参数 | 最多 16 组「参数名」与「参数值」,「添加参数」增加一组;参数名为 1–64 个字母、数字或 . _ ~ -,不重复;参数值「静态」为可打印 ASCII,最长 256 字符,「表达式」为值表达式,见报头值与查询参数值 | 无 |
| 重定向 / 改写路径 | 移除查询参数 | 参数名,逗号分隔,最多 16 个;不能同时出现在「设置查询参数」中 | 空 |
| 请求头 / 响应头 | 头名称 | 1–64 个 token 字符,不能是受保护头 | x-custom |
| 请求头 / 响应头 | 值 | 「静态」:最长 4096 字符,不含控制字符;「表达式」:值表达式,见报头值与查询参数值 | 静态,空 |
| 请求头 / 响应头 | 移除请求头或响应头 | 开 / 关;开启时没有值 | 关 |
| 响应头 | 追加 | 开 / 关;开启时在响应已有的同名头之外再加一行(例如 Link、Vary),关闭时替换 | 关 |
| 覆盖设置 | 各项设置 | 见覆盖设置 | 绕过缓存「开启」,其余「不更改」 |
| 源站覆盖 | 源站组 | 「默认组」或网站的源站组,见源站组 | 网站的第一个源站组;没有时为「默认组」 |
| 源站覆盖 | 回源 Host | 主机名或 IP,可带端口,写法同源站的回源 Host;空为不更改 | 空 |
| 源站覆盖 | SNI | 主机名;空为不更改 | 空 |
| 源站覆盖 | 端口 | 1–65535;空为不更改 | 空 |
| 压缩算法 | 优先顺序 | Zstandard、Brotli、Gzip 中的若干个:「添加算法」追加,上下移动调整顺序;列表为空时显示「不压缩」 | 不压缩 |
| 限速 | 预设 | 宽松(每分钟 300 次)、标准(每分钟 100 次)、严格(每分钟 20 次)或自定义;选择自定义后显示下面两项 | 标准 |
| 限速 | 每个窗口请求数 | 1–100000 | 100 |
| 限速 | 窗口(秒) | 1–3600 | 60 |
| 限速 | 限速键 | ip.src、http.host、tls.ja4 或 http.request.headers.<名称>(选择「请求头」后填写名称) | ip.src |
| 限速 | 状态码 | 429 / 403 | 429 |
受保护头不能通过规则设置或移除:Host、Authorization、Proxy-Authorization、Cookie、Set-Cookie、Content-Length、Transfer-Encoding、Connection、Upgrade、TE、Trailer、CDN-Loop,以及以 X-Edgeweir- 开头的头。
请求头和响应头的静态值与值表达式都写入配置版本并下发到节点,不要填入 API 密钥或其他秘密。
「源站覆盖」至少修改一项:选择「默认组」以外的源站组,或填写「回源 Host」「SNI」「端口」之一。
覆盖设置
「覆盖设置」的每一项默认「不更改」,数值项留空即不更改。
| 字段 | 取值 | 阶段 | 作用 |
|---|---|---|---|
| 绕过缓存 | 不更改 / 开启 / 关闭 | 配置、缓存 | 本请求绕过或使用缓存 |
| 强制 HTTPS | 不更改 / 开启 / 关闭 | 配置、缓存 | HTTP 请求重定向到 HTTPS |
| Gzip | 不更改 / 开启 / 关闭 | 配置、缓存 | 「关闭」:本次响应不用 gzip;「开启」:重新允许被先前规则关闭的 gzip |
| Brotli、Zstandard | 不更改 / 开启 / 关闭 | 配置 | 同 Gzip |
| WebSocket | 不更改 / 开启 / 关闭 | 配置 | 覆盖网站的「WebSocket」 |
| Under Attack | 不更改 / 开启 / 关闭 | 配置 | 覆盖网站的 Under Attack;全局 Under Attack 不受影响 |
| CC 防护 | 不更改 / 开启 / 关闭 | 配置 | 「关闭」:本请求不受 CC 级别挑战与单 IP 自动封禁,计数照常 |
| CC 最高级别 | 不更改 / Cookie 跳转 / JS 计算 / 工作量证明 / 图片验证码 | 配置 | 本请求的 CC 级别不高于所选级别 |
| 回源连接超时(秒) | 0.1–120 | 配置 | 覆盖源站池的连接超时 |
| 回源发送超时(秒)、回源读取超时(秒) | 0.1–3600 | 配置 | 覆盖源站池的发送、读取超时 |
| 日志采样率(%) | 0–100 | 配置 | 本请求的访问日志采样率 |
| 请求体上限(MiB) | 0–10240,0 不限 | 配置 | 覆盖网站的请求体上限;需要节点能力 site-content-v1 |
后命中的规则逐项覆盖先命中的规则。压缩算法的开关只在网站已开启的算法中生效,规则不能开启网站未开启的算法。
动态目标与查询参数
-
在重定向或改写路径规则的「目标」旁选择「表达式」。
-
按需切换「保留查询参数」,填写「设置查询参数」与「移除查询参数」。
-
点击「保存」。
-
验证:例如重定向规则的表达式为
starts_with(http.request.uri.path, "/old/"),目标为regex_replace(http.request.uri.path, "^/old/", "/new/"),打开「保留查询参数」,「移除查询参数」为utm_source:curl -sI -H 'Host: www.example.com' 'http://<节点 IP>/old/a?utm_source=x&id=1'返回 301,
Location: /new/a?id=1。
值表达式是字符串字面量、字符串字段或返回字符串的函数调用:
concat("https://www.example.com", http.request.uri.path)
regex_replace(http.request.uri.path, "^/old/(.*)$", "/new/${1}")
wildcard_replace(http.request.full_uri, "https://*.example.com/*", "https://example.com/${1}/${2}")| 项目 | 行为 |
|---|---|
| 重定向结果 | 必须是以单个 / 开头的路径,或带主机、不含账号的 http(s) URL;不含空白、控制字符和反斜杠 |
| 改写结果 | 必须以单个 / 开头,不含 ?、#、\ 与控制字符 |
| 结果无效 | 请求返回 503(X-Edgeweir-Error: policy-unavailable) |
| 默认查询串 | 重定向不带原请求的查询串;改写路径保留查询串 |
| 处理顺序 | 重定向打开「保留查询参数」时先把原请求的查询串接到目标后(目标已有查询串时用 & 连接);改写路径关闭「保留查询参数」时先清空查询串。然后从整个查询串中删除「移除查询参数」与「设置查询参数」中的同名参数,再按参数名顺序追加「设置查询参数」 |
| 参数名比较 | 每个参数第一个 = 之前的部分,区分大小写 |
| 参数值 | 字母、数字与 - . _ ~ 以外的字符一律百分号编码 |
| 片段与空查询 | 目标中 # 之后的片段保留在最后;没有参数时不留 ? |
报头值与查询参数值
-
在请求头或响应头规则的「值」旁、或「设置查询参数」某一行的参数名后选择「表达式」。
-
填写值表达式,例如回源阶段的请求头
X-Client-Country取ip.geoip.country,X-Req取http.request.id。编辑框下方显示「第 N 个字符:原因」时按函数与字段修改。 -
响应头需要在已有同名头之外再加一行时打开「追加」。
-
点击「保存」。规则修改热更新,不重载 nginx。
-
验证:例如回源阶段的请求头规则
X-Req=http.request.id,源站收到的X-Req与响应头X-Request-Id相同;响应变换阶段两条追加Link的规则使响应带两行Link:curl -sI -H 'Host: www.example.com' 'http://<节点 IP>/' | grep -i '^link:'
| 项目 | 行为 |
|---|---|
| 可用阶段 | 请求头:请求变换、回源;响应头:响应变换(可读取响应字段);查询参数:重定向、改写路径所在的阶段 |
| 报头值 | 结果最长 4096 字节、不含控制字符(\x00–\x1f、\x7f);结果无效或求值失败(正则超出执行预算、函数结果超过 8192 字节)时跳过这一条报头动作,请求照常继续,不返回 503 |
| 跳过时的日志 | 每条规则在每个节点每 60 秒最多写一条 NOTICE 级 nginx 错误日志 edgeweir: header value skipped site=<网站 ID> rule=<规则 ID>,内容不含报头名与值;nginx 为请求期间的日志附加客户端 IP、请求行和 Host |
| 追加 | 在已有同名头之外再加一行,不合并、不去重;之后的规则读取 http.response.headers["名称"] 得到以 , 连接的全部行;与「移除」互斥 |
| 查询参数值 | 结果照旧百分号编码(字母、数字与 - . _ ~ 以外的字节);求值失败时与动态目标相同,请求返回 503(X-Edgeweir-Error: policy-unavailable) |
| 求值时机 | 请求阶段的值按当时的请求求值(改写之后的路径、查询与 http.request.uri.args);响应头的值在边缘层的响应头过滤阶段求值,缓存命中与回源响应都会重新计算 |
| 节点要求 | rules-v3,见节点能力与发布 |
执行顺序
| 项目 | 行为 |
|---|---|
| 放行与拦截名单 | 最先执行。地址命中拦截名单返回 403;命中放行名单只豁免拦截名单,不跳过规则;同时在两种名单中时放行优先 |
| 作用域 | 每个阶段先执行全局规则,再执行网站规则;同一作用域内按列表顺序 |
| 结束请求 | 拦截、重定向(含批量重定向)和限速超额结束请求 |
| 批量重定向 | 重定向阶段的全局规则与网站规则之后查表 |
| 放行 | 只跳过同一作用域剩余的自定义 WAF 规则,不跳过另一作用域或限速;网站的放行规则不能绕过全局规则中的拦截 |
| 叠加 | 其余动作依次叠加,后执行的覆盖先执行的设置;覆盖设置与源站覆盖逐项覆盖,后命中的压缩规则替换先命中的 |
| 排序 | 拖动只改变同一阶段内的顺序 |
动作行为
| 动作 | 行为 |
|---|---|
| 改写路径 | 改变发往源站的路径与查询串;后续阶段的表达式读取改写后的路径、查询串、查询参数与扩展名(http.request.full_uri 不变);缓存规则、缓存键、刷新与批量重定向仍按改写前的请求 |
| 响应变换 | 同时作用于缓存命中和回源响应 |
| 覆盖设置:Gzip、Brotli、Zstandard | 只影响本次响应,不绕过缓存。网站在边缘压缩(任一算法开启)时,缓存中仍是同一份未压缩对象;网站不在边缘压缩时,「Gzip」关闭移除发往源站的 Accept-Encoding,缓存按源站的 Vary 区分变体,源站不带 Vary: Accept-Encoding 时可能命中此前缓存的压缩对象 |
| 覆盖设置:强制 HTTPS | 网站没有证书时请求返回 503 |
| 覆盖设置:日志采样率 | 按该比例记录匹配的请求;网站的访问日志采样率为「关闭」时这些日志同样保存,见访问日志 |
| 源站覆盖 | 选中组的源站之间照常负载均衡、重试、健康检查与会话保持;「端口」作用于组内每个源站;「回源 Host」不影响 S3 源站;缓存键不含源站组,见源站组 |
| 压缩算法 | 只在列表内、网站已开启且未被覆盖设置关闭的算法中,按请求 Accept-Encoding 的 q 值协商,q 值相同时按列表顺序;「不压缩」时不压缩;不绕过缓存 |
| 拦截、限速超额 | 响应头 X-Edgeweir-Error: policy-denied;限速超额另带 Retry-After(窗口秒数) |
| 挑战 | 请求带有级别足够的通行凭证时继续执行后续规则,否则返回挑战页(非 GET/HEAD 请求返回 403 与 X-Edgeweir-Challenge: required);放行规则跳过 Under Attack 与 CC 挑战,但在放行之前命中的挑战规则仍然生效。见挑战与 CC 防护 |
| 记录 | 不改变响应。节点按规则与分钟统计命中的请求数(不含节点自己的预热请求),网站「安全」页签的「记录命中」卡片按时间范围列出命中最多的规则(全局规则带「(全局)」,已删除的规则显示为「已删除的规则」),数值为近似值:每个节点每分钟最多上报 20 条规则。所在集群有节点不支持(缺少能力 rule-log-v1)时卡片提示「所在集群有节点不上报记录命中」。此外每条规则在每个节点每 60 秒最多写一条 NOTICE 级 nginx 错误日志,内容为网站 ID 和规则 ID;nginx 为请求期间的日志附加客户端 IP、请求行和 Host |
限速
| 项目 | 行为 |
|---|---|
| 范围 | 单节点固定窗口,不是全网共享配额;每个网站独立计数,全局规则中的限速也按网站分别计数 |
| 键 | 作用域、规则 ID 与键值合成 MD5 摘要计数 |
| 内存 | 每个已发布网站固定 256 KiB 共享内存(节点参数 --rate-limit-dict-kb),约容纳 1980 个计数,网站之间不借用;每个集群最多 512 个已发布网站(默认合计 128 MiB);记录去重另用 1 MiB |
| 内存不足 | 分区满时新客户端的请求不计数、直接放行,已有计数的客户端照常限速;节点每个网站每分钟最多写一条 WARN 级 nginx 错误日志,含累计放行次数。节点缺少网站的分区时返回 503(X-Edgeweir-Error: rate-limit-unavailable) |
| 重载与重启 | nginx 重载保留计数;节点重启、或网站被移除后重新加入时清零 |
| 热更新 | 同一批网站的规则和名单修改不重载;新增或移除网站需要重载,既有分区的名称与大小不变 |
表达式
ip.src in $blocked
http.request.uri.path matches "^/(admin|private)/" and not ssl eq true
http.request.method in {"POST" "PUT"}
http.request.headers["x-region"] eq "nz"
ip.geoip.country eq "NZ" and ip.geoip.asnum in {64512 64513}
http.response.code ge 500
lower(http.host) eq "www.example.com"
len(http.request.uri.query) gt 1024
url_decode(http.request.uri.query) contains "<script"
not starts_with(http.request.uri.path, "/api/")
http.request.uri.path.extension in {"jpg" "png" "webp"}
http.request.cookies["role"] eq "admin"
http.user_agent wildcard "*curl*"
http.referer strict wildcard "https://*.example.com/*"
substring(sha256(http.request.uri.path), 0, 8) eq "a1b2c3d4"字段
| 字段 | 类型 | 值 |
|---|---|---|
http.host | 字符串 | 请求的 Host |
http.request.method | 字符串 | 请求方法 |
http.request.uri.path | 字符串 | nginx 规范化后的路径 |
http.request.uri.path.extension | 字符串 | 路径最后一段中最后一个 . 之后的部分,小写;没有时为空字符串 |
http.request.uri.query | 字符串 | 查询字符串,不含 ? |
http.request.uri | 字符串 | 原始请求 URI(路径加查询) |
http.request.full_uri | 字符串 | scheme:// 加 Host(小写、不含端口)再加原始请求 URI;不随改写变化 |
http.request.headers["name"] | 字符串 | 名称不区分大小写;多个值用 , 连接 |
http.request.cookies["名称"] | 字符串 | 请求 Cookie 头中第一个同名 Cookie 的原始值(不解码,引号保留);名称区分大小写,为 1–64 个 token 字符;没有时为空字符串。Cookie 以 ; 分隔,每一对及其名称和值两侧的空格与制表符忽略,没有 = 的一段跳过;多个 Cookie 头以 ; 连接 |
http.request.uri.args["名称"] | 字符串 | 查询串中第一个同名参数的原始值(名称与值都不解码,配合 url_decode);名称为第一个 = 之前的部分,区分大小写,为 1–64 个不含 " # & = 的可打印 ASCII 字符;没有 = 的参数值为空字符串;没有该参数时为空字符串;随改写变化 |
http.referer、http.user_agent | 字符串 | 等同 http.request.headers["referer"]、["user-agent"],随请求头规则变化 |
http.request.version | 字符串 | HTTP/1.0、HTTP/1.1、HTTP/2.0 或 HTTP/3.0 |
http.request.scheme | 字符串 | http 或 https |
http.request.id | 字符串 | 节点的请求 ID,与响应头 X-Request-Id 相同 |
http.request.timestamp.sec | 整数 | 节点收到请求时的 Unix 秒 |
edge.server_port | 整数 | 接收请求的监听端口 |
http.response.code | 整数 | 响应状态码;仅响应变换与压缩阶段 |
http.response.headers["name"] | 字符串 | 响应头;仅响应变换与压缩阶段 |
http.response.content_type.media_type | 字符串 | 响应 Content-Type 去掉参数后的小写媒体类型;仅响应变换与压缩阶段 |
http.response.cache_status | 字符串 | 边缘缓存的状态:HIT、MISS、BYPASS、EXPIRED、STALE、UPDATING 或 REVALIDATED(同 X-Cache);节点自身生成的响应(拦截、重定向、错误页)以及不经过缓存的 WebSocket 与 gRPC 为空字符串;仅响应变换与压缩阶段 |
ip.src | IP | 访客地址,按集群的访客 IP 设置取得:直连时为 TCP 客户端地址;PROXY protocol 时为 PROXY 头中的地址(HTTP/3 为 UDP 对端);可信代理报头时为可信代理在报头中写明的地址 |
ip.peer | IP | 直连对端:TCP 客户端地址(HTTP/3 为 UDP 对端),不受访客 IP 设置影响;直连模式下等于 ip.src。需要 client-ip-v1 |
ssl | 布尔 | HTTPS 请求为 true |
ip.geoip.country | 字符串 | ISO 国家代码;无记录时为空字符串 |
ip.geoip.subdivision | 字符串 | City MMDB 中的一级行政区代码,没有代码时为其英文名称;无记录或 City MMDB 的国家与 ip.geoip.country 不一致时为空字符串 |
ip.geoip.asnum | 整数 | ASN;无记录时为 0 |
ip.geoip.as_name | 字符串 | AS 名称:来自与 ASN 相同的数据库,IPinfo Lite 的 as_name 或 ASN MMDB 的 autonomous_system_organization;无记录时为空字符串 |
tls.ja4 | 字符串 | 连接的 JA4 TLS 客户端指纹;明文 HTTP 为空字符串,见 JA4 |
运算符与字面量
| 语法 | 适用类型 | 说明 |
|---|---|---|
eq、ne | 全部 | IP 类型按地址或 CIDR 包含关系比较 |
lt、le、gt、ge | 整数 | 有序比较 |
contains | 字符串 | 子串匹配 |
matches | 字符串 | 正则匹配 |
wildcard "模式" | 字符串 | 整串通配匹配:* 匹配零个或多个字节(至多 8 个),\*、\\ 表示字面量,其他反斜杠无效;ASCII 字母不区分大小写;模式最长 1024 字节,不含控制字符 |
strict wildcard "模式" | 字符串 | 同 wildcard,区分大小写 |
in {…} | 全部 | 集合,元素以空白分隔 |
in $名称 | IP | 引用 IP 名单;只用于字段,不用于函数结果 |
not、and、or、( ) | — | 优先级 not → and → or |
| 字面量 | — | 字符串用双引号(JSON 转义);整数;true / false;IP 与 CIDR 不加引号;单独的 true 是有效表达式 |
函数(参数, …) | — | 比较的左侧可以是函数,按返回类型适用上述运算符;返回布尔的函数可单独作为条件 |
函数
所有字符串按 UTF-8 字节处理。参数是字段、字符串字面量或另一个函数调用。
| 函数 | 返回 | 说明 |
|---|---|---|
lower(s)、upper(s) | 字符串 | 只转换 ASCII 字母 |
len(s) | 整数 | 字节数 |
starts_with(s, 前缀)、ends_with(s, 后缀) | 布尔 | 按字节比较前缀、后缀;空串总是匹配 |
url_decode(s) | 字符串 | 解码一遍:%XX(十六进制不区分大小写)解为字节,+ 解为空格;不完整或非十六进制的 % 原样保留 |
concat(s1, s2, …) | 字符串 | 2–8 个参数按顺序拼接 |
regex_replace(s, "正则", "替换串") | 字符串 | 替换第一个匹配;正则见正则;没有匹配时返回原串 |
wildcard_replace(s, "通配模式", "替换串"[, "s"]) | 字符串 | 通配模式匹配整个字符串:* 匹配零个或多个字节(至多 8 个),\*、\\ 表示字面量;默认 ASCII 不区分大小写,第四个参数 "s" 区分;前面的 * 取最短匹配;不匹配时返回原串 |
url_encode(s) | 字符串 | RFC 3986 未保留字符(字母、数字与 - . _ ~)以外的字节一律编码为 %XX(大写十六进制) |
base64_encode(s) | 字符串 | 标准字母表,带填充 |
base64_decode(s) | 字符串 | 接受标准与 URL 安全字母表(可混用)、有无填充,忽略末尾多余的位;含其他字符、空白、位置不对或多余的填充、长度除以 4 余 1 时返回空字符串 |
md5(s)、sha1(s)、sha256(s) | 字符串 | 摘要的小写十六进制 |
substring(s, 起点[, 长度]) | 字符串 | 按字节截取:起点从 0 开始,负数从末尾数(超出开头时从第 1 个字节起);省略长度时到末尾;起点不小于长度时为空字符串。起点为 -65536–65536、长度为 0–65536 的整数字面量 |
to_string(x) | 字符串 | 整数写成十进制,布尔为 true / false,IP 为节点看到的地址文本,字符串原样返回;参数可以是任何类型的字段或函数 |
| 项目 | 规则 |
|---|---|
| 使用位置 | 条件(含缓存规则条件)可用 regex_replace、wildcard_replace 以外的函数;值表达式可用全部函数,regex_replace、wildcard_replace 每个表达式各至多一次 |
| 参数类型 | 除 to_string(任何类型)与 substring 的起点、长度(整数字面量)外,参数都是字符串 |
| 字面量参数 | 正则、通配模式、替换串与 "s" 必须是字符串字面量;通配模式与替换串最长 1024 字节,不含控制字符 |
| 替换串 | ${1}–${8} 引用正则的捕获组或通配模式的 *,编号不超过组数或 * 数;未参与匹配的组替换为空串;其他 $ 是字面量;捕获保留原串的大小写 |
| 嵌套 | 至多 4 层 |
| 结果长度 | 函数算出的字符串超过 8192 字节时求值失败 |
| 求值失败 | 正则超出执行预算、结果超长或动态目标无效时,请求返回 503(X-Edgeweir-Error: policy-unavailable) |
IP 语义
| 项目 | 行为 |
|---|---|
| IPv4 映射地址 | ::ffff:a.b.c.d 与对应 IPv4 地址相同;前缀短于 96 位的映射 CIDR 视为歧义被拒绝 |
| IPv4 兼容写法 | ::a.b.c.d 是普通 IPv6 地址(::1.2.3.4 保存为 ::102:304/128),不与 IPv4 等同 |
| NAT64 | 独立的 IPv6 地址,不与 IPv4 等同 |
| CIDR | 保存前清除主机位 |
| 拒绝 | 带前导零的写法(八进制歧义)、zone ID(%) |
正则
matches 与 regex_replace 使用同一个子集。控制台校验、节点校验与节点执行(PCRE)接受同一个子集;各引擎语义不一致的构造一律拒绝。
| 项目 | 规则 |
|---|---|
| 匹配对象 | 值的 UTF-8 字节,区分大小写;.、[^…]、\D、\W 每次匹配一个字节(一个汉字为 3 个字节) |
| 长度与字符 | 最多 256 个可打印 ASCII 字符;其他字符写作 \t \n \r \f 或 \x00–\x7f |
| 锚点 | ^ 值的开头;$ 值的末尾;\b \B ASCII 单词边界 |
| 字符 | . 为除 \n 外的任意字节;\d \D \w \W 按 ASCII;反斜杠加 ASCII 标点表示该标点,字面 ] { } 须转义 |
| 字符类 | […] [^…] 可含字符、\d \D \w \W 与范围 a-z;- 只在首尾为字面量,其余位置写 \-;类内 [ 须转义 |
| 量词 | * + ? {n} {n,} {n,m}(n ≤ m ≤ 1000,无前导零),可加 ? 变为惰性;只跟在字符、字符类或转义之后 |
| 分组 | ( ) 与 |;分组不带量词 |
| 不支持 | \s \S \v(写作 [ \t\r\n\f] 等显式类)、大于 \x7f 的 \xHH、\z \A \Q \p{…} \K 等其他转义、反向引用与八进制、(? 开头的分组、占有与叠加量词、{,n}、[[:alpha:]]、空字符类与 []…] |
| 执行预算 | 节点使用 PCRE,匹配上限 10000、深度上限 100;执行出错时请求返回 503(X-Edgeweir-Error: policy-unavailable) |
已保存的规则或缓存规则条件使用了不再支持的构造(例如 \s)时,保存所属网站或它的规则返回 RULE_INVALID,改写后再保存。其他发布(别的网站、ACME 挑战、系统设置)不受影响:该规则沿用上次编译的结果,并发出告警「已存规则不再通过校验,沿用上次编译结果」;从未编译过的这类规则会让所属网站暂不下发。已停用的网站不编译规则。
支持范围是 wirefilter 风格的子集,不是 wirefilter 的完整实现。
复杂度上限
| 项目 | 上限 |
|---|---|
| 表达式长度 | 4096 字符;缓存规则条件 16384 字符 |
| token 数 | 512 |
| 语法嵌套 | 16 层 |
| 基本条件、函数与参数 | 合计 128 个 |
| 集合元素 | 256 个 |
| 函数嵌套 | 4 层 |
| 函数结果 | 8192 字节 |
| 规则数 | 每个网站 64 条;全局规则 32 条 |
| 批量重定向 | 每个网站 5000 条 |
批量重定向
网站的精确匹配重定向表:每条把一个来源重定向到一个静态目标。按前缀或通配重定向时,使用重定向规则与 wildcard_replace。
-
打开 网站,选择网站,进入「批量重定向」页签。
-
点击「添加重定向」,填写「来源」「目标」,选择「状态码」,按需打开「保留查询参数」。
-
或点击「导入」,在「导入重定向」中每行填写一条(「每行一条:来源 目标 [状态码]」),按需打开「替换现有条目」,点击「导入」。
-
点击「保存」。控制台提示「已保存」,并发布新的配置版本(原因「已更新规则与 IP 名单」)。
-
验证:
curl -sI -H 'Host: www.example.com' http://<节点 IP>/old返回该条目的状态码,
Location为目标。
| 字段 | 取值 | 默认值 |
|---|---|---|
| 来源 | /路径(匹配网站的全部域名)或 域名/路径(只匹配该域名);2–512 字节,不含空白、? 与控制字符;域名小写,须是网站的域名或网站泛域名下一级的子域名 | / |
| 目标 | 同静态重定向目标,最长 1024 字节 | / |
| 状态码 | 301 / 302 / 307 / 308 | 301 |
| 保留查询参数 | 开 / 关;开启时把原请求的查询串接到目标后(目标已有查询串时用 & 连接) | 关 |
| 项目 | 行为 |
|---|---|
| 匹配 | 按客户端原始请求(改写之前)的 Host(小写、不含端口)与规范化后的路径精确匹配,查询串不参与;先查 域名/路径,再查 /路径 |
| 顺序 | 在重定向阶段的全局规则与网站规则之后;命中重定向规则的请求不再查表 |
| 导入 | 每行的字段以空白分隔(行内没有空白时以逗号分隔),状态码默认 301;跳过空行与以 # 开头的行;与已有条目来源相同时替换该条目;打开「替换现有条目」时替换整张表;有无效行时提示「第 N 行无效」,不导入任何一行 |
| 列表 | 每页 50 条;「筛选」按来源或目标查找 |
| 更新 | 节点热更新,不重载 nginx |
| 审计 | 修改写审计 site.bulk_redirects_update(记录条数) |
| 上限 | 每个网站 5000 条,来源不重复 |
| 节点要求 | rules-v2,见节点能力与发布 |
IP 名单
- 打开 IP 名单,点击「创建名单」。
- 填写「名称」:以字母或下划线开头,只含字母、数字和下划线,1–64 字符。
- 选择「动作」:「供规则引用」「拦截」或「放行」。
- 在「IP 地址和 CIDR」中填写条目,以换行、空格或逗号分隔。
- 点击「保存」。
- 验证:列表显示
$名称和「N 条记录」,拦截与放行名单另带「拦截」「放行」标记;在规则或缓存规则条件中用ip.src in $名称引用。
| 动作 | 作用 |
|---|---|
| 供规则引用 | 只作为集合,由规则引用 |
| 拦截 | 拦截名单:作用于所有集群的全部网站,无需规则;地址命中时返回 403,先于所有规则执行 |
| 放行 | 放行名单:作用于所有集群的全部网站;地址命中时豁免拦截名单与封禁,不跳过规则 |
| 项目 | 行为 |
|---|---|
| 名称 | 所有名单共用一个命名空间,名称唯一;创建后不可修改;保存规则时按名称绑定到名单 ID |
| 引用 | 任何网站规则、全局规则或缓存规则条件都可引用任何名单,含拦截与放行名单;L4 应用 的「放行名单」「拦截名单」同样可以选择任何名单 |
| L4 应用 | 「拦截」「放行」动作只作用于网站;L4 应用只按自己选择的名单判断 |
| 修改 | 条目和「动作」可随时修改;创建、修改或删除名单都向所有集群发布新版本(原因「已更新规则与 IP 名单」),节点热更新 |
| 删除 | 被规则、缓存规则条件或 L4 应用引用的名单不能删除(「IP 名单正在被使用:…」,列出前 5 个引用者:规则名,网站规则带网站名;缓存规则所在的网站名;L4 应用名) |
| 条目 | IPv4 / IPv6 地址或 CIDR;清除主机位、去重、排序;拒绝前导零写法和 zone ID |
| 配额 | 最多 128 份名单、合计 50000 条记录;每份最多 10000 条;不增加条目数的修改总能保存 |
| 回滚 | 网站配置回滚沿用当前的名单和全局规则;引用已删除名单时拒绝回滚 |
API:GET、POST /api/v1/ip-lists,PUT、DELETE /api/v1/ip-lists/{id}。
节点能力与发布
| 项目 | 行为 |
|---|---|
| 能力 | 规则和放行/拦截名单需要节点能力 rules-v1;ip.geoip.country、ip.geoip.subdivision 需要 geoip-city-v1;ip.geoip.asnum 需要 geoip-asn-v1;挑战动作需要 challenge-v1;tls.ja4(字段或限速键)需要 ja4-v1;使用 ip.geoip.subdivision 时控制台另外检查 geoip-subdivision-v1(不写入配置) |
| 规则扩展 | 以下任何一项需要 rules-v2:函数与 http.request.full_uri、http.request.uri.path.extension、http.response.content_type.media_type;表达式目标、查询参数编辑,以及重定向打开或改写路径关闭「保留查询参数」;源站覆盖;压缩阶段;只在配置阶段可用的覆盖项与 Gzip「开启」;不是构建器形状的缓存规则条件与「浏览器 TTL(秒)」;批量重定向;默认组以外的源站组 |
| 表达式变量与报头值 | 以下任何一项需要 rules-v3:http.request.cookies[…]、http.request.uri.args[…]、http.referer、http.user_agent、http.request.version、http.request.scheme、http.request.id、http.request.timestamp.sec、edge.server_port、ip.geoip.as_name(另需 geoip-asn-v1)、http.response.cache_status;url_encode、base64_encode、base64_decode、md5、sha1、sha256、substring、to_string;wildcard、strict wildcard;请求头、响应头与查询参数的表达式值;响应头「追加」;重定向 303;错误页的 {{time}}、{{path}} |
| 直连对端 | 读取 ip.peer 的配置需要 client-ip-v1 |
| 已有配置 | 没有用到规则扩展的配置与之前相同,不要求 rules-v2;构建器形状的缓存规则仍以原来的结构化条件下发;没有用到 rules-v3 各项的配置同样逐字节不变 |
| 控制台与 AccessKey | 保存时即使集群内有活动节点缺少所需能力也照常发布;缺少能力的节点保留 last-known-good 配置,集群与节点 中显示「需要升级」,见节点升级 |
| 服务账号与后台任务 | 它们发布的配置引入新能力时,检查集群内所有活动节点(含暂时离线的节点);有节点缺少能力时拒绝(NODE_CAPABILITY_REQUIRED,「节点尚不支持:…(<节点>)」),原配置与版本不变 |
| 界面 | 集群有活动节点缺少 rules-v2 时,「规则」「缓存」「批量重定向」页签显示「所在集群有节点不支持规则扩展,暂时无法使用」,不能再选用规则扩展,已设置的项仍可修改或清除;「批量重定向」只读。缺少 rules-v3 时「规则」页签显示「所在集群有节点不支持,暂时无法开启」:菜单里不列 rules-v3 的字段与条件,「表达式」值、「追加」与 303 不能新选,已设置的仍可修改或清除;「错误页」页签不列 {{time}}、{{path}},模板用到它们时显示同样的提示 |
| 未知能力 | 节点拒绝含未知能力或未知枚举的配置,继续使用 last-known-good |
配置 GeoIP 数据库
GeoIP 字段读取节点本地的 MMDB 文件。节点不自动下载更新,也不向数据商发送访客地址。
| 数据库 | 字段 | 获取方式 | 许可证与更新 | 署名 |
|---|---|---|---|---|
| IPinfo Lite | 国家、ASN | 节点发布镜像内置;安装包和压缩包节点从 IPinfo 下载(免费账号) | CC BY-SA 4.0;IPinfo 每日更新,镜像内为构建当天的快照 | IP address data is powered by IPinfo |
| City MMDB,如 DB-IP Lite City | 国家、一级行政区 | 运营者下载 | DB-IP Lite:CC BY 4.0,按月更新 | IP Geolocation by DB-IP |
| ASN MMDB,如 DB-IP Lite ASN | ASN | 运营者下载 | 同上 | 同上 |
| 项目 | 行为 |
|---|---|
| 优先级 | 国家和 ASN 优先取 IPinfo Lite,无记录时取 City / ASN MMDB |
| 一级行政区 | 只来自 City MMDB;City MMDB 的国家与最终国家一致时才有值 |
| 镜像内置数据 | /usr/share/edgeweir-node/geoip/ipinfo_lite.mmdb,构建时按 IPinfo 公布的 sha256 校验;同目录 NOTICE 记录下载时间和 sha256 |
| 控制台署名 | 防护设置 → GeoIP 数据库 带 IPinfo 署名链接 |
-
容器节点使用发布镜像时,国家和 ASN 无需配置;更新数据时拉取新镜像,或挂载另行下载的副本并设置
EDGEWEIR_GEOIP_IPINFO。 -
安装包或压缩包节点:从 IPinfo 下载
ipinfo_lite.mmdb。需要按一级行政区匹配时,另外下载 City MMDB。核对来源、许可和完整性,记录下载日期。 -
把文件放在节点可读的只读目录,为节点设置环境变量:
/etc/default/edgeweir-node EDGEWEIR_GEOIP_IPINFO=/etc/edgeweir-node/geoip/ipinfo_lite.mmdb EDGEWEIR_GEOIP_CITY=/etc/edgeweir-node/geoip/dbip-city-lite.mmdb容器部署挂载该目录并以
-e传入同样的变量。 -
重启节点,装入数据库:
sudo systemctl restart edgeweir-node -
验证:防护设置 → GeoIP 数据库 中该节点显示「国家:可用」「ASN:可用」;配置了 City MMDB 时另显示「省份:可用」。
| 变量 | 参数 | 默认值 | 说明 |
|---|---|---|---|
EDGEWEIR_GEOIP_IPINFO | --geoip-ipinfo | auto | IPinfo Lite MMDB 路径;auto 使用镜像内置的数据库(存在时),off 关闭 |
EDGEWEIR_GEOIP_CITY | --geoip-city | 空 | City MMDB 路径 |
EDGEWEIR_GEOIP_ASN | --geoip-asn | 空 | ASN MMDB 路径 |
| 项目 | 行为 |
|---|---|
| 能力上报 | 有国家数据(IPinfo Lite 或 City MMDB)时上报 geoip-city-v1,有 City MMDB 时上报 geoip-subdivision-v1,有 ASN 数据时上报 geoip-asn-v1;当前版本节点另报 geoip-country-v1 |
| 旧版节点 | 未上报 geoip-country-v1 的节点只在有 City MMDB 时上报 geoip-city-v1,控制台视其具备 geoip-subdivision-v1 |
| 缺少能力 | 节点拒绝使用其缺少的 GeoIP 字段的配置,保留 last-known-good |
| 文件无效 | 指定的 MMDB 文件无效或数据库类型不符时节点 agent 不能启动;镜像内置的 IPinfo Lite 无效时 agent 记录错误并不使用它 |
| 查询 | agent 读取文件,经权限 0600 的本机 Unix socket 向 Lua worker 提供结果;每个 worker 缓存最多 10000 个结果、有效期 5 分钟;单次查询超时 200 毫秒 |
| 查询失败 | 网站规则、全局规则(含值表达式)或缓存规则条件使用 GeoIP 字段时,该网站的每个请求都要查询;服务不可用时这些请求返回 503 |
| 更新 | 先在一个节点替换文件或镜像并重启、验证,再更新其余节点;不要原地改写正在使用的 MMDB |
限制
| 项目 | 说明 |
|---|---|
| 限速 | 只有单节点固定窗口;没有全网共享配额和滑动窗口 |
| 表达式 | wirefilter 风格子集;只有内置函数,不支持自定义函数和原始 Lua |
| 字符串替换 | regex_replace、wildcard_replace 只用于值表达式(重定向目标、改写路径、报头值与查询参数值),每个表达式各一次;regex_replace 只替换第一个匹配 |
| Cookie 与查询参数 | 按名称取第一个值,不解码;不能列举全部 Cookie 或参数,名称不能用通配 |
| 批量重定向 | 只做精确匹配,目标为静态值 |
| 受保护头 | 见动作字段;不能通过规则修改 |
| 压缩 | 覆盖设置与压缩规则只能在网站已开启的算法中选择 |
| 源站组 | 缓存键不含源站组 |
| GeoIP 数据 | 镜像内置的 IPinfo Lite 是构建当天的快照;一级行政区需运营者提供 City MMDB;精度取决于所选数据库 |
故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 编辑框下方显示「第 N 个字符:原因」 | 该位置语法、字段、类型、函数或正则不受支持,原因说明具体问题 | 按上方语法表修改 |
| 保存时提示「检查规则「名称」的字段」 | 该规则的这个字段无效,例如目标格式、参数名重复或同时设置与移除同一参数;表达式无效时提示位置与原因 | 按动作字段表修改 |
| 保存提示「规则无效」 | 动作不属于该阶段、头名称受保护、重定向目标或改写路径格式无效;源站覆盖选择了网站没有的源站组 | 按动作字段表修改;先在「源站」页签添加该组的源站 |
| 「网站没有这些域名:…」 | 批量重定向来源中的域名不属于该网站 | 改用网站的域名,或写成 /路径 |
| 「第 N 行无效」 | 导入的该行字段数、来源、目标或状态码无效 | 修正该行后重新导入 |
| 「N 条无效」 | 批量重定向表中有无效或来源重复的条目 | 修正标出的条目 |
| 「所在集群有节点不支持规则扩展,暂时无法使用」 | 集群内有活动节点缺少 rules-v2 | 升级节点,见节点升级 |
| 「IP 名单不存在:…」 | 表达式引用的名单(列出的名称)不存在 | 先在 IP 名单 创建该名单,或修正名称 |
| 「IP 名单名称已存在」 | 已有同名名单 | 使用其他名称 |
| 「IP 名单正在被使用:…」 | 删除仍被列出的规则、网站的缓存规则条件或 L4 应用引用的名单 | 先从这些规则与 L4 应用中移除引用 |
| 「IP 名单已达上限(128 份名单、50,000 条记录)」 | 超过配额 | 合并或删除名单 |
| 节点显示「需要升级」 | 节点缺少配置所需能力(rules-v1、rules-v2、GeoIP 能力等),保留 last-known-good 配置 | 升级节点或配置 GeoIP 数据库 |
| 「节点尚不支持:…(<节点>)」 | 服务账号或后台任务发布的配置用到 rules-v1、rules-v2 或 GeoIP 能力,而集群内有活动节点缺少这些能力 | 升级节点或配置 GeoIP 数据库 |
503,X-Edgeweir-Error: policy-unavailable | 正则执行超出预算、函数结果超过 8192 字节、动态目标、改写路径或查询参数值的表达式无效,或 GeoIP 查询失败 | 简化正则或表达式;检查值表达式的结果;检查节点 GeoIP 服务 |
表达式值的请求头或响应头没有出现,节点日志有 header value skipped site=… rule=… | 该规则算出的值超过 4096 字节、含控制字符,或求值失败;节点跳过这一条报头动作 | 用 substring、url_encode 等限制或清理值;同一条规则 60 秒内只记一条日志 |
| 「所在集群有节点不支持,暂时无法开启」(规则页签) | 集群内有活动节点缺少 rules-v3 | 升级节点,见节点升级 |
| 开启强制 HTTPS 的规则使请求返回 503 | 网站没有证书 | 在「HTTPS」页签选择证书 |
| 限速在多个节点间未合并计数 | 限速按单节点计数 | 按节点数折算阈值 |
部分访客未被限速,节点日志有 rate limit partition full | 网站的限速分区已满,新客户端不计数 | 调大节点参数 --rate-limit-dict-kb |