Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions ipsec-vpn/src/com/untangle/app/ipsec_vpn/IpsecVpnApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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
*
Expand Down
3 changes: 2 additions & 1 deletion uvm/api/com/untangle/uvm/HookManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
56 changes: 56 additions & 0 deletions wan-balancer/src/com/untangle/app/wan_balancer/WanBalancerApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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());
}

/**
Expand All @@ -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
*
Expand All @@ -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
*/
Expand Down
86 changes: 86 additions & 0 deletions wan-failover/src/com/untangle/app/wan_failover/WanFailoverApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down