]> xenbits.xensource.com Git - libvirt.git/commitdiff
Turn on IPv6 support in the bridge_driver.c virtual network driver
authorLaine Stump <laine@laine.org>
Thu, 16 Dec 2010 20:50:01 +0000 (15:50 -0500)
committerLaine Stump <laine@laine.org>
Thu, 23 Dec 2010 20:54:46 +0000 (15:54 -0500)
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
src/network/bridge_driver.c

index 7a245186b47fba7b6ff8869112ee4e225963563b..b1b04859ba2bfb2a8ab513bd258ebf87f99fd113 100644 (file)
       </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
     <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>
index 0c37eb6101990143de6ac4f01153e270f91813b7..07791f152a2fad8c8fa5f85b8ac676c389646a50 100644 (file)
@@ -824,6 +824,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)
@@ -926,9 +985,16 @@ networkAddGeneralIptablesRules(struct network_driver *driver,
         goto err8;
     }
 
+    /* add IPv6 general rules, if needed */
+    if (networkAddGeneralIp6tablesRules(driver, network) < 0) {
+        goto err9;
+    }
+
     return 0;
 
     /* unwind in reverse order from the point of failure */
+err9:
+    iptablesRemoveForwardAllowCross(driver->iptables, AF_INET, network->def->bridge);
 err8:
     iptablesRemoveForwardRejectIn(driver->iptables, AF_INET, network->def->bridge);
 err7:
@@ -956,6 +1022,8 @@ networkRemoveGeneralIptablesRules(struct network_driver *driver,
     int ii;
     virNetworkIpDefPtr ipv4def;
 
+    networkRemoveGeneralIp6tablesRules(driver, network);
+
     for (ii = 0;
          (ipv4def = virNetworkDefGetIpByIndex(network->def, AF_INET, ii));
          ii++) {
@@ -984,13 +1052,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;
-    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;
 }
 
@@ -999,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 */
@@ -1084,30 +1161,47 @@ 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 write to %s to disable IPv6 on bridge %s"),
+                                 field, network->def->bridge);
+            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;
     }
@@ -1119,7 +1213,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 +1360,7 @@ networkStartNetworkDaemon(struct network_driver *driver,
                           virNetworkObjPtr network)
 {
     int ii, err;
-    bool v4present = false;
+    bool v4present = false, v6present = false;
     virErrorPtr save_err = NULL;
     virNetworkIpDefPtr ipdef;
 
@@ -1301,8 +1399,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 +1414,8 @@ networkStartNetworkDaemon(struct network_driver *driver,
          ii++) {
         if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET))
             v4present = true;
+        if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6))
+            v6present = true;
 
         /* Add the IP address/netmask to the bridge */
         if (networkAddAddrToBridge(driver, network, ipdef) < 0) {
@@ -1708,9 +1810,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 +1824,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 +1848,8 @@ cleanup:
 static int networkUndefine(virNetworkPtr net) {
     struct network_driver *driver = net->conn->networkPrivateData;
     virNetworkObjPtr network;
-    virNetworkIpDefPtr ipv4def;
+    virNetworkIpDefPtr ipdef;
+    bool dhcp_present = false, v6present = false;
     int ret = -1, ii;
 
     networkDriverLock(driver);
@@ -1781,12 +1874,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)
+                dhcp_present = true;
+        } else if (VIR_SOCKET_IS_FAMILY(&ipdef->address, AF_INET6)) {
+            v6present = true;
+        }
     }
-    if (ipv4def) {
+
+    if (dhcp_present) {
         dnsmasqContext *dctx = dnsmasqContextNew(network->def->name, DNSMASQ_STATE_DIR);
         if (dctx == NULL)
             goto cleanup;