update 2026-10-07 18:47:42

This commit is contained in:
action committed 2026-10-07 18:47:42 +08:00
1 parent 617f7ca2a6
commit bd061545ed
4832 files changed
+1693456

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
// Use the real LuCI CBI dependency evaluator, including checkbox empty values.
const fs = require('fs');
const vm = require('vm');
const assert = require('assert');
const path = require('path');
const cbi = fs.readFileSync(process.argv[2], 'utf8');
const client = fs.readFileSync(path.join(__dirname, '../luasrc/model/cbi/shadowsocksr/client.lua'), 'utf8');
const filter = client.slice(client.indexOf('o = s:option(Flag, "filter_aaaa"'), client.indexOf('if is_finded("chinadns-ng") then', client.indexOf('o = s:option(Flag, "filter_aaaa"')));
const deps = [...filter.matchAll(/o:depends\(\{pdnsd_enable = "(\d+)", ipv6_support = (false|"0")\}\)/g)]
.map(m => ({ pdnsd_enable: m[1], ipv6_support: m[2] === 'false' ? false : '0' }));
assert.strictEqual(deps.length, 3);
const fields = { pdnsd_enable: { type: 'select', value: '1' }, ipv6_support: { type: 'checkbox', value: '1', checked: false } };
const ctx = { document: { querySelectorAll: query => [fields[query.match(/id="([^"]+)"/)[1]]] } };
vm.createContext(ctx);
vm.runInContext(cbi.slice(cbi.indexOf('function cbi_d_checkvalue('), cbi.indexOf('function cbi_d_update(')), ctx);
for (const mode of ['0', '1', '4', '6', '7']) {
fields.pdnsd_enable.value = mode;
for (const checked of [false, true, false]) {
fields.ipv6_support.checked = checked;
assert.strictEqual(ctx.cbi_d_check(deps), !checked && ['1', '4', '7'].includes(mode));
}
}
assert.match(filter, /function o\.remove\(self, section\)\s*end/);
console.log('PASS: actual CBI checkbox dependencies toggle immediately and preserve the saved filter');
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Exercise DNS generation with BusyBox awk, both backends and all run modes."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
source = Path(__file__).resolve().parents[1] / 'root/usr/share/shadowsocksr/gfw2ipset.sh'
with tempfile.TemporaryDirectory(prefix='ssr6-dns-') as tmp:
root = Path(tmp)
config = root / 'config'
output = root / 'output'
init = root / 'etc/init.d'
for directory in (config, output, init, root / 'bin'):
directory.mkdir(parents=True)
for name in ('gfw_base.conf', 'gfw_list.conf'):
(config / name).write_text(''.join(
f'server=/{domain}/127.0.0.1#5335\nipset=/{domain}/gfwlist\n'
for domain in ('proxy.example', 'white.example', 'black.example', 'deny.example')))
for name, domain in [('white', 'white'), ('black', 'black'), ('deny', 'deny')]:
(config / f'{name}.list').write_text(f'# comment\n\n{domain}.example\n')
(init / 'shadowsocksr').write_text('''
check_run_environment() { USE_TABLES="$BACKEND"; }
echolog() { :; }
normalize_run_mode() { echo "$MODE"; }
ipv6_enabled() { [ "$V6" = 1 ] && [ "$USE_TABLES" = nftables ]; }
uci_get_by_type() { echo 0; }
TMP_DNSMASQ_PATH="$TEST_OUTPUT"
dns_port=5335
''')
script = root / 'generate.sh'
script.write_text(source.read_text().replace('/etc/ssrplus/', str(config) + '/'))
busybox = shutil.which('busybox')
if not busybox:
raise SystemExit('BusyBox is required to test the OpenWrt awk dialect')
(root / 'bin/awk').symlink_to(busybox)
count = 0
for backend in ('nftables', 'iptables'):
for mode in ('gfw', 'router', 'all'):
for ipv6 in ('0', '1'):
env = dict(os.environ, BACKEND=backend, MODE=mode, V6=ipv6,
IPKG_INSTROOT=tmp, TEST_OUTPUT=str(output),
PATH=str(root / 'bin') + ':' + os.environ['PATH'])
subprocess.run([busybox, 'sh', str(script)], env=env, check=True)
gfw = (output / 'gfw_list.conf').read_text()
white = (output / 'whitelist_forward.conf').read_text()
black = (output / 'blacklist_forward.conf').read_text()
assert 'proxy.example' in gfw
for domain in ('white.example', 'black.example', 'deny.example'):
assert domain not in gfw, (backend, mode, ipv6, domain)
assert 'server=' not in white
assert 'server=/black.example/127.0.0.1#5335' in black
if backend == 'nftables':
assert '4#inet#ss_spec#whitelist_domain' in white
assert ('6#inet#ss_spec#whitelist_domain6' in white) == (ipv6 == '1')
assert ('6#inet#ss_spec#blacklist6' in black) == (ipv6 == '1')
if mode == 'gfw':
assert '4#inet#ss_spec#gfwlist' in gfw
assert ('6#inet#ss_spec#gfwlist6' in gfw) == (ipv6 == '1')
else:
assert 'nftset=' not in gfw and 'ipset=' not in gfw
else:
assert 'nftset=' not in gfw + white + black
assert ('ipset=/proxy.example/gfwlist' in gfw) == (mode == 'gfw')
count += 1
print(f'PASS: {count} DNS generation combinations, domain overrides and AAAA sets')
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Run as root on OpenWrt with the package installed. Rules stay in a new netns.
set -eu
ns="ssr6-check-$$"
tmp=$(mktemp -d)
cleanup() {
ip netns del "$ns" 2>/dev/null || true
rm -rf "$tmp"
}
trap cleanup EXIT INT TERM
ip netns add "$ns"
. /usr/share/shadowsocksr/ipv6.sh
uci_get_by_type() { :; }
uci_get_by_name() { :; }
FWMARK=0x53535250
local_port=1234
LOCAL_PORT=301
Interface=""
LAN_AC_IP="w192.0.2.1 2001:db8::1"
WAN_BP_IP="192.0.2.2 2001:db8:1::/64"
WAN_FW_IP="192.0.2.3 2001:db8:2::/64"
LAN_BP_IP="2001:db8::2"
LAN_FP_IP="2001:db8::3"
LAN_GM_IP="2001:db8::4"
count=0
for RUNMODE in gfw router all; do
for OUTPUT in 0 1 2; do
for udp in tproxy quic disabled; do
TPROXY=""
DISABLE_UDP_RULES=0
case "$udp" in
tproxy) TPROXY=2 ;;
disabled) DISABLE_UDP_RULES=1 ;;
esac
for PROXY_PORTS in "" "-m multiport --dports 53,80,443,8000:8100"; do
ip netns exec "$ns" nft flush ruleset
ip netns exec "$ns" nft add table inet ss_spec
ipv6_rules_generate > "$tmp/rules"
ip netns exec "$ns" nft -f "$tmp/rules"
# Validate the serialized form used during a firewall reload.
ip netns exec "$ns" nft list ruleset > "$tmp/snapshot"
ip netns exec "$ns" nft flush ruleset
ip netns exec "$ns" nft -f "$tmp/snapshot"
count=$((count + 1))
done
done
done
done
printf 'PASS: %s IPv6 mode/OUTPUT/UDP/port combinations and snapshot reloads\n' "$count"
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/lua
-- Run on OpenWrt with the installed Lua dependencies; reads UCI, never updates it.
-- lua subscribe-ss.lua /path/to/subscribe.lua
local path = arg[1] or "/usr/share/shadowsocksr/subscribe.lua"
local file = assert(io.open(path))
local source = file:read("*a")
file:close()
source = source:gsub("^#![^\n]*", "")
local boundary = assert(source:find("\nlocal function md5_string", 1, true))
-- Load only the parser definitions, stopping before subscription execution.
local parse = assert(loadstring(source:sub(1, boundary - 1) .. [[
has_xray, has_ss_rust, has_mihomo = true, false, false
return processData
]], "@" .. path))()
local b64 = require("nixio").bin.b64encode
local count = 0
local function check(label, link, expected)
local ok, node = pcall(parse, "ss", link)
assert(ok, label .. ": parser raised an error")
if expected then
assert(node, label .. ": node rejected")
assert(node.type == "v2ray" and node.v2ray_protocol == "shadowsocks", label .. ": wrong backend")
for key, value in pairs(expected) do
assert(tostring(node[key]) == tostring(value), label .. ": incorrect " .. key)
end
else
assert(node == nil, label .. ": malformed node accepted")
end
count = count + 1
end
local expected = {server="ss.example.com", server_port=20070, encrypt_method_ss="aes-256-gcm", password="test-password", alias="乌克兰W01"}
local suffix = "#%E4%B9%8C%E5%85%8B%E5%85%B0W01"
local full = b64("aes-256-gcm:test-password@ss.example.com:20070")
check("whole-link base64", full .. suffix, expected)
check("unpadded whole-link base64", full:gsub("=+$", "") .. suffix, expected)
check("SIP002", b64("aes-256-gcm:test-password") .. "@ss.example.com:20070" .. suffix, expected)
check("plaintext credentials", "aes-256-gcm:test-password@ss.example.com:20070" .. suffix, expected)
local password = "p@ss:word+/%?#"
local credentials = "aes-256-gcm:" .. password
for _,link in ipairs({b64(credentials .. "@ss.example.com:20070"), b64(credentials) .. "@ss.example.com:20070", b64(credentials):gsub("+", "-"):gsub("/", "_"):gsub("=+$", "") .. "@ss.example.com:20070"}) do
check("reserved password characters", link, {password=password, server="ss.example.com", server_port=20070})
end
check("percent-encoded plaintext", "aes-256-gcm:p%40ss%3Aword+%2F%25%3F%23@ss.example.com:20070", {password=password})
check("legacy query", full .. "?type=ws&host=cdn.example.com&path=%2Fsocket&security=tls&sni=tls.example.com", {transport="ws", ws_path="/socket", tls="1", tls_host="tls.example.com"})
check("UUID extension", "12345678-1234-1234-1234-123456789abc@ss.example.com:443?encryption=none&type=ws", {password="12345678-1234-1234-1234-123456789abc", encrypt_method_ss="none", transport="ws"})
check("IPv6", b64("aes-256-gcm:test-password@[2001:db8::1]:443"), {server="2001:db8::1", server_port=443})
for _,link in ipairs({"", "invalid", b64("missing-separator"), b64("aes-256-gcm:password@ss.example.com"), "aes-256-gcm:password@:443", b64(":password@ss.example.com:443")}) do
check("malformed input", link, nil)
end
print("SS subscription parser: " .. count .. " cases passed")