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