diff --git a/api/src/main/java/com/cloud/agent/api/to/StaticNatRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/StaticNatRuleTO.java index 8064c301991..9c817f2debb 100644 --- a/api/src/main/java/com/cloud/agent/api/to/StaticNatRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/StaticNatRuleTO.java @@ -28,6 +28,7 @@ import com.cloud.network.rules.StaticNatRule; public class StaticNatRuleTO extends FirewallRuleTO { String dstIp; + boolean shouldApplyCrossNetworkSnat = false; protected StaticNatRuleTO() { } @@ -79,4 +80,12 @@ public class StaticNatRuleTO extends FirewallRuleTO { return dstIp; } + public boolean isShouldApplyCrossNetworkSnat() { + return shouldApplyCrossNetworkSnat; + } + + public void setShouldApplyCrossNetworkSnat(boolean shouldApplyCrossNetworkSnat) { + this.shouldApplyCrossNetworkSnat = shouldApplyCrossNetworkSnat; + } + } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java index 0439b168a9d..af0332b70f6 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java @@ -39,7 +39,8 @@ public class SetStaticNatRulesConfigItem extends AbstractConfigItemFacade { final LinkedList rules = new LinkedList<>(); for (final StaticNatRuleTO rule : command.getRules()) { - final StaticNatRule staticNatRule = new StaticNatRule(rule.revoked(), rule.getProtocol(), rule.getSrcIp(), rule.getStringSrcPortRange(), rule.getDstIp()); + final StaticNatRule staticNatRule = new StaticNatRule(rule.revoked(), rule.getProtocol(), rule.getSrcIp(), + rule.getStringSrcPortRange(), rule.getDstIp(), rule.isShouldApplyCrossNetworkSnat()); rules.add(staticNatRule); } final StaticNatRules staticNatRules = new StaticNatRules(rules); diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java index a375a913b28..09f8daab0f7 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java @@ -25,18 +25,25 @@ public class StaticNatRule { private String sourceIpAddress; private String sourcePortRange; private String destinationIpAddress; + private boolean shouldApplyCrossNetworkSnat = false; public StaticNatRule() { // Empty constructor for (de)serialization } public StaticNatRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress) { + this(revoke, protocol, sourceIpAddress, sourcePortRange, destinationIpAddress, false); + } + + public StaticNatRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, + String destinationIpAddress, boolean shouldApplyCrossNetworkSnat) { super(); this.revoke = revoke; this.protocol = protocol; this.sourceIpAddress = sourceIpAddress; this.sourcePortRange = sourcePortRange; this.destinationIpAddress = destinationIpAddress; + this.shouldApplyCrossNetworkSnat = shouldApplyCrossNetworkSnat; } public boolean isRevoke() { @@ -79,4 +86,12 @@ public class StaticNatRule { this.destinationIpAddress = destinationIpAddress; } + public boolean isShouldApplyCrossNetworkSnat() { + return shouldApplyCrossNetworkSnat; + } + + public void setShouldApplyCrossNetworkSnat(boolean shouldApplyCrossNetworkSnat) { + this.shouldApplyCrossNetworkSnat = shouldApplyCrossNetworkSnat; + } + } diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index c6296682bb0..b01083e6b07 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -446,6 +446,7 @@ public class CommandSetupHelper { for (final StaticNatRule rule : rules) { final IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId()); final StaticNatRuleTO ruleTO = new StaticNatRuleTO(rule, null, sourceIp.getAddress().addr(), rule.getDestIpAddress()); + ruleTO.setShouldApplyCrossNetworkSnat(requiresReturnPathSnat(guestNetworkId, rule.getDestIpAddress())); rulesTO.add(ruleTO); } } @@ -459,6 +460,25 @@ public class CommandSetupHelper { cmds.addCommand(cmd); } + /** + * This method determines whether to use network-wide SNAT or NIC aware SNAT + * @param networkId + * @param destinationIp + * @return + */ + private boolean requiresReturnPathSnat(final long networkId, final String destinationIp) { + if (!VirtualNetworkApplianceManager.NicSnatEnabled.value()) { + return false; + } + + final NicVO destinationNic = _nicDao.findByIp4AddressAndNetworkId(destinationIp, networkId); + if (destinationNic == null) { + logger.debug("Unable to find destination NIC for ip [{}] in network [{}], assuming default NIC.", destinationIp, networkId); + return false; + } + return !destinationNic.isDefaultNic(); + } + public void createApplyFirewallRulesCommands(final List rules, final VirtualRouter router, final Commands cmds, final long guestNetworkId) { final List rulesTO = new ArrayList<>(); String systemRule = null; @@ -697,6 +717,7 @@ public class CommandSetupHelper { final IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId()); final StaticNatRuleTO ruleTO = new StaticNatRuleTO(0, sourceIp.getAddress().addr(), null, null, rule.getDestIpAddress(), null, null, null, rule.isForRevoke(), false); + ruleTO.setShouldApplyCrossNetworkSnat(requiresReturnPathSnat(guestNetworkId, rule.getDestIpAddress())); rulesTO.add(ruleTO); } } diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java index 8ef77d3fb32..58607b4a106 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java @@ -50,6 +50,7 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA String RouterHealthChecksResultFetchIntervalCK = "router.health.checks.results.fetch.interval"; String RouterHealthChecksFailuresToRecreateVrCK = "router.health.checks.failures.to.recreate.vr"; String RemoveControlIpOnStopCK = "systemvm.release.control.ip.on.stop"; + String UseNicAwareSnat = "network.nic.snat.enabled"; ConfigKey RouterTemplateXen = new ConfigKey<>(String.class, RouterTemplateXenCK, "Advanced", "SystemVM Template (XenServer)", "Name of the default router template on Xenserver.", true, ConfigKey.Scope.Zone, null); @@ -131,6 +132,17 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA ConfigKey RemoveControlIpOnStop = new ConfigKey<>(Boolean.class, RemoveControlIpOnStopCK, "Advanced", "true", "on stopping routers and system VMs the IP will be released to preserve IPv4 space.", true, ConfigKey.Scope.Zone, null); + ConfigKey NicSnatEnabled = new ConfigKey<>(Boolean.class, + UseNicAwareSnat, + "Advanced", + "false", + "When enabled, Virtual Router overwrites the SNAT source IP in POSTROUTING for traffic exiting " + + "non-default NICs. The SNAT source is selected based on the egress interface to preserve return path " + + "consistency in multi-NIC configurations.", + true, + ConfigKey.Scope.Global, + null); + int DEFAULT_ROUTER_VM_RAMSIZE = 256; // 256M int DEFAULT_ROUTER_CPU_MHZ = 500; // 500 MHz boolean USE_POD_VLAN = false; diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index dd65719ad03..f2207a34299 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -3382,7 +3382,8 @@ Configurable, StateListener