Files
NeoTachyon/tachyon/files/usr/lib/providers/fptn/runtime.uc
T

416 lines
15 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let common = require("core.common");
let uci_core = require("core.uci");
let connections = require("config.connections");
let as_string = common.as_string;
let bool_value = common.bool_value;
let write_json = common.write_json;
let shell_quote = common.shell_quote;
let command_from_args = common.command_from_args;
let command_output = common.command_output;
let command_status = common.command_status;
let command_success_from_args = common.command_success_from_args;
let object_or_empty = common.object_or_empty;
let option = common.option;
let bool_option = common.bool_option;
let section_name = common.section_name;
let remove_file = common.remove_file;
const MODE = as_string(getenv("TACHYON_CONFIG_NAME")) || "tachyon";
const LIB_DIR = as_string(getenv("TACHYON_LIB_DIR")) || "/usr/lib/tachyon";
const CONFIG_NAME = MODE;
let cfg = require("providers.fptn.common").config({ lib_dir: LIB_DIR });
function log_message(msg, level) {
level = level || "info";
command_status("logger -t tachyon-fptn -p " + (level == "fatal" ? "user.err" : level == "warn" ? "user.warning" : "user.info") + " " + shell_quote(msg));
}
function provider_available() {
let st = fs.stat(cfg.binary);
return st != null && st.mode != null && (int(st.mode) & 73) != 0;
}
function enabled_sections() {
let sections = uci_core.section_objects(CONFIG_NAME, "section");
if (connections && connections.active_provider_sections)
return connections.active_provider_sections("fptn", sections);
if (connections && connections.fptn_sections)
return connections.fptn_sections();
let result = [];
for (let s in sections) {
if (bool_option(s, "enabled", true) && option(s, "action", "") == "fptn")
push(result, s);
}
return result;
}
function enabled_rule_count() {
return length(enabled_sections());
}
function running_pid() {
let pid_data = trim(as_string(fs.readfile(cfg.pid_file) || ""));
if (pid_data == "" || match(pid_data, /[^0-9]/))
return null;
let pid = int(pid_data);
if (pid <= 0)
return null;
if (fs.stat("/proc/" + pid) != null)
return pid;
remove_file(cfg.pid_file);
return null;
}
function package_version() {
if (!provider_available())
return "";
let out = trim(command_output(shell_quote(cfg.binary) + " --version 2>&1 || true"));
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i) || match(out, /([0-9]+\.[0-9a-zA-Z._-]+)/);
if (m)
return m[1];
let pkgs = require("core.packages");
if (pkgs && pkgs.version)
return pkgs.version("fptn") || pkgs.version("fptn-client") || "";
return "";
}
function remove_kernel_routing() {
let p = cfg.rule_priority || "102";
let mark_spec = cfg.fwmark + "/" + cfg.mark_mask;
for (let i = 0; i < 5; i++) {
if (command_status("ip rule del fwmark " + mark_spec + " table " + cfg.route_table + " priority " + p + " 2>/dev/null") != 0)
break;
}
command_status("ip route flush table " + cfg.route_table + " 2>/dev/null; true");
command_status("ip -4 route del default dev " + shell_quote(cfg.tun_interface) + " table main 2>/dev/null; true");
if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0)
command_status("ip link set dev " + shell_quote(cfg.tun_interface) + " down 2>/dev/null; true");
}
function install_kernel_routing() {
remove_kernel_routing();
command_status("ip link set dev " + shell_quote(cfg.tun_interface) + " up 2>/dev/null; true");
let route_ok = command_status("ip route replace default dev " + shell_quote(cfg.tun_interface) + " table " + cfg.route_table + " 2>/dev/null") == 0;
if (!route_ok) {
log_message("Failed to add default route dev " + cfg.tun_interface + " to table " + cfg.route_table, "warn");
return false;
}
let p = cfg.rule_priority || "102";
let mark_spec = cfg.fwmark + "/" + cfg.mark_mask;
let rule_ok = command_status("ip rule add fwmark " + mark_spec + " table " + cfg.route_table + " priority " + p + " 2>/dev/null") == 0;
if (!rule_ok) {
log_message("Failed to add ip rule for table " + cfg.route_table, "warn");
return false;
}
// Ensure table main default route was not hijacked by fptn-client-cli
let main_default_dev = trim(command_output("ip -4 route show table main default 2>/dev/null | awk '{print $5; exit}'"));
if (main_default_dev == cfg.tun_interface) {
log_message("FPTN client hijacked table main default route; restoring WAN route", "warn");
command_status("ip -4 route del default dev " + shell_quote(cfg.tun_interface) + " table main 2>/dev/null; true");
}
return true;
}
function stop_runtime() {
let pid = running_pid();
if (pid != null) {
log_message("Stopping FPTN daemon (PID " + pid + ")", "info");
command_status("kill -15 " + pid + " 2>/dev/null; true");
let stopped = false;
for (let i = 0; i < 20; i++) {
if (fs.stat("/proc/" + pid) == null) {
stopped = true;
break;
}
system("sleep 0.1 2>/dev/null || sleep 1");
}
if (!stopped && fs.stat("/proc/" + pid) != null) {
log_message("FPTN daemon did not stop gracefully; sending SIGKILL", "warn");
command_status("kill -9 " + pid + " 2>/dev/null; true");
}
}
remove_file(cfg.pid_file);
remove_kernel_routing();
return true;
}
function launch_fptn_process(section) {
let token = as_string(option(section, "access_token", ""));
if (token == "")
return false;
command_status("mkdir -p " + shell_quote(cfg.state_dir) + " 2>/dev/null");
let cmd_args = [
cfg.binary,
"--access-token", token,
"--tun-interface-name", cfg.tun_interface,
"--enable-split-tunnel", "false"
];
let sni = as_string(option(section, "sni", ""));
if (sni != "") {
push(cmd_args, "--sni");
push(cmd_args, sni);
}
let bypass_method = as_string(option(section, "bypass_method", ""));
if (bypass_method != "") {
push(cmd_args, "--bypass-method");
push(cmd_args, bypass_method);
}
let preferred_server = as_string(option(section, "preferred_server", ""));
if (preferred_server != "") {
push(cmd_args, "--preferred-server");
push(cmd_args, preferred_server);
}
let cmd_str = command_from_args(cmd_args) + " >> " + shell_quote(cfg.log_file) + " 2>&1 & echo $! > " + shell_quote(cfg.pid_file);
log_message("Starting FPTN client on interface " + cfg.tun_interface, "info");
system(cmd_str);
return true;
}
function supervise_runtime() {
let sup_pid_file = cfg.state_dir + "/supervisor.pid";
let cur_pid = trim(as_string(fs.readfile(sup_pid_file) || ""));
if (cur_pid != "" && !match(cur_pid, /[^0-9]/) && fs.stat("/proc/" + cur_pid) != null)
return true;
let delays = [ 2, 5, 10, 20, 30, 30, 30 ];
for (let i = 0; i < length(delays); i++) {
let sec_wait = delays[i];
system(sprintf("sleep %d 2>/dev/null || sleep %d", sec_wait, sec_wait));
let sections = enabled_sections();
if (length(sections) == 0)
break;
let tun_ok = command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0;
let p = running_pid();
if (!tun_ok || p == null) {
log_message("FPTN supervisor: retrying start (attempt " + (i + 1) + ")", "info");
stop_runtime();
launch_fptn_process(sections[0]);
for (let wait_i = 0; wait_i < 20; wait_i++) {
if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0) {
tun_ok = true;
break;
}
system("sleep 0.2 2>/dev/null || sleep 1");
}
}
if (tun_ok) {
let routed = install_kernel_routing();
if (routed) {
log_message("FPTN supervisor: interface " + cfg.tun_interface + " is UP and routing installed", "info");
remove_file(sup_pid_file);
return true;
}
}
}
remove_file(sup_pid_file);
return false;
}
function ensure_routing() {
let sections = enabled_sections();
if (length(sections) == 0)
return true;
let tun_up = command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0;
let p = running_pid();
if (p != null && tun_up) {
let route_installed = command_status("ip route show table " + cfg.route_table + " default dev " + shell_quote(cfg.tun_interface) + " 2>/dev/null | grep -q default") == 0;
let rule_installed = command_status("ip rule show 2>/dev/null | grep -q " + shell_quote(cfg.route_table)) == 0;
if (route_installed && rule_installed)
return true;
return install_kernel_routing();
}
return start_runtime();
}
function start_runtime() {
let sections = enabled_sections();
if (length(sections) == 0) {
stop_runtime();
return true;
}
if (!provider_available()) {
log_message("Cannot start FPTN: binary " + cfg.binary + " not found or not executable", "warn");
return false;
}
let section = sections[0];
let token = as_string(option(section, "access_token", ""));
if (token == "") {
log_message("Cannot start FPTN: access_token not configured for section " + section_name(section), "warn");
return false;
}
// Stop and disable conflicting standalone init.d service if present
command_status("/etc/init.d/fptn stop >/dev/null 2>&1 || true; /etc/init.d/fptn disable >/dev/null 2>&1 || true;");
stop_runtime();
if (!launch_fptn_process(section))
return false;
let started = false;
for (let i = 0; i < 30; i++) {
if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0) {
started = true;
break;
}
let p = running_pid();
if (p == null && i > 5)
break;
system("sleep 0.1 2>/dev/null || sleep 1");
}
if (!started) {
log_message("FPTN interface " + cfg.tun_interface + " did not come up immediately (WAN may be initializing); launching background retry supervisor", "warn");
let sup_pid_file = cfg.state_dir + "/supervisor.pid";
let sup_exec = sprintf("ucode -L %s %s supervise-runtime",
shell_quote(LIB_DIR), shell_quote(LIB_DIR + "/providers/fptn/runtime.uc"));
system(common.background_command_with_pid(sup_exec, ">/dev/null", ">" + shell_quote(sup_pid_file)));
return true;
}
let routing_ok = install_kernel_routing();
if (!routing_ok) {
log_message("FPTN interface " + cfg.tun_interface + " came up but routing failed; launching background supervisor", "warn");
let sup_pid_file = cfg.state_dir + "/supervisor.pid";
let sup_exec = sprintf("ucode -L %s %s supervise-runtime",
shell_quote(LIB_DIR), shell_quote(LIB_DIR + "/providers/fptn/runtime.uc"));
system(common.background_command_with_pid(sup_exec, ">/dev/null", ">" + shell_quote(sup_pid_file)));
return true;
}
log_message("FPTN client successfully started on " + cfg.tun_interface + " (table " + cfg.route_table + ")", "info");
return true;
}
function status_json() {
let installed = provider_available();
let pid = running_pid();
let running = pid != null;
let rule_count = enabled_rule_count();
let ver = package_version();
let tun_up = command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0;
let route_installed = command_status("ip route show table " + cfg.route_table + " default dev " + shell_quote(cfg.tun_interface) + " 2>/dev/null | grep -q default") == 0;
let rule_installed = command_status("ip rule show 2>/dev/null | grep -q " + shell_quote(cfg.route_table)) == 0;
let ready = installed && running && tun_up && route_installed && rule_installed && rule_count > 0;
let status_msg = "FPTN is running";
if (!installed)
status_msg = "FPTN is not installed";
else if (rule_count == 0)
status_msg = "FPTN is not configured";
else if (!running)
status_msg = "FPTN is stopped";
else if (!tun_up)
status_msg = "FPTN is running (degraded: tun interface down)";
else if (!route_installed)
status_msg = "FPTN is running (degraded: table " + cfg.route_table + " route missing)";
else if (!rule_installed)
status_msg = "FPTN is running (degraded: table " + cfg.route_table + " ip rule missing)";
write_json({
installed: installed,
configured: rule_count > 0,
enabled_rule_count: rule_count,
service_running: running,
process_running: running,
tun_up: tun_up,
route_installed: route_installed,
rule_installed: rule_installed,
pid: pid,
version: ver,
binary: cfg.binary,
tun_interface: cfg.tun_interface,
route_table: cfg.route_table,
log_file: cfg.log_file,
ready: ready,
status_message: status_msg
});
return true;
}
function check_json() {
write_json({
fptn_installed: provider_available() ? 1 : 0,
fptn_version: package_version(),
binary: cfg.binary
});
return true;
}
function module_exports() {
return {
provider_available: provider_available,
package_version: package_version,
start_runtime: start_runtime,
stop_runtime: stop_runtime,
ensure_routing: ensure_routing,
supervise_runtime: supervise_runtime,
status_json: status_json,
check_json: check_json
};
}
if (sourcepath(1) != null && sourcepath(1) != "")
return module_exports();
let mode = as_string(ARGV[0]);
if (mode == "start-runtime")
exit(start_runtime() ? 0 : 1);
else if (mode == "stop-runtime")
exit(stop_runtime() ? 0 : 1);
else if (mode == "restart-runtime") {
stop_runtime();
exit(start_runtime() ? 0 : 1);
}
else if (mode == "ensure-routing")
exit(ensure_routing() ? 0 : 1);
else if (mode == "supervise-runtime")
exit(supervise_runtime() ? 0 : 1);
else if (mode == "status")
status_json();
else if (mode == "check")
check_json();
else if (mode == "installed" || mode == "provider-available")
exit(provider_available() ? 0 : 1);
else if (mode == "package-version" || mode == "version")
printf("%s\n", package_version());
else if (mode == "enabled-rule-count")
printf("%d\n", enabled_rule_count());
else {
warn("Usage: providers/fptn/runtime.uc <start-runtime|stop-runtime|restart-runtime|ensure-routing|supervise-runtime|status|check|installed|version>\n");
exit(1);
}