#!/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 \n"); exit(1); }