Created
February 8, 2025 20:33
-
-
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
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-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