Use the new "libvirt-nat" zone for native NAT networks.
The "libvirt" zone is still in use, but only to handle DHCP packets.
Those won't be dispatched to the "libvirt-zone" because said zone is
using sources (instead of interfaces). DHCP packets don't have a valid
source address.
The use of "libvirt" zone is necessary due to a Linux < 5.5 limitation
in which nftables iifname cannot be matched in postrouting hook (i.e.
masquerade). In the future, when we can assume Linux 5.5+, we can
further improve this by attaching interfaces to the "libvirt-nat" zone
instead of using sources. Thus making the "libvirt" zone unnecessary.
Signed-off-by: Eric Garver <eric(a)garver.life>
---
src/network/bridge_driver_linux.c | 55 +++++++++++++++++++++++++++----
1 file changed, 48 insertions(+), 7 deletions(-)
diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c
index 42f098ff1f9b..d6c7d378f5f7 100644
--- a/src/network/bridge_driver_linux.c
+++ b/src/network/bridge_driver_linux.c
@@ -140,7 +140,10 @@ networkUseOnlyFirewallDRules(void)
return false;
if (virFirewallDPolicyExists("libvirt-routed-out") &&
- virFirewallDZoneExists("libvirt-routed")) {
+ virFirewallDZoneExists("libvirt-routed") &&
+ virFirewallDPolicyExists("libvirt-nat-out") &&
+ virFirewallDZoneExists("libvirt-nat") &&
+ virFirewallDZoneExists("libvirt")) {
return true;
}
@@ -825,6 +828,48 @@ networkAddOnlyFirewallDRules(virNetworkDef *def)
if (def->forward.type == VIR_NETWORK_FORWARD_ROUTE) {
if (virFirewallDInterfaceSetZone(def->bridge, "libvirt-routed") <
0)
return -1;
+ } else if (def->forward.type == VIR_NETWORK_FORWARD_NAT) {
+ virNetworkIPDef *ipdef;
+ size_t i;
+
+ /* The initial DHCP packets won't be dispatched to the
+ * libvirt-nat zone because they don't yet have an IP address.
+ * The libvirt-nat zone needs to use sources instead of
+ * interfaces because kernels < 5.5 do not support matching
+ * iifname in postrouting.
+ *
+ * As a workaround, add the interface to the libvirt zone. This
+ * will allow dhcp to function. Afterwards packets will go to
+ * the libvirt-nat zone.
+ */
+ if (virFirewallDInterfaceSetZone(def->bridge, "libvirt") < 0)
+ return -1;
+
+ for (i = 0;
+ (ipdef = virNetworkDefGetIPByIndex(def, AF_UNSPEC, i));
+ i++) {
+ int prefix = virNetworkIPDefPrefix(ipdef);
+ g_autofree char *networkstr = NULL;
+
+ if (!(networkstr = virSocketAddrFormatWithPrefix(&ipdef->address,
prefix, true)))
+ return -1;
+
+ if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET) ||
+ def->forward.natIPv6 == VIR_TRISTATE_BOOL_YES) {
+ if (virFirewallDSourceSetZone(networkstr, "libvirt-nat") <
0)
+ return -1;
+ if (def->forward.natIPv6 == VIR_TRISTATE_BOOL_YES) {
+ const char *rich_rules[] = {"rule family=ipv6
masquerade"};
+ size_t rich_rules_count = sizeof(rich_rules) /
sizeof(rich_rules[0]);
+
+ if (virFirewallDApplyPolicyRichRules("libvirt-nat-out",
rich_rules, rich_rules_count) < 0)
+ return -1;
+ }
+ } else if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) {
+ if (virFirewallDSourceSetZone(networkstr, "libvirt-routed")
< 0)
+ return -1;
+ }
+ }
}
return 0;
@@ -890,10 +935,8 @@ int networkAddFirewallRules(virNetworkDef *def)
virNetworkIPDef *ipdef;
g_autoptr(virFirewall) fw = virFirewallNew();
- if (!def->bridgeZone && networkUseOnlyFirewallDRules() &&
- def->forward.type == VIR_NETWORK_FORWARD_ROUTE) {
+ if (!def->bridgeZone && networkUseOnlyFirewallDRules())
return networkAddOnlyFirewallDRules(def);
- }
if (virOnce(&createdOnce, networkSetupPrivateChains) < 0)
return -1;
@@ -968,10 +1011,8 @@ void networkRemoveFirewallRules(virNetworkDef *def)
virNetworkIPDef *ipdef;
g_autoptr(virFirewall) fw = virFirewallNew();
- if (!def->bridgeZone && networkUseOnlyFirewallDRules() &&
- def->forward.type == VIR_NETWORK_FORWARD_ROUTE) {
+ if (!def->bridgeZone && networkUseOnlyFirewallDRules())
return;
- }
virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
networkRemoveChecksumFirewallRules(fw, def);
--
2.37.3