diff --git a/ipsec-vpn/src/com/untangle/app/ipsec_vpn/IpsecVpnApp.java b/ipsec-vpn/src/com/untangle/app/ipsec_vpn/IpsecVpnApp.java index b9b433f133..69956aefd7 100644 --- a/ipsec-vpn/src/com/untangle/app/ipsec_vpn/IpsecVpnApp.java +++ b/ipsec-vpn/src/com/untangle/app/ipsec_vpn/IpsecVpnApp.java @@ -40,6 +40,9 @@ import com.untangle.uvm.vnet.PipelineConnector; import com.untangle.uvm.util.I18nUtil; import com.untangle.uvm.HostTableEntry; +import com.untangle.uvm.app.App; +import static com.untangle.uvm.app.License.WAN_FAILOVER; +import static com.untangle.uvm.app.AppSettings.AppState.RUNNING; /** * The IPsec application manages all IPsec tunnels and VPN configurations. @@ -430,6 +433,10 @@ protected void preStart(boolean isPermanentTransition) // Fix for NGFW-14844, wait for charon to restart and then rewrite STRONGSWAN_CONF_FILE waitForCharonStart(); + + // Initialize active WAN address from WAN Failover if it's running + initializeActiveWanFromFailover(); + reconfigure(); } @@ -461,6 +468,30 @@ private void waitForCharonStart() { } } + /** + * Initialize active WAN address from WAN Failover if it's running. + * This ensures IPsec uses the correct active WAN (based on weights and status) + * when it starts while WAN Failover is already running. + */ + private void initializeActiveWanFromFailover() { + try { + App wanFailoverApp = UvmContextFactory.context().appManager().app(WAN_FAILOVER); + if (wanFailoverApp != null && wanFailoverApp.getRunState() == RUNNING) { + logger.info("WAN Failover is running - requesting current active WAN ID via hook"); + // Trigger WAN Failover to update and send the current active WAN ID + // WAN Failover listens to REQUEST_ACTIVE_WAN_ID hook and will respond via WAN_FAILOVER_CHANGE + // Using synchronous call since this hook doesn't require arguments + UvmContextFactory.context().hookManager().callCallbacksSynchronous( + com.untangle.uvm.HookManager.REQUEST_ACTIVE_WAN_ID); + logger.debug("Successfully triggered WAN Failover to send active WAN ID"); + } else { + logger.info("WAN Failover is not running - using default WAN address"); + } + } catch (Exception e) { + logger.warn("Error initializing active WAN from WAN Failover: ", e.getMessage()); + } + } + /** * After the app is started, we create our timer tasks * diff --git a/uvm/api/com/untangle/uvm/HookManager.java b/uvm/api/com/untangle/uvm/HookManager.java index 577fe480a6..e0ad1acfc2 100644 --- a/uvm/api/com/untangle/uvm/HookManager.java +++ b/uvm/api/com/untangle/uvm/HookManager.java @@ -34,7 +34,8 @@ public interface HookManager public static String CAPTURE_USERNAME_CHECK = "capture-username-check"; public static String WEBFILTER_BASE_CATEGORIZE_SITE = "webfilter-base-categorize-site"; public static String WAN_FAILOVER_CHANGE = "wan-failover-change"; - + public static String WAN_BALANCER_CHANGE = "wan-balancer-change"; + public static String REQUEST_ACTIVE_WAN_ID = "request-active-wan-id"; public boolean isRegistered( String hookName, HookCallback callback ); public boolean registerCallback( String groupName, HookCallback callback ); diff --git a/wan-balancer/src/com/untangle/app/wan_balancer/WanBalancerApp.java b/wan-balancer/src/com/untangle/app/wan_balancer/WanBalancerApp.java index eca4e7c8b0..3ef5e81199 100644 --- a/wan-balancer/src/com/untangle/app/wan_balancer/WanBalancerApp.java +++ b/wan-balancer/src/com/untangle/app/wan_balancer/WanBalancerApp.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import com.untangle.uvm.HookManager; import com.untangle.uvm.UvmContextFactory; import com.untangle.uvm.SettingsManager; @@ -22,6 +23,9 @@ import com.untangle.uvm.vnet.Affinity; import com.untangle.uvm.vnet.Fitting; import com.untangle.uvm.vnet.PipelineConnector; +import com.untangle.uvm.app.App; +import static com.untangle.uvm.app.License.WAN_FAILOVER; +import static com.untangle.uvm.app.AppSettings.AppState.RUNNING; /** * The Wan Balancer Application @@ -138,6 +142,7 @@ public synchronized void setSettings(final WanBalancerSettings newSettings) * Sync the new settings */ syncToSystem(true); + UvmContextFactory.context().hookManager().callCallbacks( HookManager.WAN_BALANCER_CHANGE, this.settings.getWeights()); } /** @@ -155,6 +160,34 @@ protected void preStart(boolean isPermanentTransition) if (isPermanentTransition) syncToSystem(true); } + /** + * Called after the application is started + * + * @param isPermanentTransition + * Permanent transition flag + */ + @Override + protected void postStart(boolean isPermanentTransition) + { + // Notify WAN Failover that WAN Balancer has started + // This is done in postStart() to ensure WAN Balancer is fully running + // before WAN Failover checks the run state + notifyWanFailover(); + } + /** + * Called before the application is stopped + * + * @param isPermanentTransition + * Permanent transition flag + */ + @Override + protected void preStop(boolean isPermanentTransition) + { + // Notify WAN Failover that WAN Balancer is stopping so it can re-evaluate active WAN + // This ensures IPsec and other apps switch from weight-based to first-active-WAN logic + notifyWanFailover(); + } + /** * Called after the application is stopped * @@ -179,6 +212,29 @@ protected void postDestroy() syncToSystem(false); } + /** + * Notify WAN Failover that WAN Balancer has stopped or started. + * This triggers WAN Failover to re-evaluate the active WAN and switch + * from weight-based selection to first-active-WAN selection. + */ + private void notifyWanFailover() + { + try { + App wanFailoverApp = UvmContextFactory.context().appManager().app(WAN_FAILOVER); + if (wanFailoverApp != null && wanFailoverApp.getRunState() == RUNNING) { + // WAN Failover is already listening to WAN_BALANCER_CHANGE hook + // Send current weights + int[] weights = null; + if (this.settings != null) { + weights = this.settings.getWeights(); + } + UvmContextFactory.context().hookManager().callCallbacks(HookManager.WAN_BALANCER_CHANGE, weights); + } + } catch (Exception e) { + logger.warn("Failed to notify WAN Failover: ", e.getMessage()); + } + } + /** * Called after the application is initialized */ diff --git a/wan-failover/src/com/untangle/app/wan_failover/WanFailoverApp.java b/wan-failover/src/com/untangle/app/wan_failover/WanFailoverApp.java index 19d56447b1..f698975219 100644 --- a/wan-failover/src/com/untangle/app/wan_failover/WanFailoverApp.java +++ b/wan-failover/src/com/untangle/app/wan_failover/WanFailoverApp.java @@ -44,9 +44,14 @@ public class WanFailoverApp extends AppBase private final PipelineConnector[] connectors = new PipelineConnector[] {}; private final WanFailoverNetworkHookCallback networkHookCallback = new WanFailoverNetworkHookCallback(); + private final WanFailoverWanBalancerHookCallback wanBalancerHookCallback = new WanFailoverWanBalancerHookCallback(); + private final ActiveWanIdHookCallback activeWanIdHookCallback = new ActiveWanIdHookCallback(); private WanFailoverSettings settings = null; + // Initialize to empty array instead of null so we never get null pointer exceptions + // Will be populated by WAN Balancer via the WAN_BALANCER_CHANGE hook when available + protected static int[] wanBalancerWeights = new int[0]; protected static ExecManager execManager = null; private WanFailoverTesterMonitor wanFailoverTesterMonitor = null; @@ -220,6 +225,8 @@ protected void postInit() protected synchronized void preStart(boolean isPermanentTransition) { UvmContextFactory.context().hookManager().registerCallback(com.untangle.uvm.HookManager.NETWORK_SETTINGS_CHANGE, this.networkHookCallback); + UvmContextFactory.context().hookManager().registerCallback(com.untangle.uvm.HookManager.WAN_BALANCER_CHANGE, this.wanBalancerHookCallback); + UvmContextFactory.context().hookManager().registerCallback(com.untangle.uvm.HookManager.REQUEST_ACTIVE_WAN_ID, this.activeWanIdHookCallback); if (WanFailoverApp.execManager == null) { WanFailoverApp.execManager = UvmContextFactory.context().createExecManager(); @@ -241,6 +248,8 @@ protected synchronized void preStart(boolean isPermanentTransition) protected synchronized void postStop(boolean isPermanentTransition) { UvmContextFactory.context().hookManager().unregisterCallback(com.untangle.uvm.HookManager.NETWORK_SETTINGS_CHANGE, this.networkHookCallback); + UvmContextFactory.context().hookManager().unregisterCallback(com.untangle.uvm.HookManager.WAN_BALANCER_CHANGE, this.wanBalancerHookCallback); + UvmContextFactory.context().hookManager().unregisterCallback(com.untangle.uvm.HookManager.REQUEST_ACTIVE_WAN_ID, this.activeWanIdHookCallback); if (this.wanFailoverTesterMonitor != null) { this.wanFailoverTesterMonitor.stop(); @@ -305,6 +314,83 @@ public void callback(Object... args) } } } + /** + * Called when WAN Balancer settings have changed + * Updates the active WAN interface ID based on the new balancer configuration + */ + public void wanBalancerSettingsEvent() + { + if (this.wanFailoverTesterMonitor != null) { + logger.info("Updating active WAN ID"); + this.wanFailoverTesterMonitor.updateActiveWanId(); + } + } + + /** + * Callback hook for changes to WAN Balancer settings + * Monitors WAN Balancer configuration changes and updates failover behavior accordingly + */ + private class WanFailoverWanBalancerHookCallback implements HookCallback + { + + /** + * Gets the name for the callback hook + * + * @return The name of the callback hook + */ + public String getName() + { + return "wan-failover-wan-balancer-settings-change-hook"; + } + + /** + * Callback handler invoked when WAN Balancer settings change + * Processes weight updates from WAN Balancer and triggers active WAN ID re-evaluation + * + * @param args + * The callback arguments - expects an int array of WAN weights as args[0] + * Empty arrays can be used to trigger re-evaluation without changing weights + */ + public void callback(Object... args) + { + if (args.length > 0 && args[0] instanceof int[]) { + int[] weights = (int[]) args[0]; + //set wanFailover wanbalancerweights as per wanBalancer passed argument + WanFailoverApp.wanBalancerWeights = weights; + logger.info("WAN Balancer weights updated (length= {} )", weights.length); + wanBalancerSettingsEvent(); + } + } + } + /** + * Callback hook for requesting the active WAN IP + * Responds to requests for the current active WAN interface by re-evaluating and returning the active WAN ID + */ + private class ActiveWanIdHookCallback implements HookCallback + { + + /** + * Gets the name for the callback hook + * + * @return The name of the callback hook + */ + public String getName() + { + return "request-active-wan-ip"; + } + + /** + * Callback handler invoked when active WAN IP is requested + * Triggers active WAN ID re-evaluation to determine the current active WAN interface + * + * @param args + * The callback arguments (not used - can be called with no arguments) + */ + public void callback(Object... args) + { + wanBalancerSettingsEvent(); + } + } /** * Used to compare wan failover events diff --git a/wan-failover/src/com/untangle/app/wan_failover/WanFailoverTesterMonitor.java b/wan-failover/src/com/untangle/app/wan_failover/WanFailoverTesterMonitor.java index 7da112fd87..236ba02c64 100644 --- a/wan-failover/src/com/untangle/app/wan_failover/WanFailoverTesterMonitor.java +++ b/wan-failover/src/com/untangle/app/wan_failover/WanFailoverTesterMonitor.java @@ -14,6 +14,9 @@ import com.untangle.uvm.UvmContextFactory; import com.untangle.uvm.ExecManagerResult; import com.untangle.uvm.network.InterfaceSettings; +import com.untangle.uvm.app.App; +import static com.untangle.uvm.app.License.WAN_BALANCER; +import static com.untangle.uvm.app.AppSettings.AppState.RUNNING; /** * The WanFailoverTesterMonitor is a daemon thread that launches and monitors @@ -109,18 +112,48 @@ public synchronized void wanStateChange(Integer interfaceId, boolean active) /** * Get active wan interface identifer and send to hook listeners. */ - private void updateActiveWanId() - { + protected void updateActiveWanId() { int activeWanId = 0; - for (activeWanId = 1; activeWanId < InterfaceSettings.MAX_INTERFACE_ID + 1; activeWanId++) { - if(wanStatusArray[activeWanId] == null){ - continue; + int maxWeight = -1; + + // Check if WAN Balancer is actually running before using weights + boolean wanBalancerRunning = false; + try { + App wanBalancerApp = UvmContextFactory.context().appManager().app(WAN_BALANCER); + if (wanBalancerApp != null && wanBalancerApp.getRunState() == RUNNING) { + wanBalancerRunning = true; } - if(wanStatusArray[activeWanId]){ - break; + } catch (Exception e) { + logger.warn("Unable to check WAN Balancer status: ", e.getMessage()); + } + + int[] weights = WanFailoverApp.wanBalancerWeights; + // Only use weights if WAN Balancer is running AND we have valid weights + if (wanBalancerRunning && weights != null && weights.length > 0) { + int length = Math.min(weights.length, InterfaceSettings.MAX_INTERFACE_ID + 1); + + for (int i = 1; i < length; i++) { + int weight = weights[i-1]; + + if (weight > 0 && wanStatusArray[i] != null && wanStatusArray[i]) { + if (weight > maxWeight) { + maxWeight = weight; + activeWanId = i; + } + } } } - UvmContextFactory.context().hookManager().callCallbacks( HookManager.WAN_FAILOVER_CHANGE, activeWanId ); + + // Fallback: If WAN Balancer is not running or no weighted active WAN found, use first active WAN + if (activeWanId == 0) { + activeWanId = java.util.stream.IntStream.rangeClosed(1, InterfaceSettings.MAX_INTERFACE_ID) + .filter(i -> wanStatusArray[i] != null && wanStatusArray[i]) + .findFirst() + .orElse(0); + } + + logger.info("Active WAN selected: {} (WAN Balancer running: {} )", activeWanId, wanBalancerRunning); + UvmContextFactory.context().hookManager().callCallbacks(HookManager.WAN_FAILOVER_CHANGE, activeWanId); } /**