Skip to content

Instantly share code, notes, and snippets.

@CodeAdminDe
Created February 8, 2025 20:33
Show Gist options
  • Select an option

  • Save CodeAdminDe/62c54152a5517d08bcfbf964e06d5729 to your computer and use it in GitHub Desktop.

Select an option

Save CodeAdminDe/62c54152a5517d08bcfbf964e06d5729 to your computer and use it in GitHub Desktop.
pfSense HA Failover // Custom script to handle WireGuard tunnel status based on CARP VIP status
#!/usr/local/bin/php-cgi -f
<?php
#
# Copyright by Frederic Roggon <[email protected]>
# License: GPL-3.0-or-later (https://spdx.org/licenses/GPL-3.0-or-later.html)
#
#
#this files goes at /custom as wgCarpFailover.php on primary and secondary firewall and needs chmod +x permissions
# set a shellcmd command with the following content "nohup /custom/wgCarpFailoverUpDown.php >/dev/null 2>&1 &" (w/o ").
require_once("config.inc");
require_once("util.inc");
$scriptName="custom-wgCarpFailover.php";
$wgTunnelInterface = "tun_wg1";
$carpEnabledInterface = "vtnet1.420";
$checkLoopIntervalInSecs = 1;
$bootWaitSecs = 30;
log_error("$scriptName - Alive, but waiting for full system boot for $bootWaitSecs seconds...");
sleep(30);
log_error("$scriptName - Started, checking for CARP changes on $carpEnabledInterface to toggle $wgTunnelInterface every $checkLoopIntervalInSecs secs ...");
while (true) {
$ifconfigOutputArr = [];
$ifconfigReturnVar = 0;
exec("ifconfig $carpEnabledInterface", $ifconfigOutputArr, $ifconfigReturnVar);
if ($ifconfigReturnVar === 0) {
$ifconfigOutputStr = implode("\n", $ifconfigOutputArr);
if (strpos($ifconfigOutputStr, 'carp:') !== false) {
if (strpos($ifconfigOutputStr, 'carp: MASTER') !== false) {
handleCarpStatus($scriptName, $wgTunnelInterface, "MASTER");
} elseif (strpos($ifconfigOutputStr, 'carp: BACKUP') !== false) {
handleCarpStatus($scriptName, $wgTunnelInterface, "BACKUP");
} else {
log_error("$scriptName - Interface $carpEnabledInterface is not in MASTER or BACKUP Status. Interfaces or system not ready?");
}
} else {
log_error("$scriptName - Interface $carpEnabledInterface is not a CARP-enabled interface.");
}
} else {
log_error("$scriptName - Error while executing ifconfig command (exit return val 1). Interfaces or system not ready?");
}
sleep($checkLoopIntervalInSecs);
}
function handleCarpStatus(string $scriptName, string $wgTunnelInterface, string $carpStatus) : void {
global $config;
if (!in_array($carpStatus, ['MASTER', 'BACKUP'])) {
log_error("$scriptName - Error while executing ifconfig command (exit return val 1). Interfaces or system not ready?");
return;
}
switch ($carpStatus) {
case 'MASTER':
$ifconfigWgInfStatusOutputArr = [];
$ifconfigWgInfStatusReturnVar = 0;
exec("ifconfig $wgTunnelInterface", $ifconfigWgInfStatusOutputArr, $ifconfigWgInfStatusReturnVar);
if ($ifconfigWgInfStatusReturnVar === 0) {
$ifconfigWgInfStatusOutputStr = implode("\n", $ifconfigWgInfStatusOutputArr);
} else {
log_error("$scriptName - Error while executing ifconfig $wgTunnelInterface command to handle $carpStatus (exit return val 1). Interfaces or system not ready?");
return;
}
if (strpos($ifconfigWgInfStatusOutputStr, 'UP') === false) {
log_error("$scriptName - Upping currently down WireGuard interface $wgTunnelInterface on this peer due to CARP $carpStatus event.");
$ifconfigWgUpOutputArr = [];
$ifconfigWgUpReturnVar = 0;
exec("ifconfig $wgTunnelInterface up", $ifconfigWgUpOutputArr, $ifconfigWgUpReturnVar);
if ($ifconfigWgUpReturnVar === 0) {
log_error("$scriptName - Upped WireGuard interface $wgTunnelInterface successfully.");
} else {
log_error("$scriptName - ERROR: Upped WireGuard interface $wgTunnelInterface failed.");
}
log_error("$scriptName - State clearing: Loading WAN IP and calculate Remote GW of $wgTunnelInterface.");
$pfLocalWanIp = $config['interfaces']['wan']['ipaddr'];
$wgRemoteVpnIp = "";
$wgRemoteVpnSubnet = "";
foreach($config['interfaces'] as $k => $v) {
if ("tun_wg1" === $v['if']) {
$wgRemoteVpnIp = $config['interfaces'][$k]['ipaddr'];
$wgRemoteVpnSubnet = $config['interfaces'][$k]['subnet'];
}
}
if (!empty($wgRemoteVpnIp) && !empty($wgRemoteVpnSubnet)) {
$ipLong = ip2long($wgRemoteVpnIp);
$maskLong = ~((1 << (32 - $wgRemoteVpnSubnet)) - 1);
$networkLong = $ipLong & $maskLong;
$network = long2ip($networkLong);
$wgRemoteVpnGw = long2ip($networkLong + 1);
} else {
log_error("$scriptName - ERROR: State clearing: Can not load WireGuard IP / Subnet to calculate Remote GW and clear that state.");
return;
}
log_error("$scriptName - State clearing: Loaded WAN IP ($pfLocalWanIp) and Remote GW ($wgRemoteVpnGw) of $wgTunnelInterface successfully.");
log_error("$scriptName - Killing old state for Gateway monitoring traffic ($pfLocalWanIp to $wgRemoteVpnGw) on this peer to allow fast switch.");
$pfctlClearStateOutputArr = [];
$pfctlClearStateReturnVar = 0;
exec("pfctl -k $pfLocalWanIp -k $wgRemoteVpnGw", $pfctlClearStateOutputArr, $pfctlClearStateReturnVar);
if ($pfctlClearStateReturnVar === 0) {
log_error("$scriptName - Killed old state for $pfLocalWanIp to $wgRemoteVpnGw successfully.");
} else {
log_error("$scriptName - ERROR: Killing state for $pfLocalWanIp to $wgRemoteVpnGw failed.");
}
}
break;
case 'BACKUP':
$ifconfigWgInfStatusOutputArr = [];
$ifconfigWgInfStatusReturnVar = 0;
exec("ifconfig $wgTunnelInterface", $ifconfigWgInfStatusOutputArr, $ifconfigWgInfStatusReturnVar);
if ($ifconfigWgInfStatusReturnVar === 0) {
$ifconfigWgInfStatusOutputStr = implode("\n", $ifconfigWgInfStatusOutputArr);
} else {
log_error("$scriptName - Error while executing ifconfig $wgTunnelInterface command to handle $carpStatus (exit return val 1). Interfaces or system not ready?");
return;
}
if (strpos($ifconfigWgInfStatusOutputStr, 'UP') !== false) {
log_error("$scriptName - Downing currently up WireGuard interface $wgTunnelInterface on this peer due to CARP $carpStatus event.");
$ifconfigWgUpOutputArr = [];
$ifconfigWgUpReturnVar = 0;
exec("ifconfig $wgTunnelInterface down", $ifconfigWgUpOutputArr, $ifconfigWgUpReturnVar);
if ($ifconfigWgUpReturnVar === 0) {
log_error("$scriptName - Downed WireGuard interface $wgTunnelInterface successfully.");
} else {
log_error("$scriptName - ERROR: Downing WireGuard interface $wgTunnelInterface failed.");
}
}
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment