mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 20:08:05 +08:00
🌴 Sync 2026-10-28 19:42:05
This commit is contained in:
1 parent
2d9a92ead9
commit
c3e527335a
22 files changed
+7105
No files matched your search
@@ -0,0 +1,42 @@
|
||||
# 找出不合法的 CIDR 行(校验规则与 cidr2range.awk 保持一致)
|
||||
# 输出格式:<行号>: <原始内容>
|
||||
{
|
||||
line = $0
|
||||
sub(/#.*/, "", line)
|
||||
gsub(/[ \t\r]/, "", line)
|
||||
if (line == "")
|
||||
next
|
||||
|
||||
n = split(line, p, "/")
|
||||
if (n != 2) {
|
||||
print NR ": " $0
|
||||
next
|
||||
}
|
||||
|
||||
alen = split(p[1], q, ".")
|
||||
if (alen != 4) {
|
||||
print NR ": " $0
|
||||
next
|
||||
}
|
||||
|
||||
# 与 cidr2range.awk 保持一致:前缀必须是纯数字,不能靠 +0 转换
|
||||
if (p[2] !~ /^[0-9]+$/) {
|
||||
print NR ": " $0
|
||||
next
|
||||
}
|
||||
len = p[2] + 0
|
||||
if (len < 0 || len > 32) {
|
||||
print NR ": " $0
|
||||
next
|
||||
}
|
||||
|
||||
bad = 0
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if (q[i] !~ /^[0-9]+$/)
|
||||
bad = 1
|
||||
else if (q[i] + 0 > 255)
|
||||
bad = 1
|
||||
}
|
||||
if (bad)
|
||||
print NR ": " $0
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
# 数据管线:原始 CIDR -> 归一化 -> 按优先级互斥化 -> 每运营商一个 CIDR 文件
|
||||
#
|
||||
# 用法: build.sh <rawdir> <outdir> <优先级列表>
|
||||
# rawdir : 含 <isp>.raw 的目录,每行一个 CIDR
|
||||
# 优先级 : 空格分隔的运营商名,排在前面的优先占有重叠地址
|
||||
#
|
||||
# 设计要点:
|
||||
# 1. 每个集合内部先合并去重(RFC1918 无关,只做区间合并)
|
||||
# 2. 逐级做差集:后一个集合要扣掉所有前面已占用的地址
|
||||
# -> 保证同一 IP 只出现在一个集合里,规则顺序不再影响归属
|
||||
# 3. 全程只依赖 awk + sort,无 Python、无 ipcalc
|
||||
set -eu
|
||||
|
||||
RAW=$1
|
||||
OUT=$2
|
||||
PRIORITY=$3
|
||||
|
||||
LIB=${ISP_SPLIT_LIB:-$(dirname "$0")}
|
||||
WORK=$OUT/.work
|
||||
|
||||
mkdir -p "$OUT" "$WORK"
|
||||
: > "$WORK/claimed"
|
||||
|
||||
printf '%-16s %8s %8s %14s\n' "运营商" "原始" "互斥后" "地址数" > "$WORK/report"
|
||||
|
||||
for isp in $PRIORITY; do
|
||||
src=$RAW/$isp.raw
|
||||
[ -f "$src" ] || {
|
||||
printf '%-16s %8s\n' "$isp" "缺失" >> "$WORK/report"
|
||||
continue
|
||||
}
|
||||
|
||||
# 1. CIDR -> 区间,排序,合并
|
||||
awk -f "$LIB/cidr2range.awk" "$src" |
|
||||
sort |
|
||||
awk -f "$LIB/merge.awk" > "$WORK/$isp.ranges"
|
||||
|
||||
# 2. 扣掉高优先级运营商已占用的地址
|
||||
awk -v claimed="$WORK/claimed" -f "$LIB/exclude.awk" "$WORK/$isp.ranges" > "$WORK/$isp.excl"
|
||||
|
||||
# 3. 区间 -> 最小 CIDR 集合
|
||||
awk -f "$LIB/range2cidr.awk" "$WORK/$isp.excl" > "$OUT/$isp.txt"
|
||||
|
||||
# 4. 已占用集合累加(需重新合并以保持有序且互不重叠)
|
||||
cat "$WORK/claimed" "$WORK/$isp.excl" |
|
||||
sort |
|
||||
awk -f "$LIB/merge.awk" > "$WORK/claimed.new"
|
||||
mv "$WORK/claimed.new" "$WORK/claimed"
|
||||
|
||||
# 统计
|
||||
n_raw=$(awk -f "$LIB/cidr2range.awk" "$src" | wc -l | tr -d ' ')
|
||||
n_out=$(wc -l < "$OUT/$isp.txt" | tr -d ' ')
|
||||
n_addr=$(awk '{ split($0, p, "/"); t += 2 ^ (32 - (p[2] + 0)) } END { printf "%.0f", t }' "$OUT/$isp.txt")
|
||||
printf '%-16s %8s %8s %14s\n' "$isp" "$n_raw" "$n_out" "$n_addr" >> "$WORK/report"
|
||||
done
|
||||
|
||||
cat "$WORK/report" >&2
|
||||
@@ -0,0 +1,59 @@
|
||||
# CIDR -> 数值区间
|
||||
# 输入:每行一个 CIDR(允许 # 注释、行尾空白)
|
||||
# 输出:每行 "<起始> <结束>",均为零填充的 10 位定宽数字
|
||||
#
|
||||
# 为什么零填充:
|
||||
# OpenWrt 的 busybox sort 对大数执行 sort -n 时并不按数值排序
|
||||
# (实测 3232235520 会排在 134744064 之前,等于没排序)。
|
||||
# 把数值零填充到 10 位后,普通字典序 == 数值序,任何平台都成立。
|
||||
#
|
||||
# 纯算术实现,不使用位运算,兼容 busybox awk / mawk / gawk
|
||||
|
||||
function pad(n, s) {
|
||||
s = sprintf("%.0f", n)
|
||||
while (length(s) < 10)
|
||||
s = "0" s
|
||||
return s
|
||||
}
|
||||
|
||||
{
|
||||
line = $0
|
||||
sub(/#.*/, "", line)
|
||||
gsub(/[ \t\r]/, "", line)
|
||||
if (line == "")
|
||||
next
|
||||
|
||||
n = split(line, p, "/")
|
||||
if (n != 2)
|
||||
next
|
||||
|
||||
alen = split(p[1], q, ".")
|
||||
if (alen != 4)
|
||||
next
|
||||
|
||||
# 前缀必须是纯数字;否则 "abc"/"" 会被 +0 当成 0,单行就吞掉整个 IPv4
|
||||
if (p[2] !~ /^[0-9]+$/)
|
||||
next
|
||||
len = p[2] + 0
|
||||
if (len < 0 || len > 32)
|
||||
next
|
||||
|
||||
bad = 0
|
||||
for (i = 1; i <= 4; i++) {
|
||||
if (q[i] !~ /^[0-9]+$/)
|
||||
bad = 1
|
||||
else if (q[i] + 0 > 255)
|
||||
bad = 1
|
||||
}
|
||||
if (bad)
|
||||
next
|
||||
|
||||
ip = q[1] * 16777216 + q[2] * 65536 + q[3] * 256 + q[4]
|
||||
if (ip < 0 || ip > 4294967295)
|
||||
next
|
||||
|
||||
blk = 2 ^ (32 - len)
|
||||
s = int(ip / blk) * blk
|
||||
e = s + blk - 1
|
||||
print pad(s), pad(e)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# 区间差集:target 减去 claimed
|
||||
# 用法:awk -v claimed=<已占用区间文件> -f exclude.awk < target
|
||||
# claimed:已按字典序排好的互不重叠区间 "<起始> <结束>"(零填充定宽)
|
||||
# target :同样已排序去重叠的区间(stdin)
|
||||
# 输出:target 中未被 claimed 覆盖的部分(零填充、升序、互不重叠)
|
||||
# 目的:让同一个 IP 只归属一个运营商集合,消除归属歧义
|
||||
|
||||
function pad(n, s) {
|
||||
s = sprintf("%.0f", n)
|
||||
while (length(s) < 10)
|
||||
s = "0" s
|
||||
return s
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
if (claimed == "") {
|
||||
print "exclude.awk: 缺少参数 -v claimed=<已占用区间文件>" > "/dev/stderr"
|
||||
exit 2
|
||||
}
|
||||
nc = 0
|
||||
while ((getline l < claimed) > 0) {
|
||||
n = split(l, f, " ")
|
||||
if (n < 2)
|
||||
continue
|
||||
nc++
|
||||
cs[nc] = f[1] + 0
|
||||
ce[nc] = f[2] + 0
|
||||
}
|
||||
close(claimed)
|
||||
ci = 1
|
||||
}
|
||||
{
|
||||
s = $1 + 0
|
||||
e = $2 + 0
|
||||
if (s > e)
|
||||
next
|
||||
|
||||
# 跳过完全位于 s 之前的已占用区间
|
||||
while (ci <= nc && ce[ci] < s)
|
||||
ci++
|
||||
|
||||
cur = ci
|
||||
while (cur <= nc && cs[cur] <= e) {
|
||||
if (cs[cur] > s)
|
||||
print pad(s), pad(cs[cur] - 1)
|
||||
if (ce[cur] + 1 > s)
|
||||
s = ce[cur] + 1
|
||||
if (s > e)
|
||||
break
|
||||
cur++
|
||||
}
|
||||
|
||||
if (s <= e)
|
||||
print pad(s), pad(e)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
# 两个运营商集合的三段差异:仅主源 / 仅校验源 / 共同,并计算 Jaccard
|
||||
#
|
||||
# 用法:
|
||||
# awk -v vf=<校验源区间文件> [-v outp=<仅主源区间输出>] [-v outv=<仅校验源区间输出>] \
|
||||
# -f ispdiff.awk <主源区间文件>
|
||||
#
|
||||
# 输入格式:两边的区间文件都必须是「每行 <起始> <结束>,升序、互不重叠」,
|
||||
# 也就是 cidr2range.awk | sort | merge.awk 的输出(零填充十位定宽)。
|
||||
# 输出(stdout 一行六列):
|
||||
# <主源地址数> <校验源地址数> <共同> <仅主> <仅校验> <Jaccard 百分比>
|
||||
#
|
||||
# 为什么用扫描线而不是逐条 CIDR 比对:
|
||||
# 运营商集合动辄上千段,"每段对每段"是 O(n*m);排序后双指针是 O(n+m),
|
||||
# 在 4 千行量级一次 diff 只要几十毫秒,路由器上完全无感。
|
||||
#
|
||||
# 注意:awk 函数里除第一个参数外多写的形参才是局部变量,这里用显式形参
|
||||
# (i/j/k/s/e 都是全局的,只用一次,不影响正确性)。
|
||||
|
||||
BEGIN {
|
||||
nv = 0
|
||||
while ((getline line < vf) > 0) {
|
||||
if (line == "") continue
|
||||
split(line, a, " ")
|
||||
if (a[1] == "" || a[2] == "") continue
|
||||
nv++
|
||||
vs[nv] = a[1] + 0
|
||||
ve[nv] = a[2] + 0
|
||||
}
|
||||
close(vf)
|
||||
np = 0
|
||||
}
|
||||
|
||||
{
|
||||
if ($1 == "" || $2 == "") next
|
||||
np++
|
||||
ps[np] = $1 + 0
|
||||
pe[np] = $2 + 0
|
||||
}
|
||||
|
||||
END {
|
||||
ptot = 0
|
||||
for (i = 1; i <= np; i++) ptot += pe[i] - ps[i] + 1
|
||||
vtot = 0
|
||||
for (i = 1; i <= nv; i++) vtot += ve[i] - vs[i] + 1
|
||||
|
||||
# 交集长度(双指针)
|
||||
common = 0
|
||||
i = 1
|
||||
j = 1
|
||||
while (i <= np && j <= nv) {
|
||||
s = (ps[i] > vs[j]) ? ps[i] : vs[j]
|
||||
e = (pe[i] < ve[j]) ? pe[i] : ve[j]
|
||||
if (s <= e) common += e - s + 1
|
||||
if (pe[i] < ve[j]) i++
|
||||
else if (ve[j] < pe[i]) j++
|
||||
else { i++; j++ }
|
||||
}
|
||||
|
||||
# 仅主源区间(主源减去校验源)
|
||||
if (outp != "") {
|
||||
j = 1
|
||||
for (i = 1; i <= np; i++) {
|
||||
s = ps[i]
|
||||
e = pe[i]
|
||||
while (j <= nv && ve[j] < s) j++
|
||||
k = j
|
||||
while (k <= nv && vs[k] <= e) {
|
||||
if (vs[k] > s) print s " " (vs[k] - 1) > outp
|
||||
if (ve[k] + 1 > s) s = ve[k] + 1
|
||||
if (s > e) break
|
||||
k++
|
||||
}
|
||||
if (s <= e) print s " " e > outp
|
||||
}
|
||||
close(outp)
|
||||
}
|
||||
|
||||
# 仅校验源区间(校验源减去主源)
|
||||
if (outv != "") {
|
||||
i = 1
|
||||
for (j = 1; j <= nv; j++) {
|
||||
s = vs[j]
|
||||
e = ve[j]
|
||||
while (i <= np && pe[i] < s) i++
|
||||
k = i
|
||||
while (k <= np && ps[k] <= e) {
|
||||
if (ps[k] > s) print s " " (ps[k] - 1) > outv
|
||||
if (pe[k] + 1 > s) s = pe[k] + 1
|
||||
if (s > e) break
|
||||
k++
|
||||
}
|
||||
if (s <= e) print s " " e > outv
|
||||
}
|
||||
close(outv)
|
||||
}
|
||||
|
||||
if (ptot + vtot - common > 0)
|
||||
jac = common * 100 / (ptot + vtot - common)
|
||||
else
|
||||
jac = 100
|
||||
printf "%.0f %.0f %.0f %.0f %.0f %.2f\n", ptot, vtot, common, ptot - common, vtot - common, jac
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# 合并重叠/相邻区间
|
||||
# 输入:已按字典序排好的 "<起始> <结束>"(零填充定宽,字典序 == 数值序)
|
||||
# 输出:互不重叠、已合并的区间,同样零填充
|
||||
#
|
||||
# 注意:不能依赖 sort -n —— busybox 的 sort -n 对大数不生效。
|
||||
# 输入已经是零填充格式,普通 sort 即可得到正确数值序。
|
||||
|
||||
function pad(n, s) {
|
||||
s = sprintf("%.0f", n)
|
||||
while (length(s) < 10)
|
||||
s = "0" s
|
||||
return s
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
have = 0
|
||||
}
|
||||
{
|
||||
s = $1 + 0
|
||||
e = $2 + 0
|
||||
if (s > e)
|
||||
next
|
||||
|
||||
if (!have) {
|
||||
cs = s
|
||||
ce = e
|
||||
have = 1
|
||||
next
|
||||
}
|
||||
|
||||
# 与当前合并段重叠或正好相邻
|
||||
if (s <= ce + 1) {
|
||||
if (e > ce)
|
||||
ce = e
|
||||
next
|
||||
}
|
||||
|
||||
print pad(cs), pad(ce)
|
||||
cs = s
|
||||
ce = e
|
||||
}
|
||||
END {
|
||||
if (have)
|
||||
print pad(cs), pad(ce)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# 数值区间 -> 最小 CIDR 集合
|
||||
# 输入:每行 "<起始> <结束>"(升序、互不重叠)
|
||||
# 输出:每行一个 CIDR
|
||||
#
|
||||
# 注意:awk 函数里除第一个参数外,多写的形参才是局部变量。
|
||||
# int2ip(n, a,b,c,d) 中的 a,b,c,d 若不加在形参列表里就会变成全局变量,
|
||||
# 直接污染外层的块大小 b,导致死循环。这是本项目踩过的一个坑。
|
||||
function int2ip(n, a, b, c, d) {
|
||||
a = int(n / 16777216) % 256
|
||||
b = int(n / 65536) % 256
|
||||
c = int(n / 256) % 256
|
||||
d = n % 256
|
||||
return a "." b "." c "." d
|
||||
}
|
||||
|
||||
function log2(x, r) {
|
||||
r = 0
|
||||
while (x > 1) {
|
||||
x = x / 2
|
||||
r++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
{
|
||||
s = $1 + 0
|
||||
e = $2 + 0
|
||||
if (s > e)
|
||||
next
|
||||
|
||||
while (s <= e) {
|
||||
# 找出以 s 为起点、长度不超过 e 的最大 2 的幂块
|
||||
blk = 1
|
||||
while (s % (blk * 2) == 0 && s + blk * 2 - 1 <= e)
|
||||
blk = blk * 2
|
||||
if (blk < 1)
|
||||
blk = 1
|
||||
|
||||
printf "%s/%d\n", int2ip(s), 32 - log2(blk)
|
||||
s = s + blk
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# 把运营商分流规则块插入 /etc/config/mwan3 的正确位置
|
||||
#
|
||||
# 为什么不直接 uci add:
|
||||
# mwan3 按配置文件中的顺序渲染 nft 规则链(first-match),而 uci add 只能追加到末尾。
|
||||
# 追加的规则必然排在 default_rule 之后,永远匹配不到 —— 等于白装。
|
||||
#
|
||||
# 用法:
|
||||
# awk -v insert=<规则片段文件> -v anchor=<锚点正则> -f rules-splice.awk /etc/config/mwan3
|
||||
#
|
||||
# 行为:
|
||||
# 1. 只删掉带精确标记 option isp_split '1' 的本工具规则块
|
||||
# (不再按 isp_ 前缀或注释里的 isp_split 字符串判定,避免误删用户规则)
|
||||
# 2. 在第一个匹配 anchor 的 rule 块之前插入 insert 文件内容
|
||||
# 3. 找不到锚点且 insert 非空时,输出警告到 stderr 并以 3 退出,
|
||||
# 绝不静默追加到链尾(追加在 catch-all 之后永远不命中)
|
||||
#
|
||||
# 注意:awk 函数除首参外多写的形参才是局部变量,务必写全,否则污染全局。
|
||||
|
||||
function blocktext(i, e, j, t) {
|
||||
e = (i < nb) ? bs[i + 1] - 1 : NR
|
||||
t = ""
|
||||
# 只拼接块内的 option/list 行。块间注释、空行不能算进前一个块,
|
||||
# 否则一段含 isp_split 字样的注释就会把上面的用户规则误判成我们的。
|
||||
for (j = bs[i]; j <= e; j++)
|
||||
if (line[j] ~ /^[[:space:]]*(option|list)[[:space:]]/)
|
||||
t = t " " line[j]
|
||||
return t
|
||||
}
|
||||
|
||||
function is_ours(i, t) {
|
||||
if (bt[i] != "rule")
|
||||
return 0
|
||||
t = blocktext(i)
|
||||
if (t ~ /option[[:space:]]+isp_split[[:space:]]+['"]1['"]/)
|
||||
return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function warn_no_anchor() {
|
||||
print "rules-splice.awk: 找不到锚点 '" anchor "',拒绝把规则追加到链尾(inserted=0)" > "/dev/stderr"
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
if (insert == "") {
|
||||
print "rules-splice.awk: 缺少参数 -v insert=<规则片段文件>" > "/dev/stderr"
|
||||
exit 2
|
||||
}
|
||||
# 先读入待插入片段,顺便判断是否为空(卸载时 insert 为空是合法的)
|
||||
ni = 0
|
||||
while ((getline l < insert) > 0)
|
||||
ins[++ni] = l
|
||||
close(insert)
|
||||
}
|
||||
|
||||
{
|
||||
line[NR] = $0
|
||||
if ($1 == "config") {
|
||||
nb++
|
||||
bs[nb] = NR
|
||||
bt[nb] = $2
|
||||
bn[nb] = $3
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
for (i = 1; i <= nb; i++) {
|
||||
gsub(/['\''"]/, "", bt[i])
|
||||
gsub(/['\''"]/, "", bn[i])
|
||||
}
|
||||
|
||||
# 找锚点:第一个不是我们的、且匹配 anchor 的 rule 块
|
||||
aidx = 0
|
||||
for (i = 1; i <= nb && aidx == 0; i++) {
|
||||
if (bt[i] != "rule")
|
||||
continue
|
||||
if (is_ours(i))
|
||||
continue
|
||||
# blocktext 只含 option/list 行;section 名(如 default_rule_v4)
|
||||
# 也要参与锚点匹配,否则默认锚点里的 default_rule 会失效。
|
||||
if (blocktext(i) ~ anchor || bn[i] ~ anchor)
|
||||
aidx = i
|
||||
}
|
||||
|
||||
if (nb == 0) {
|
||||
if (ni > 0) {
|
||||
warn_no_anchor()
|
||||
exit 3
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 文件头(第一个 config 之前的内容)
|
||||
for (j = 1; j < bs[1]; j++)
|
||||
print line[j]
|
||||
|
||||
inserted = 0
|
||||
for (i = 1; i <= nb; i++) {
|
||||
if (is_ours(i))
|
||||
continue
|
||||
if (!inserted && i == aidx) {
|
||||
for (k = 1; k <= ni; k++)
|
||||
print ins[k]
|
||||
inserted = 1
|
||||
}
|
||||
e = (i < nb) ? bs[i + 1] - 1 : NR
|
||||
for (j = bs[i]; j <= e; j++)
|
||||
print line[j]
|
||||
}
|
||||
|
||||
# 没有锚点:insert 为空(卸载)时原样输出;非空时拒绝追加
|
||||
if (!inserted && ni > 0) {
|
||||
warn_no_anchor()
|
||||
exit 3
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user