summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2010-08-10 21:32:20 -0500
committerDan Williams <dcbw@redhat.com>2010-08-10 21:37:47 -0500
commit17f630d433e20746226a53c08afedafa9596816a (patch)
treeeded13b9272dd2eadef8053045072ab40576efbb
parentd500eaa33b81ce30980330f475e7520980b81e8c (diff)
ip6: handle DHCPv6 initial routing better
DHCPv6 doesn't really use broadcast; instead clients use reserved multicast addresses to talk to the server. ff02::1:2 (link scope) and ff05::1:3 (site scope) are used. This means the routing table has to have a route that can handle outgoing traffic to these addresses, which is ff00::/8. The kernel sometimes adds one for us, so we need to (a) make sure we don't tear that route down, and (b) that if it's not there before we start DHCPv6, that we add it. Otherwise dhclient complains about not being able to send outgoing traffic from it's send_packet6() function with "no route to host". It will then use an expired lease, which causes NM to assign that leases IP address to the interface, whcih causes the kernel to assign the required ff00::/8 route, and then dhclient performs a renew (since the expired lease has expired of course) and then everything works out in the end. But the latency sucks. So make DHCPv6 faster by ensuring that dhclient has the routes it needs before we start the DHCP session.
-rw-r--r--src/nm-device.c14
-rw-r--r--src/nm-system.c145
-rw-r--r--src/nm-system.h10
3 files changed, 135 insertions, 34 deletions
diff --git a/src/nm-device.c b/src/nm-device.c
index e291c2f33c..dbbd3266c7 100644
--- a/src/nm-device.c
+++ b/src/nm-device.c
@@ -1579,6 +1579,8 @@ dhcp6_start (NMDevice *self,
NMSettingConnection *s_con;
const char *uuid;
const char *ip_iface;
+ const struct in6_addr dest = { { { 0xFF,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
+ int err;
if (!connection) {
NMActRequest *req;
@@ -1599,6 +1601,18 @@ dhcp6_start (NMDevice *self,
g_object_unref (priv->dhcp6_config);
priv->dhcp6_config = nm_dhcp6_config_new ();
+ /* DHCPv6 communicates with the DHCPv6 server via two multicast addresses,
+ * ff02::1:2 (link-scope) and ff05::1:3 (site-scope). Make sure we have
+ * a multicast route (ff00::/8) for client <-> server communication.
+ */
+ err = nm_system_set_ip6_route (priv->ip_iface ? priv->ip_ifindex : priv->ifindex,
+ &dest, 8, NULL, 256, 0, RTPROT_BOOT, RT_TABLE_LOCAL, NULL);
+ if (err) {
+ nm_log_err (LOGD_DEVICE | LOGD_IP6,
+ "(%s): failed to add IPv6 multicast route: %s",
+ priv->ip_iface ? priv->ip_iface : priv->iface, nl_geterror ());
+ }
+
s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
g_assert (s_con);
uuid = nm_setting_connection_get_uuid (s_con);
diff --git a/src/nm-system.c b/src/nm-system.c
index 87e83a571b..24c31c2772 100644
--- a/src/nm-system.c
+++ b/src/nm-system.c
@@ -481,32 +481,34 @@ nm_system_apply_ip4_config (const char *iface,
return TRUE;
}
-static struct rtnl_route *
-nm_system_device_set_ip6_route (const char *iface,
- const struct in6_addr *ip6_dest,
- guint32 ip6_prefix,
- const struct in6_addr *ip6_gateway,
- guint32 metric,
- int mss)
+int
+nm_system_set_ip6_route (int ifindex,
+ const struct in6_addr *ip6_dest,
+ guint32 ip6_prefix,
+ const struct in6_addr *ip6_gateway,
+ guint32 metric,
+ int mss,
+ int protocol,
+ int table,
+ struct rtnl_route **out_route)
{
struct nl_handle *nlh;
struct rtnl_route *route;
struct nl_addr *dest_addr;
struct nl_addr *gw_addr = NULL;
- int err, iface_idx;
+ int err = 0;
- nlh = nm_netlink_get_default_handle ();
- g_return_val_if_fail (nlh != NULL, NULL);
+ g_return_val_if_fail (ifindex >= 0, -1);
- iface_idx = nm_netlink_iface_to_index (iface);
- g_return_val_if_fail (iface_idx >= 0, NULL);
+ nlh = nm_netlink_get_default_handle ();
+ g_return_val_if_fail (nlh != NULL, -1);
- route = create_route (iface_idx, mss);
- g_return_val_if_fail (route != NULL, NULL);
+ route = create_route (ifindex, mss);
+ g_return_val_if_fail (route != NULL, -1);
/* Destination */
dest_addr = nl_addr_build (AF_INET6, (struct in6_addr *) ip6_dest, sizeof (*ip6_dest));
- g_return_val_if_fail (dest_addr != NULL, NULL);
+ g_return_val_if_fail (dest_addr != NULL, -1);
nl_addr_set_prefixlen (dest_addr, (int) ip6_prefix);
rtnl_route_set_dst (route, dest_addr);
@@ -521,7 +523,7 @@ nm_system_device_set_ip6_route (const char *iface,
} else {
nm_log_warn (LOGD_DEVICE | LOGD_IP6, "Invalid gateway");
rtnl_route_put (route);
- return NULL;
+ return -1;
}
}
@@ -529,13 +531,19 @@ nm_system_device_set_ip6_route (const char *iface,
if (metric)
rtnl_route_set_prio (route, metric);
+ if (protocol)
+ rtnl_route_set_protocol (route, protocol);
+
+ if (table)
+ rtnl_route_set_table (route, table);
+
/* Add the route */
err = rtnl_route_add (nlh, route, 0);
if (err == -ESRCH && ip6_gateway) {
/* Gateway might be over a bridge; try adding a route to gateway first */
struct rtnl_route *route2;
- route2 = create_route (iface_idx, mss);
+ route2 = create_route (ifindex, mss);
if (route2) {
/* Add route to gateway over bridge */
rtnl_route_set_dst (route2, gw_addr);
@@ -553,15 +561,12 @@ nm_system_device_set_ip6_route (const char *iface,
if (gw_addr)
nl_addr_put (gw_addr);
- if (err) {
- nm_log_err (LOGD_DEVICE | LOGD_IP6,
- "(%s): failed to set IPv6 route: %s",
- iface, nl_geterror ());
+ if (out_route)
+ *out_route = route;
+ else
rtnl_route_put (route);
- route = NULL;
- }
- return route;
+ return err;
}
static gboolean
@@ -618,24 +623,33 @@ nm_system_apply_ip6_config (const char *iface,
}
if (flags & NM_IP6_COMPARE_FLAG_ROUTES) {
+ int ifindex = nm_netlink_iface_to_index (iface);
+
for (i = 0; i < nm_ip6_config_get_num_routes (config); i++) {
NMIP6Route *route = nm_ip6_config_get_route (config, i);
- struct rtnl_route *tmp;
+ int err;
/* Don't add the route if it doesn't have a gateway and the connection
* is never supposed to be the default connection.
*/
if ( nm_ip6_config_get_never_default (config)
- && IN6_IS_ADDR_UNSPECIFIED(nm_ip6_route_get_dest (route)))
+ && IN6_IS_ADDR_UNSPECIFIED (nm_ip6_route_get_dest (route)))
continue;
- tmp = nm_system_device_set_ip6_route (iface,
- nm_ip6_route_get_dest (route),
- nm_ip6_route_get_prefix (route),
- nm_ip6_route_get_next_hop (route),
- nm_ip6_route_get_metric (route),
- nm_ip6_config_get_mss (config));
- rtnl_route_put (tmp);
+ err = nm_system_set_ip6_route (ifindex,
+ nm_ip6_route_get_dest (route),
+ nm_ip6_route_get_prefix (route),
+ nm_ip6_route_get_next_hop (route),
+ nm_ip6_route_get_metric (route),
+ nm_ip6_config_get_mss (config),
+ RTPROT_UNSPEC,
+ RT_TABLE_UNSPEC,
+ NULL);
+ if (err) {
+ nm_log_err (LOGD_DEVICE | LOGD_IP6,
+ "(%s): failed to set IPv6 route: %s",
+ iface, nl_geterror ());
+ }
}
}
@@ -1225,6 +1239,45 @@ foreach_route (void (*callback)(struct nl_object *, gpointer),
nl_cache_free (route_cache);
}
+static void
+dump_route (struct rtnl_route *route)
+{
+ char buf6[INET6_ADDRSTRLEN];
+ char buf4[INET_ADDRSTRLEN];
+ struct nl_addr *nl;
+ struct in6_addr *addr6 = NULL;
+ struct in_addr *addr4 = NULL;
+ int prefixlen = 0;
+ const char *sf = "UNSPEC";
+ int family = rtnl_route_get_family (route);
+
+ memset (buf6, 0, sizeof (buf6));
+ memset (buf4, 0, sizeof (buf4));
+ nl = rtnl_route_get_dst (route);
+ if (nl) {
+ if (rtnl_route_get_family (route) == AF_INET) {
+ addr4 = nl_addr_get_binary_addr (nl);
+ if (addr4)
+ inet_ntop (AF_INET, addr4, &buf4[0], sizeof (buf4));
+ } else if (rtnl_route_get_family (route) == AF_INET6) {
+ addr6 = nl_addr_get_binary_addr (nl);
+ if (addr6)
+ inet_ntop (AF_INET6, addr6, &buf6[0], sizeof (buf6));
+ }
+ prefixlen = nl_addr_get_prefixlen (nl);
+ }
+
+ if (family == AF_INET)
+ sf = "INET";
+ else if (family == AF_INET6)
+ sf = "INET6";
+
+ nm_log_dbg (LOGD_IP4 | LOGD_IP6, " route idx %d family %s (%d) addr %s/%d",
+ rtnl_route_get_oif (route),
+ sf, family,
+ strlen (buf4) ? buf4 : (strlen (buf6) ? buf6 : "<unknown>"),
+ prefixlen);
+}
typedef struct {
const char *iface;
@@ -1238,6 +1291,10 @@ check_one_route (struct nl_object *object, void *user_data)
RouteCheckData *data = (RouteCheckData *) user_data;
struct rtnl_route *route = (struct rtnl_route *) object;
int err;
+ guint32 log_level = LOGD_IP4 | LOGD_IP6;
+
+ if (nm_logging_level_enabled (LOGL_DEBUG))
+ dump_route (route);
/* Delete all routes from this interface */
if (rtnl_route_get_oif (route) != data->iface_idx)
@@ -1259,11 +1316,19 @@ check_one_route (struct nl_object *object, void *user_data)
addr = nl_addr_get_binary_addr (nl);
if (addr) {
- if (IN6_IS_ADDR_LINKLOCAL (addr) || IN6_IS_ADDR_MC_LINKLOCAL (addr))
+ if ( IN6_IS_ADDR_LINKLOCAL (addr)
+ || IN6_IS_ADDR_MC_LINKLOCAL (addr)
+ || (IN6_IS_ADDR_MULTICAST (addr) && (nl_addr_get_prefixlen (nl) == 8)))
return;
}
}
+ if (data->family == AF_INET)
+ log_level = LOGD_IP4;
+ else if (data->family == AF_INET6)
+ log_level = LOGD_IP6;
+ nm_log_dbg (log_level, " deleting route");
+
err = rtnl_route_del (nm_netlink_get_default_handle (), route, 0);
if (err < 0 && (err != -ERANGE)) {
nm_log_err (LOGD_DEVICE,
@@ -1275,6 +1340,8 @@ check_one_route (struct nl_object *object, void *user_data)
static void flush_routes (int ifindex, const char *iface, int family)
{
RouteCheckData check_data;
+ guint32 log_level = LOGD_IP4 | LOGD_IP6;
+ const char *sf = "UNSPEC";
g_return_if_fail (iface != NULL);
@@ -1286,6 +1353,16 @@ static void flush_routes (int ifindex, const char *iface, int family)
}
}
+ if (family == AF_INET) {
+ log_level = LOGD_IP4;
+ sf = "INET";
+ } else if (family == AF_INET6) {
+ log_level = LOGD_IP6;
+ sf = "INET6";
+ }
+ nm_log_dbg (log_level, "(%s): flushing routes ifindex %d family %s (%d)",
+ iface, ifindex, sf, family);
+
memset (&check_data, 0, sizeof (check_data));
check_data.iface = iface;
check_data.iface_idx = ifindex;
diff --git a/src/nm-system.h b/src/nm-system.h
index 29455cb4bf..8cb22d747a 100644
--- a/src/nm-system.h
+++ b/src/nm-system.h
@@ -66,6 +66,16 @@ gboolean nm_system_apply_ip4_config (const char *iface,
int priority,
NMIP4ConfigCompareFlags flags);
+int nm_system_set_ip6_route (int ifindex,
+ const struct in6_addr *ip6_dest,
+ guint32 ip6_prefix,
+ const struct in6_addr *ip6_gateway,
+ guint32 metric,
+ int mss,
+ int protocol,
+ int table,
+ struct rtnl_route **out_route);
+
gboolean nm_system_apply_ip6_config (const char *iface,
NMIP6Config *config,
int priority,