At this point everything is already in place to make IPv6 happen, we just need to add a few rules, remove some checks for IPv4-only, and document the changes to the XML on the website. --- docs/formatnetwork.html.in | 35 +++++++-- src/network/bridge_driver.c | 186 ++++++++++++++++++++++++++++++++---------- 2 files changed, 169 insertions(+), 52 deletions(-) diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index 7a24518..b1b0485 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -85,8 +85,13 @@ </dd> <dt><code>forward</code></dt> <dd>Inclusion of the <code>forward</code> element indicates that - the virtual network is to be connected to the physical LAN. If - no attributes are set, NAT forwarding will be used for connectivity. + the virtual network is to be connected to the physical + LAN. the <code>mode</code> attribute determines the method of + forwarding; possible selections are 'nat' and 'route'. If mode + is not specified, NAT forwarding will be used for + connectivity. If a network has any IPv6 addresses defined, + even if <code>mode</code> is given as 'nat', the IPv6 traffic + will be forwarded using routing, since IPv6 has no concept of NAT. Firewall rules will allow forwarding to any other network device whether ethernet, wireless, dialup, or VPN. If the <code>dev</code> attribute is set, the firewall rules will restrict forwarding to the named @@ -118,21 +123,37 @@ <dl> <dt><code>ip</code></dt> <dd>The <code>address</code> attribute defines an IPv4 address in - dotted-decimal format, that will be configured on the bridge + dotted-decimal format, or an IPv6 address in standard + colon-separated hexadecimal format, that will be configured on + the bridge device associated with the virtual network. To the guests this - address will be their default route. The <code>netmask</code> + address will be their default route. For IPv4 addresses, the <code>netmask</code> attribute defines the significant bits of the network address, - again specified in dotted-decimal format. <span class="since">Since 0.3.0</span> + again specified in dotted-decimal format. For IPv6 addresses, + and as an alternate method for IPv4 addresses, you can specify + the significant bits of the network address with the <code>prefix</code> + attribute, which is an integer (for example, <code>netmask='255.255.255.0'</code> + could also be given as <code>prefix='24'</code>. The <code>family</code> + attribute is used to specify the type of address - 'ipv4' or 'ipv6'; if no + <code>family</code> is given, 'ipv4' is assumed. A network can have more than + one of each family of address defined, but only a single address can have a + <code>dhcp</code> or <code>tftp</code> element. <span class="since">Since 0.3.0; + IPv6, multiple addresses on a single network, <code>family</code>, and + <code>prefix</code> since 0.8.7</span> </dd><dt><code>tftp</code></dt><dd>Immediately within the <code>ip</code> element there is an optional <code>tftp</code> element. The presence of this element and of its attribute <code>root</code> enables TFTP services. The attribute specifies - the path to the root directory served via TFTP. + the path to the root directory served via TFTP. <code>tftp</code> is not + supported for IPv6 addresses, can only be specified on a single IPv4 address + per network. <span class="since">Since 0.7.1</span> </dd><dt><code>dhcp</code></dt><dd>Also within the <code>ip</code> element there is an optional <code>dhcp</code> element. The presence of this element enables DHCP services on the virtual network. It will further - contain one or more <code>range</code> elements. + contain one or more <code>range</code> elements. The + <code>dhcp</code> element is not supported for IPv6, and + is only supported on a single IP address per network for IPv4. <span class="since">Since 0.3.0</span> </dd> <dt><code>range</code></dt> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index e405429..665a13f 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -825,6 +825,65 @@ networkRemoveRoutingIptablesRules(struct network_driver *driver, } } +/* Add all once/network rules required for IPv6 (if any IPv6 addresses are defined) */ +static int +networkAddGeneralIp6tablesRules(struct network_driver *driver, + virNetworkObjPtr network) +{ + + if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) + return 0; + + /* Catch all rules to block forwarding to/from bridges */ + + if (iptablesAddForwardRejectOut(driver->iptables, AF_INET6, + network->def->bridge) < 0) { + networkReportError(VIR_ERR_SYSTEM_ERROR, + _("failed to add ip6tables rule to block outbound traffic from '%s'"), + network->def->bridge); + goto err1; + } + + if (iptablesAddForwardRejectIn(driver->iptables, AF_INET6, + network->def->bridge) < 0) { + networkReportError(VIR_ERR_SYSTEM_ERROR, + _("failed to add ip6tables rule to block inbound traffic to '%s'"), + network->def->bridge); + goto err2; + } + + /* Allow traffic between guests on the same bridge */ + if (iptablesAddForwardAllowCross(driver->iptables, AF_INET6, + network->def->bridge) < 0) { + networkReportError(VIR_ERR_SYSTEM_ERROR, + _("failed to add ip6tables rule to allow cross bridge traffic on '%s'"), + network->def->bridge); + goto err3; + } + + return 0; + + /* unwind in reverse order from the point of failure */ +err3: + iptablesRemoveForwardRejectIn(driver->iptables, AF_INET6, network->def->bridge); +err2: + iptablesRemoveForwardRejectOut(driver->iptables, AF_INET6, network->def->bridge); +err1: + return -1; +} + +static void +networkRemoveGeneralIp6tablesRules(struct network_driver *driver, + virNetworkObjPtr network) +{ + if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) + return; + + iptablesRemoveForwardAllowCross(driver->iptables, AF_INET6, network->def->bridge); + iptablesRemoveForwardRejectIn(driver->iptables, AF_INET6, network->def->bridge); + iptablesRemoveForwardRejectOut(driver->iptables, AF_INET6, network->def->bridge); +} + static int networkAddGeneralIptablesRules(struct network_driver *driver, virNetworkObjPtr network) @@ -927,6 +986,11 @@ networkAddGeneralIptablesRules(struct network_driver *driver, goto err8; } + /* add IPv6 general rules, if needed */ + if (networkAddGeneralIp6tablesRules(driver, network) < 0) { + goto err8; + } + return 0; /* unwind in reverse order from the point of failure */ @@ -957,6 +1021,8 @@ networkRemoveGeneralIptablesRules(struct network_driver *driver, int ii; virNetworkIpDefPtr ipv4def; + networkRemoveGeneralIp6tablesRules(driver, network); + for (ii = 0; (ipv4def = virNetworkDefGetIpByIndex(network->def, AF_INET, ii)); ii++) { @@ -985,12 +1051,18 @@ networkAddIpSpecificIptablesRules(struct network_driver *driver, virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { - if (network->def->forwardType == VIR_NETWORK_FORWARD_NAT && - networkAddMasqueradingIptablesRules(driver, network, ipdef) < 0) - return -1; - else if (network->def->forwardType == VIR_NETWORK_FORWARD_ROUTE && - networkAddRoutingIptablesRules(driver, network, ipdef) < 0) - return -1; + /* NB: in the case of IPv6, routing rules are added when the + * forward mode is NAT. This is because IPv6 has no NAT. + */ + + if (network->def->forwardType == VIR_NETWORK_FORWARD_NAT) { + if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET)) + return networkAddMasqueradingIptablesRules(driver, network, ipdef); + else if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6)) + return networkAddRoutingIptablesRules(driver, network, ipdef); + } else if (network->def->forwardType == VIR_NETWORK_FORWARD_ROUTE) { + return networkAddRoutingIptablesRules(driver, network, ipdef); + } return 0; } @@ -1000,10 +1072,14 @@ networkRemoveIpSpecificIptablesRules(struct network_driver *driver, virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { - if (network->def->forwardType == VIR_NETWORK_FORWARD_NAT) - networkRemoveMasqueradingIptablesRules(driver, network, ipdef); - else if (network->def->forwardType == VIR_NETWORK_FORWARD_ROUTE) + if (network->def->forwardType == VIR_NETWORK_FORWARD_NAT) { + if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET)) + networkRemoveMasqueradingIptablesRules(driver, network, ipdef); + else if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6)) + networkRemoveRoutingIptablesRules(driver, network, ipdef); + } else if (network->def->forwardType == VIR_NETWORK_FORWARD_ROUTE) { networkRemoveRoutingIptablesRules(driver, network, ipdef); + } } /* Add all rules for all ip addresses (and general rules) on a network */ @@ -1085,30 +1161,46 @@ networkEnableIpForwarding(void) #define SYSCTL_PATH "/proc/sys" -static int networkDisableIPV6(virNetworkObjPtr network) +static int +networkSetIPv6Sysctls(virNetworkObjPtr network) { char *field = NULL; int ret = -1; - if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/disable_ipv6", network->def->bridge) < 0) { - virReportOOMError(); - goto cleanup; - } + if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) { + /* Only set disable_ipv6 if there are no ipv6 addresses defined for + * the network. + */ + if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/disable_ipv6", + network->def->bridge) < 0) { + virReportOOMError(); + goto cleanup; + } - if (access(field, W_OK) < 0 && errno == ENOENT) { - VIR_DEBUG("ipv6 appears to already be disabled on %s", network->def->bridge); - ret = 0; - goto cleanup; - } + if (access(field, W_OK) < 0 && errno == ENOENT) { + VIR_DEBUG("ipv6 appears to already be disabled on %s", + network->def->bridge); + ret = 0; + goto cleanup; + } - if (virFileWriteStr(field, "1", 0) < 0) { - virReportSystemError(errno, - _("cannot enable %s"), field); - goto cleanup; + if (virFileWriteStr(field, "1", 0) < 0) { + virReportSystemError(errno, + _("cannot enable %s"), field); + goto cleanup; + } + VIR_FREE(field); } - VIR_FREE(field); - if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/accept_ra", network->def->bridge) < 0) { + /* The rest of the ipv6 sysctl tunables should always be set, + * whether or not we're using ipv6 on this bridge. + */ + + /* Prevent guests from hijacking the host network by sending out + * their own router advertisements. + */ + if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/accept_ra", + network->def->bridge) < 0) { virReportOOMError(); goto cleanup; } @@ -1120,7 +1212,11 @@ static int networkDisableIPV6(virNetworkObjPtr network) } VIR_FREE(field); - if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/autoconf", network->def->bridge) < 0) { + /* All interfaces used as a gateway (which is what this is, by + * definition), must always have autoconf=0. + */ + if (virAsprintf(&field, SYSCTL_PATH "/net/ipv6/conf/%s/autoconf", + network->def->bridge) < 0) { virReportOOMError(); goto cleanup; } @@ -1262,7 +1358,7 @@ static int networkStartNetworkDaemon(struct network_driver *driver, virNetworkObjPtr network) { - int ii, err, v4present = 0; + int ii, err, v4present = 0, v6present = 0; virErrorPtr save_err = NULL; virNetworkIpDefPtr ipdef; @@ -1301,8 +1397,10 @@ networkStartNetworkDaemon(struct network_driver *driver, goto err1; } - /* Disable IPv6 on the bridge */ - if (networkDisableIPV6(network) < 0) + /* Disable IPv6 on the bridge if there are no IPv6 addresses + * defined, and set other IPv6 sysctl tunables appropriately. + */ + if (networkSetIPv6Sysctls(network) < 0) goto err1; /* Add "once per network" rules */ @@ -1314,6 +1412,8 @@ networkStartNetworkDaemon(struct network_driver *driver, ii++) { if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET)) v4present = 1; + if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6)) + v6present = 1; /* Add the IP address/netmask to the bridge */ if (networkAddAddrToBridge(driver, network, ipdef) < 0) { @@ -1708,9 +1808,7 @@ static virNetworkPtr networkDefine(virConnectPtr conn, const char *xml) { goto cleanup; } - /* we only support dhcp on one IPv4 address per defined network, and currently - * don't support IPv6. - */ + /* We only support dhcp on one IPv4 address per defined network */ for (ii = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii)); ii++) { @@ -1724,14 +1822,6 @@ static virNetworkPtr networkDefine(virConnectPtr conn, const char *xml) { ipv4def = ipdef; } } - } else { - /* we currently only support IPv4 */ - networkReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported address family '%s' (%d) in network definition"), - ipdef->family ? ipdef->family : "unspecified", - VIR_SOCKET_FAMILY(&ipdef->address)); - goto cleanup; - } } if (ipv4def) { @@ -1756,7 +1846,8 @@ cleanup: static int networkUndefine(virNetworkPtr net) { struct network_driver *driver = net->conn->networkPrivateData; virNetworkObjPtr network; - virNetworkIpDefPtr ipv4def; + virNetworkIpDefPtr ipdef; + int v4present = 0, v6present = 0; int ret = -1, ii; networkDriverLock(driver); @@ -1781,12 +1872,17 @@ static int networkUndefine(virNetworkPtr net) { /* we only support dhcp on one IPv4 address per defined network */ for (ii = 0; - (ipv4def = virNetworkDefGetIpByIndex(network->def, AF_INET, ii)); + (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, ii)); ii++) { - if (ipv4def->nranges || ipv4def->nhosts) - break; + if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET)) { + if (ipdef->nranges || ipdef->nhosts) + v4present = 1; + } else if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6)) { + v6present = 1; + } } - if (ipv4def) { + + if (v4present) { dnsmasqContext *dctx = dnsmasqContextNew(network->def->name, DNSMASQ_STATE_DIR); if (dctx == NULL) goto cleanup; -- 1.7.3.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list