-
-
Save x-stp/8f19e45d804644ee2a0c39e2bf26ec8b to your computer and use it in GitHub Desktop.
Disable WAN Interface on CARP Backup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/local/bin/php | |
| <?php | |
| declare(strict_types=1); | |
| /* opnsense carp hook: toggle wan (+optional others) on master/backup | |
| * based on kronenpj (e90258f12f7a40c4f38a23b609b3288b) and spali (2da4f23e488219504b2ada12ac59a7dc) | |
| * pepijn van der stap, neosecurity.nl - 2025-11-11 | |
| */ | |
| require_once "config.inc"; | |
| require_once "system.inc"; | |
| require_once "interfaces.inc"; | |
| require_once "interfaces.lib.inc"; | |
| require_once "util.inc"; | |
| const LOG_PREFIX = "[wan-carp]"; | |
| const LOCK_FILE = "/var/run/10-wancarp.lock"; | |
| $IFACES = ["wan"]; // e.g. ['wan','opt2'] | |
| $ONLY_SUBSYSTEM = ""; // e.g. "40@vlan009" (vhid@if). leave '' to react to all | |
| $REWRITE_ROUTE_ON_BACKUP = false; // true => backup defaults via lan vip | |
| $LAN_VIP = ""; // e.g. '10.10.10.1' (used only if ^ true) | |
| $RESTART_ROUTING_ON_MASTER = false; // true => pluginctl routing restart after master | |
| $RESTART_DELAY_SECONDS = 3; | |
| $subsystem = $argv[1] ?? ""; | |
| $type = $argv[2] ?? ""; | |
| /* avoid races */ | |
| $lock = @fopen(LOCK_FILE, "c"); | |
| if ($lock && !@flock($lock, LOCK_EX | LOCK_NB)) { | |
| carp_log("lock busy; skip '$subsystem'"); | |
| exit(75); | |
| } | |
| /* validate */ | |
| if (!in_array($type, ["MASTER", "BACKUP", "INIT"], true)) { | |
| carp_log("unknown event '$type' from '$subsystem'"); | |
| exit(64); | |
| } | |
| if (strpos($subsystem, "@") === false) { | |
| carp_log("invalid subsystem '$subsystem'"); | |
| exit(64); | |
| } | |
| if ($ONLY_SUBSYSTEM !== "" && $subsystem !== $ONLY_SUBSYSTEM) { | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| if ($type === "INIT") { | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| /* pick existing target ifaces, prevent ghosting config branches */ | |
| $config["interfaces"] = $config["interfaces"] ?? []; | |
| $targets = []; | |
| foreach (array_values(array_unique($IFACES)) as $k) { | |
| if ( | |
| isset($config["interfaces"][$k]) && | |
| is_array($config["interfaces"][$k]) | |
| ) { | |
| $targets[] = $k; | |
| } else { | |
| carp_log("alias '$k' not configured; skipping"); | |
| } | |
| } | |
| if (!$targets) { | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| /* use first target (usually 'wan') as idempotency probe */ | |
| $probe = $targets[0]; | |
| $enabled = ($config["interfaces"][$probe]["enable"] ?? null) === "1"; | |
| if ($type === "MASTER") { | |
| if ($enabled) { | |
| carp_log("MASTER duplicate; already enabled ($subsystem)"); | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| foreach ($targets as $ifkey) { | |
| carp_log("MASTER on '$subsystem': enable '$ifkey'"); | |
| $config["interfaces"][$ifkey]["enable"] = "1"; | |
| if_flag($ifkey, "up"); | |
| interface_configure(false, $ifkey, true, true); | |
| } | |
| write_config(LOG_PREFIX . " master transition ($subsystem)", false); | |
| if ($RESTART_ROUTING_ON_MASTER) { | |
| sleep((int) $RESTART_DELAY_SECONDS); | |
| mwexec("/usr/local/sbin/pluginctl -s routing restart 2>&1", $rc); | |
| if ($rc !== 0) { | |
| carp_log("routing restart rc=$rc after master ($subsystem)"); | |
| } | |
| } | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| if ($type === "BACKUP") { | |
| if (!$enabled) { | |
| carp_log("BACKUP duplicate; already disabled ($subsystem)"); | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| foreach ($targets as $ifkey) { | |
| carp_log("BACKUP on '$subsystem': disable '$ifkey'"); | |
| interface_reset($ifkey); // dhclient/pppoe/states... | |
| unset($config["interfaces"][$ifkey]["enable"]); | |
| interface_configure(false, $ifkey, true, false); | |
| if_flag($ifkey, "down"); | |
| } | |
| write_config(LOG_PREFIX . " backup transition ($subsystem)", false); | |
| if ($REWRITE_ROUTE_ON_BACKUP && filter_var($LAN_VIP, FILTER_VALIDATE_IP)) { | |
| rewrite_default_route_to($LAN_VIP); | |
| } | |
| cleanup_lock($lock); | |
| exit(0); | |
| } | |
| /* helpers */ | |
| function carp_log(string $msg): void | |
| { | |
| log_error(LOG_PREFIX . " " . $msg); | |
| } | |
| function cleanup_lock($lock): void | |
| { | |
| if ($lock) { | |
| @flock($lock, LOCK_UN); | |
| @fclose($lock); | |
| } | |
| } | |
| function if_flag(string $ifkey, string $flag): void | |
| { | |
| if (function_exists("legacy_interface_flags")) { | |
| legacy_interface_flags($ifkey, $flag); | |
| return; | |
| } | |
| $dev = get_real_interface($ifkey); | |
| if ($dev) { | |
| mwexec( | |
| "/sbin/ifconfig " . | |
| escapeshellarg($dev) . | |
| " " . | |
| escapeshellarg($flag) . | |
| " 2>&1", | |
| $rc, | |
| ); | |
| if ($rc !== 0) { | |
| carp_log("ifconfig $flag failed on '$dev' rc=$rc"); | |
| } | |
| } | |
| } | |
| function rewrite_default_route_to(string $vip): void | |
| { | |
| mwexec("/sbin/route del default 2>&1", $r1); | |
| mwexec("/sbin/route add default " . escapeshellarg($vip) . " 2>&1", $r2); | |
| if ($r1 !== 0 || $r2 !== 0) { | |
| carp_log("route rewrite rc={$r1}/{$r2}"); | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment