fix(awg): support AmneziaWG 2.0/3.0/3.1, fix WARP generator safety & update release 1.3.20
This commit is contained in:
@@ -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 ],
|
||||
|
||||
@@ -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, /^(<[^<>]+>)+$/))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user