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@xxxxxxxxxxx> --- 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