fix(awg): support AmneziaWG 2.0/3.0/3.1, fix WARP generator safety & update release 1.3.20

This commit is contained in:
Dushnilin
2026-09-05 12:00:14 +03:00
parent dbe6eb013c
commit a4a757223f
25 changed files with 8476 additions and 6142 deletions
+1 -1
View File
@@ -282,7 +282,7 @@ function command_spec(command) {
fuzzer_auto_apply: [ "diagnostics/fuzzer.uc", "auto_apply", 1 ],
fuzzer_history: [ "diagnostics/fuzzer.uc", "history", 1 ],
fuzzer_clear_history: [ "diagnostics/fuzzer.uc", "clear_history", 0 ],
generate_warp: [ "service/warp_generator.uc", "", 1 ],
generate_warp: [ "service/warp_generator.uc", "", 2 ],
package_prerm: [ "service/package.uc", "prerm", 1 ],
package_postinst: [ "service/package.uc", "postinst", 0 ],
luci_postinst: [ "service/package.uc", "luci-postinst", 0 ],
+6
View File
@@ -176,6 +176,12 @@ function awg_tag_chain(value) {
if (value == "" || value == "0")
return "";
// Heal previously truncated tag chains (e.g. from broken conf import)
if (match(value, /^[0-9a-fA-F]+><[^<>]+>/))
value = "<b 0x" + value;
if (match(value, /<[^<>]+$/))
value = value + ">";
// A well-formed tag chain: keep it verbatim, including inner spacing
// ("<b 0x..>" carries a space inside each tag).
if (match(value, /^(<[^<>]+>)+$/))
+23 -1
View File
@@ -641,7 +641,7 @@ function section_has_dscp_matchers(section) {
function section_needs_priority_sets(section) {
return section_priority_action(section) != "" &&
(section_has_nft_ip_matchers(section) || section_has_nft_port_only_matchers(section) || section_has_dscp_matchers(section));
(section_has_nft_ip_matchers(section) || section_has_nft_port_only_matchers(section) || section_has_dscp_matchers(section) || section_has_source_ip_matchers(section));
}
function nft_create_priority_chains(table) {
@@ -791,6 +791,28 @@ function nft_add_section_priority_rules(table, section, interface_set, localv4_s
return false;
}
let has_source_only_matchers = section_has_source_ip_matchers(section) &&
!needs_plain_ip_rules &&
!needs_ip_port_rules &&
!has_port_only_matchers &&
!section_has_dscp_matchers(section);
if (has_source_only_matchers) {
if (is_bypass) {
if (!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, [], [], mark) ||
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, [], [], mark))
return false;
} else {
let match_tcp = [ "meta", "l4proto", "tcp" ];
let match_udp = [ "meta", "l4proto", "udp" ];
if (!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_tcp, match_tcp, mark) ||
!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_udp, match_udp, mark) ||
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_tcp, match_tcp, mark) ||
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_udp, match_udp, mark))
return false;
}
}
return true;
}
@@ -424,7 +424,17 @@ if (!response_data.warp_enabled) {
fs.unlink(tmp_patch);
}
let license_key = ARGV[0] || "";
let target_version = "2.0";
let license_key = "";
for (let a in ARGV) {
if (a == "2.0" || a == "3.1") {
target_version = a;
} else if (a != null && trim(a) != "") {
license_key = trim(a);
}
}
if (license_key != "") {
let patch_body = { license_key: license_key };
let tmp_patch = exec_output("mktemp 2>/dev/null") || "/tmp/warp_patch.json";
@@ -511,23 +521,71 @@ let i3 = to_hex(generate_extra_cps());
let i4 = to_hex(generate_extra_cps());
let i5 = to_hex(generate_extra_cps());
write_json({
function generate_base64_key() {
let k = exec_output("head -c 32 /dev/urandom | base64 2>/dev/null");
if (k != "") {
k = replace(trim(k), /[\r\n\t ]/g, "");
if (length(k) >= 40) return k;
}
let pair = exec_output("/usr/bin/sing-box generate wg-keypair 2>/dev/null");
for (let l in split(pair, "\n")) {
let m = match(l, /^PrivateKey:\s*(.+)$/);
if (m) return trim(m[1]);
}
return "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}
function random_range(min_bound, max_bound, span_min, span_max) {
let span = span_min + (time() * 7 + int(exec_output("head -c 2 /dev/urandom | hexdump -ve '1/2 \"%u\"' 2>/dev/null") || "1000")) % (span_max - span_min + 1);
let start = min_bound + (time() * 13 + int(exec_output("head -c 4 /dev/urandom | hexdump -ve '1/4 \"%u\"' 2>/dev/null") || "50000")) % (max_bound - min_bound - span);
let end = start + span;
return sprintf("%u-%u", start, end);
}
function random_s() {
let r = 20 + (int(exec_output("head -c 2 /dev/urandom | hexdump -ve '1/2 \"%u\"' 2>/dev/null") || "50")) % 130;
return r >= 12 ? r : 25;
}
let req_version = "2.0";
if (length(ARGV) > 2 && ARGV[2] != "") {
req_version = trim(ARGV[2]);
} else if (length(ARGV) > 1 && (ARGV[1] == "2.0" || ARGV[1] == "3.0" || ARGV[1] == "3.1")) {
req_version = trim(ARGV[1]);
}
// Cloudflare WARP servers ONLY support standard WireGuard / AWG 2.0 headers.
// They do NOT run AmneziaWG and do not support HeaderProtectionKey or AWG 3.x.
// Therefore, the WARP generator always outputs a working AWG 2.0 configuration.
let result = {
success: true,
local_address: response_data.config.interface.addresses.v4 + "/32 " + response_data.config.interface.addresses.v6 + "/128",
private_key: private_key,
peer_public_key: response_data.config.peers[0].public_key,
server_address: best_ip,
server_port: random_port,
awg_jc: 4,
awg_version: "2.0",
awg_jc: 5,
awg_jmin: 40,
awg_jmax: 70,
awg_s1: 0, awg_s2: 0, awg_s3: 0, awg_s4: 0,
awg_h1: 1, awg_h2: 2, awg_h3: 3, awg_h4: 4,
awg_s1: 0,
awg_s2: 0,
awg_s3: 0,
awg_s4: 0,
awg_h1: 1,
awg_h2: 2,
awg_h3: 3,
awg_h4: 4,
awg_i1: i1,
awg_i2: i2,
awg_i3: i3,
awg_i4: i4,
awg_i5: i5
});
};
if (req_version != "2.0") {
result.message_hint = "warning_warp_needs_awg20";
}
write_json(result);
@@ -1303,8 +1303,10 @@ function add_awg_endpoint(config, section) {
let j2 = awg_cps_option(section, "awg_j2");
let j3 = awg_cps_option(section, "awg_j3");
let itime = int_option(section, "awg_itime", "0");
let is_lx = trim(fs.readfile("/etc/tachyon/sing-box-variant") || "") == "lx" ||
index(trim(fs.readfile("/etc/tachyon/sing-box-version") || ""), "-lx") >= 0;
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
let sb_version_val = trim(fs.readfile(sb_version_file) || "");
let is_lx = sb_version_val != "" ? (index(sb_version_val, "-lx") >= 0) : (trim(fs.readfile(sb_variant_file) || "") == "lx");
if (is_lx) {
// sing-box-lx expects AWG fields at the endpoint root (AWG 2.0 schema).
@@ -1326,6 +1328,52 @@ function add_awg_endpoint(config, section) {
if (i3 != "" && i3 != "0") endpoint.i3 = i3;
if (i4 != "" && i4 != "0") endpoint.i4 = i4;
if (i5 != "" && i5 != "0") endpoint.i5 = i5;
// AmneziaWG version handling: 2.0, 3.0, and 3.1 (sing-box-lx v1.14.0-lx.32+)
let awg_ver = option(section, "awg_version", "");
if (awg_ver == "") {
if (option(section, "awg_rekey_after_time", "") != "" || option(section, "awg_rekey_timeout", "") != "" ||
option(section, "awg_reject_after_time", "") != "" || option(section, "awg_keepalive_timeout", "") != "" ||
option(section, "awg_max_handshake_attempts", "") != "") {
awg_ver = "3.1";
} else if (option(section, "awg_header_protection_key", "") != "" || option(section, "awg_content_padding_addition", "") != "") {
awg_ver = "3.0";
} else {
awg_ver = "2.0";
}
}
if (awg_ver == "3.0" || awg_ver == "3.1") {
let hpk = option(section, "awg_header_protection_key", "");
if (hpk != "") {
// Ensure S1-S4 are >= 12 if header_protection_key is set (sing-box-lx schema requirement)
if (endpoint.s1 < 12) endpoint.s1 = 20;
if (endpoint.s2 < 12) endpoint.s2 = 20;
if (endpoint.s3 < 12) endpoint.s3 = 20;
if (endpoint.s4 < 12) endpoint.s4 = 20;
endpoint.header_protection_key = hpk;
}
let cpa = option(section, "awg_content_padding_addition", "");
if (cpa != "") endpoint.content_padding_addition = cpa;
}
if (awg_ver == "3.1") {
let rka = option(section, "awg_rekey_after_time", "");
if (rka != "") endpoint.rekey_after_time = rka;
let rkt = option(section, "awg_rekey_timeout", "");
if (rkt != "") endpoint.rekey_timeout = rkt;
let rja = option(section, "awg_reject_after_time", "");
if (rja != "") endpoint.reject_after_time = rja;
let kpt = option(section, "awg_keepalive_timeout", "");
if (kpt != "") endpoint.keepalive_timeout = kpt;
let mha = option(section, "awg_max_handshake_attempts", "");
if (mha != "") endpoint.max_handshake_attempts = mha;
}
// j1-j3/itime are sing-box-extended-only fields and are not part of
// the lx AWG 2.0 schema, so they are intentionally not emitted here.
} else {
@@ -1340,7 +1340,9 @@ function add_combined_route_for_section(config, section) {
let has_route_matchers = route_rule.domain != null || route_rule.domain_suffix != null ||
route_rule.domain_keyword != null || route_rule.domain_regex != null ||
route_rule.ip_cidr != null || route_rule.port != null || route_rule.port_range != null ||
route_rule.ip_cidr != null || route_rule.source_ip_cidr != null ||
route_rule.port != null || route_rule.port_range != null ||
route_rule.protocol != null || route_rule.dscp != null ||
route_rule.rule_set != null;
if (has_route_matchers) {
let resolve = runtime_route.resolve_rule_for_section(section, route_rule);
+21
View File
@@ -989,6 +989,27 @@ function init_config(populate_nft, caches_prepared, no_refresh) {
stripped = true;
}
}
else if (match(check_result.reason, /header_protection_key/)) {
log_message("Installed sing-box rejected header_protection_key: " + check_result.reason + "; stripping and retrying", "warn");
let cfg_text = as_string(fs.readfile(temp_config) || "");
let cfg = length(cfg_text) > 0 ? json(cfg_text) : null;
if (type(cfg) == "object") {
if (type(cfg.endpoints) == "array") {
for (let ep in cfg.endpoints) {
if (type(ep) == "object" && ep.header_protection_key != null)
delete ep.header_protection_key;
}
}
if (type(cfg.inbounds) == "array") {
for (let inb in cfg.inbounds) {
if (type(inb) == "object" && inb.header_protection_key != null)
delete inb.header_protection_key;
}
}
write_file(temp_config, sprintf("%J", cfg));
stripped = true;
}
}
}
if (!stripped)
break;
+60 -3
View File
@@ -212,6 +212,10 @@ function apply_transport(inbound, section, protocol) {
}
function add_standard_inbound(config, section, protocol, tag_name) {
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
let sb_version_val = trim(fs.readfile(sb_version_file) || "");
let is_lx = sb_version_val != "" ? (index(sb_version_val, "-lx") >= 0) : (trim(fs.readfile(sb_variant_file) || "") == "lx");
let inbound = {
type: protocol,
tag: tag_name,
@@ -305,8 +309,6 @@ function add_standard_inbound(config, section, protocol, tag_name) {
let jmin = int_option(section, "awg_jmin", "40");
let jmax = int_option(section, "awg_jmax", "70");
if (jmax > 1200) jmax = 1200;
let is_lx = trim(fs.readfile("/etc/tachyon/sing-box-variant") || "") == "lx" ||
index(trim(fs.readfile("/etc/tachyon/sing-box-version") || ""), "-lx") >= 0;
let amnezia = {
jc: jc,
@@ -354,6 +356,52 @@ function add_standard_inbound(config, section, protocol, tag_name) {
if (i3 != "" && i3 != "0") inbound.i3 = i3;
if (i4 != "" && i4 != "0") inbound.i4 = i4;
if (i5 != "" && i5 != "0") inbound.i5 = i5;
// AmneziaWG version handling: 2.0, 3.0, and 3.1 (sing-box-lx v1.14.0-lx.32+)
let awg_ver = option(section, "awg_version", "");
if (awg_ver == "") {
if (option(section, "awg_rekey_after_time", "") != "" || option(section, "awg_rekey_timeout", "") != "" ||
option(section, "awg_reject_after_time", "") != "" || option(section, "awg_keepalive_timeout", "") != "" ||
option(section, "awg_max_handshake_attempts", "") != "") {
awg_ver = "3.1";
} else if (option(section, "awg_header_protection_key", "") != "" || option(section, "awg_content_padding_addition", "") != "") {
awg_ver = "3.0";
} else {
awg_ver = "2.0";
}
}
if (awg_ver == "3.0" || awg_ver == "3.1") {
let hpk = option(section, "awg_header_protection_key", "");
if (hpk != "") {
// Ensure S1-S4 are >= 12 if header_protection_key is set (sing-box-lx schema requirement)
if (inbound.s1 < 12) inbound.s1 = 20;
if (inbound.s2 < 12) inbound.s2 = 20;
if (inbound.s3 < 12) inbound.s3 = 20;
if (inbound.s4 < 12) inbound.s4 = 20;
inbound.header_protection_key = hpk;
}
let cpa = option(section, "awg_content_padding_addition", "");
if (cpa != "") inbound.content_padding_addition = cpa;
}
if (awg_ver == "3.1") {
let rka = option(section, "awg_rekey_after_time", "");
if (rka != "") inbound.rekey_after_time = rka;
let rkt = option(section, "awg_rekey_timeout", "");
if (rkt != "") inbound.rekey_timeout = rkt;
let rja = option(section, "awg_reject_after_time", "");
if (rja != "") inbound.reject_after_time = rja;
let kpt = option(section, "awg_keepalive_timeout", "");
if (kpt != "") inbound.keepalive_timeout = kpt;
let mha = option(section, "awg_max_handshake_attempts", "");
if (mha != "") inbound.max_handshake_attempts = mha;
}
} else {
if (i1 != "") amnezia.i1 = i1;
if (i2 != "") amnezia.i2 = i2;
@@ -380,7 +428,16 @@ function add_standard_inbound(config, section, protocol, tag_name) {
apply_tls(inbound, section, protocol);
apply_transport(inbound, section, protocol);
push(config.inbounds, inbound);
if (protocol == "awg" && is_lx) {
let server_port = int_option(section, "awg_server_port", int_option(section, "listen_port", 51820));
inbound.listen_port = server_port;
delete inbound.listen;
if (!config.endpoints)
config.endpoints = [];
push(config.endpoints, inbound);
} else {
push(config.inbounds, inbound);
}
}
function add_json_inbound(config, section, tag_name) {