This commit is contained in:
Manoj Kumar 2026-07-04 15:35:35 +01:00 committed by GitHub
commit 9c293ee8f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 75 additions and 5 deletions

View File

@ -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;
}
}

View File

@ -39,7 +39,8 @@ public class SetStaticNatRulesConfigItem extends AbstractConfigItemFacade {
final LinkedList<StaticNatRule> 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);

View File

@ -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;
}
}

View File

@ -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<? extends FirewallRule> rules, final VirtualRouter router, final Commands cmds, final long guestNetworkId) {
final List<FirewallRuleTO> 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);
}
}

View File

@ -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<String> 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<Boolean> 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<Boolean> 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;

View File

@ -3382,7 +3382,8 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
ExposeDnsAndBootpServer,
RouterLogrotateFrequency,
RemoveControlIpOnStop,
VirtualRouterUserData
VirtualRouterUserData,
NicSnatEnabled
};
}

View File

@ -1448,7 +1448,7 @@ class CsForwardingRules(CsDataBag):
)
fw4 = "-j SNAT --to-source %s -A POSTROUTING -s %s -d %s/32 -o %s -p %s -m %s --dport %s" % \
(
self.getGuestIp(),
self.getGuestIpByIp(rule['internal_ip']),
self.getNetworkByIp(rule['internal_ip']),
rule['internal_ip'],
internal_fwinterface,
@ -1563,11 +1563,20 @@ class CsForwardingRules(CsDataBag):
self.fw.append(["filter", "",
"-A FORWARD -i %s -o eth0 -d %s -m state --state NEW -j ACCEPT " % (device, rule["internal_ip"])])
# Configure the hairpin snat
self.fw.append(["nat", "front", "-A POSTROUTING -s %s -d %s -j SNAT -o %s --to-source %s" %
# Configure the hairpin snat for default nic or nic-aware snat for non-default
apply_cross_network_snat = rule.get("should_apply_cross_network_snat", False)
if apply_cross_network_snat:
internal_device = self.getDeviceByIp(rule["internal_ip"])
internal_vr_ip = self.getGuestIpByIp(rule["internal_ip"])
if internal_device and internal_vr_ip and internal_device != device:
self.fw.append(["nat", "front",
"-A POSTROUTING -o %s -d %s/32 -j SNAT --to-source %s" % (internal_device, rule["internal_ip"], internal_vr_ip)])
else:
self.fw.append(["nat", "front", "-A POSTROUTING -s %s -d %s -j SNAT -o %s --to-source %s" %
(self.getNetworkByIp(rule['internal_ip']), rule["internal_ip"], self.getDeviceByIp(rule["internal_ip"]), self.getGuestIpByIp(rule["internal_ip"]))])
class IpTablesExecutor:
config = None

View File

@ -27,6 +27,8 @@ def merge(dbag, rules):
newrule = dict()
newrule["public_ip"] = source_ip
newrule["internal_ip"] = destination_ip
if "should_apply_cross_network_snat" in rule:
newrule["should_apply_cross_network_snat"] = rule["should_apply_cross_network_snat"]
if rules["type"] == "staticnatrules":
newrule["type"] = "staticnat"