summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2016-10-31 23:31:14 +0100
committerLubomir Rintel <lkundrak@v3.sk>2016-11-09 17:23:32 +0100
commitec12fcf6bf4f306d426271c286db3c28b7025ba4 (patch)
treefe2212e8bf341cb1f72fb7837a5a1288da6708ba
parent479ad5065e96abe4588c52c9588faf046cc182d1 (diff)
downloadNetworkManager-lr/ipv6-sharing.tar.gz
policy: delegate IPv6 configuration to ipv6.method=shared connectionslr/ipv6-sharing
The policy listens to signals from shared devices that need subnets and requesting devices that provide prefixes. Whenever a subnet is needed, policy tries to obtain a subnets from all of default6 device's prefixes. When it fails to get any, it asks for more prefixes. This way we make it possible for the delegating router to either provide us with a /64 for each of our shared interfaces, or provide a larger prefix that we could subnet. The policy also updates the shared device's DNS information to keep it in sync with the best requesting device changes.
-rw-r--r--src/nm-policy.c282
1 files changed, 281 insertions, 1 deletions
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 6993438cf0..8be0dbcf7e 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -88,6 +88,8 @@ typedef struct {
char *orig_hostname; /* hostname at NM start time */
char *cur_hostname; /* hostname we want to assign */
gboolean hostname_changed; /* TRUE if NM ever set the hostname */
+
+ GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */
} NMPolicyPrivate;
struct _NMPolicy {
@@ -133,6 +135,236 @@ static void schedule_activate_all (NMPolicy *self);
/*****************************************************************************/
+typedef struct {
+ NMPlatformIP6Address prefix;
+ NMDevice *device; /* The requesting ("uplink") device */
+ guint64 next_subnet; /* Cache of the next subnet number to be
+ * assigned from this prefix */
+ GHashTable *subnets; /* ifindex -> NMPlatformIP6Address */
+} IP6PrefixDelegation;
+
+static void
+_clear_ip6_subnet (gpointer key, gpointer value, gpointer user_data)
+{
+ NMPlatformIP6Address *subnet = value;
+ NMDevice *device = nm_manager_get_device_by_ifindex (nm_manager_get (),
+ GPOINTER_TO_INT (key));
+
+ if (!device)
+ return;
+
+ /* We can not remove a subnet we already started announcing.
+ * Just un-prefer it. */
+ subnet->preferred = 0;
+ nm_device_use_ip6_subnet (device, subnet);
+ g_slice_free (NMPlatformIP6Address, subnet);
+}
+
+static void
+clear_ip6_prefix_delegation (gpointer data)
+{
+ IP6PrefixDelegation *delegation = data;
+
+ _LOGD (LOGD_IP6, "ipv6-pd: undelegating prefix %s/%d",
+ nm_utils_inet6_ntop (&delegation->prefix.address, NULL),
+ delegation->prefix.plen);
+
+ g_hash_table_foreach (delegation->subnets, _clear_ip6_subnet, NULL);
+ g_hash_table_destroy (delegation->subnets);
+}
+
+static void
+expire_ip6_delegations (NMPolicy *self)
+{
+ NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+ guint32 now = nm_utils_get_monotonic_timestamp_s ();
+ IP6PrefixDelegation *delegation = NULL;
+ int i;
+
+ for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+ delegation = &g_array_index (priv->ip6_prefix_delegations,
+ IP6PrefixDelegation, i);
+ if (delegation->prefix.timestamp + delegation->prefix.lifetime < now)
+ g_array_remove_index_fast (priv->ip6_prefix_delegations, i);
+ }
+}
+
+/*
+ * Try to obtain a new subnet for a particular active connection from given
+ * delegated prefix, possibly reusing the existing subnet.
+ * Return value of FALSE indicates no more subnets are available from
+ * this prefix (and other prefix should be used -- and requested if necessary).
+ */
+static gboolean
+ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device)
+{
+ NMPlatformIP6Address *subnet;
+ int ifindex = nm_device_get_ifindex (device);
+
+ subnet = g_hash_table_lookup (delegation->subnets, GINT_TO_POINTER (ifindex));
+ if (!subnet) {
+ /* Check for out-of-prefixes condition. */
+ if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) {
+ _LOGD (LOGD_IP6, "ipv6-pd: no more prefixes in %s/%d",
+ nm_utils_inet6_ntop (&delegation->prefix.address, NULL),
+ delegation->prefix.plen);
+ return FALSE;
+ }
+
+ /* Allocate a new subnet. */
+ subnet = g_slice_new0 (NMPlatformIP6Address);
+ g_hash_table_insert (delegation->subnets, GINT_TO_POINTER (ifindex), subnet);
+
+ subnet->plen = 64;
+ subnet->address.s6_addr32[0] = delegation->prefix.address.s6_addr32[0]
+ | htonl (delegation->next_subnet >> 32);
+ subnet->address.s6_addr32[1] = delegation->prefix.address.s6_addr32[1]
+ | htonl (delegation->next_subnet);
+
+ /* Out subnet pool management is pretty unsophisticated. We only add
+ * the subnets and index them by ifindex. That keeps the implementation
+ * simple and the dead entries make it easy to reuse the same subnet on
+ * subsequent activations. On the other hand they may waste the subnet
+ * space. */
+ delegation->next_subnet++;
+ }
+
+ subnet->timestamp = delegation->prefix.timestamp;
+ subnet->lifetime = delegation->prefix.lifetime;
+ subnet->preferred = delegation->prefix.preferred;
+
+ _LOGD (LOGD_IP6, "ipv6-pd: %s allocated from a /%d prefix on %s",
+ nm_utils_inet6_ntop (&subnet->address, NULL),
+ delegation->prefix.plen,
+ nm_device_get_iface (device));
+
+ nm_device_use_ip6_subnet (device, subnet);
+
+ return TRUE;
+}
+
+/*
+ * Try to obtain a subnet from each prefix delegated to given requesting
+ * ("uplink") device and assign it to the downlink device.
+ * Requests a new prefix if no subnet could be found.
+ */
+static void
+ip6_subnet_from_device (NMPolicy *self, NMDevice *from_device, NMDevice *device)
+{
+ NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+ IP6PrefixDelegation *delegation = NULL;
+ gboolean got_subnet = FALSE;
+ int have_prefixes = 0;
+ int i;
+
+ expire_ip6_delegations (self);
+
+ for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+ delegation = &g_array_index (priv->ip6_prefix_delegations,
+ IP6PrefixDelegation, i);
+
+ if (delegation->device != from_device)
+ continue;
+
+ if (ip6_subnet_from_delegation (delegation, device))
+ got_subnet = TRUE;
+ have_prefixes++;
+ }
+
+ if (!got_subnet) {
+ _LOGI (LOGD_IP6, "ipv6-pd: none of %d prefixes of %s can be shared on %s",
+ have_prefixes, nm_device_get_iface (from_device),
+ nm_device_get_iface (device));
+ nm_device_request_ip6_prefixes (from_device, have_prefixes + 1);
+ }
+}
+
+static void
+ip6_remove_device_prefix_delegations (NMPolicy *self, NMDevice *device)
+{
+ NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+ IP6PrefixDelegation *delegation = NULL;
+ int i;
+
+ for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+ delegation = &g_array_index (priv->ip6_prefix_delegations,
+ IP6PrefixDelegation, i);
+ if (delegation->device == device)
+ g_array_remove_index_fast (priv->ip6_prefix_delegations, i);
+ }
+}
+
+static void
+device_ip6_prefix_delegated (NMDevice *device,
+ NMPlatformIP6Address *prefix,
+ gpointer user_data)
+{
+ NMPolicyPrivate *priv = user_data;
+ NMPolicy *self = _PRIV_TO_SELF (priv);
+ IP6PrefixDelegation *delegation = NULL;
+ const GSList *connections, *iter;
+ int i;
+
+ _LOGI (LOGD_IP6, "ipv6-pd: received a prefix %s/%d from %s",
+ nm_utils_inet6_ntop (&prefix->address, NULL),
+ prefix->plen,
+ nm_device_get_iface (device));
+
+ expire_ip6_delegations (self);
+
+ for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+ /* Look for an already known prefix to update. */
+ delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+ if (IN6_ARE_ADDR_EQUAL (&delegation->prefix.address, &prefix->address))
+ break;
+ }
+
+ if (i == priv->ip6_prefix_delegations->len) {
+ /* Allocate a delegation delegation for new prefix. */
+ g_array_set_size (priv->ip6_prefix_delegations, i + 1);
+ delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+ delegation->subnets = g_hash_table_new (NULL, NULL);
+ delegation->next_subnet = 0;
+ }
+
+ delegation->device = device;
+ delegation->prefix = *prefix;
+
+ /* The newly activated connections are added to the list beginning,
+ * so traversing it from the beginning makes it likely for newly
+ * activated connections that have no subnet assigned to be served
+ * first. That is a simple yet fair policy, which is good. */
+ connections = nm_manager_get_active_connections (priv->manager);
+ for (iter = connections; iter; iter = g_slist_next (iter)) {
+ NMDevice *to_device = nm_active_connection_get_device (iter->data);
+
+ if (nm_device_needs_ip6_subnet (to_device))
+ ip6_subnet_from_delegation (delegation, to_device);
+ }
+}
+
+static void
+device_ip6_subnet_needed (NMDevice *device,
+ gpointer user_data)
+{
+ NMPolicyPrivate *priv = user_data;
+ NMPolicy *self = _PRIV_TO_SELF (priv);
+
+ _LOGD (LOGD_IP6, "ipv6-pd: %s needs a subnet",
+ nm_device_get_iface (device));
+
+ if (!priv->default_device6) {
+ /* We request the prefixes when the default IPv6 device is set. */
+ _LOGI (LOGD_IP6, "ipv6-pd: no device to obtain a subnet to share on %s from",
+ nm_device_get_iface (device));
+ return;
+ }
+ ip6_subnet_from_device (self, priv->default_device6, device);
+ nm_device_copy_ip6_dns_config (device, priv->default_device6);
+}
+
+/*****************************************************************************/
+
static NMDevice *
get_best_ip4_device (NMPolicy *self, gboolean fully_activated)
{
@@ -545,6 +777,21 @@ get_best_ip6_config (NMPolicy *self,
}
static void
+update_ip6_dns_delegation (NMPolicy *self)
+{
+ NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+ const GSList *connections, *iter;
+
+ connections = nm_manager_get_active_connections (priv->manager);
+ for (iter = connections; iter; iter = g_slist_next (iter)) {
+ NMDevice *device = nm_active_connection_get_device (iter->data);
+
+ if (device && nm_device_needs_ip6_subnet (device))
+ nm_device_copy_ip6_dns_config (device, priv->default_device6);
+ }
+}
+
+static void
update_ip6_dns (NMPolicy *self, NMDnsManager *dns_mgr)
{
NMIP6Config *ip6_config;
@@ -562,6 +809,24 @@ update_ip6_dns (NMPolicy *self, NMDnsManager *dns_mgr)
*/
nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, dns_type);
}
+
+ update_ip6_dns_delegation (self);
+}
+
+static void
+update_ip6_prefix_delegation (NMPolicy *self)
+{
+ NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+ const GSList *connections, *iter;
+
+ /* There's new default IPv6 connection, try to get a prefix for everyone. */
+ connections = nm_manager_get_active_connections (priv->manager);
+ for (iter = connections; iter; iter = g_slist_next (iter)) {
+ NMDevice *device = nm_active_connection_get_device (iter->data);
+
+ if (device && nm_device_needs_ip6_subnet (device))
+ ip6_subnet_from_device (self, priv->default_device6, device);
+ }
}
static void
@@ -615,8 +880,10 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
if (default_device6 == priv->default_device6)
return;
-
priv->default_device6 = default_device6;
+
+ update_ip6_prefix_delegation (self);
+
connection = nm_active_connection_get_applied_connection (best_ac);
_LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv6 routing and DNS",
nm_connection_get_id (connection), ip_iface);
@@ -1276,6 +1543,7 @@ device_state_changed (NMDevice *device,
}
}
}
+ ip6_remove_device_prefix_delegations (self, device);
break;
case NM_DEVICE_STATE_DISCONNECTED:
/* Reset retry counts for a device's connections when carrier on; if cable
@@ -1394,6 +1662,8 @@ device_ip6_config_changed (NMDevice *device,
nm_dns_manager_end_updates (priv->dns_manager, __func__);
}
+/*****************************************************************************/
+
static void
device_autoconnect_changed (NMDevice *device,
GParamSpec *pspec,
@@ -1432,6 +1702,8 @@ devices_list_register (NMPolicy *self, NMDevice *device)
g_signal_connect_after (device, NM_DEVICE_STATE_CHANGED, (GCallback) device_state_changed, priv);
g_signal_connect (device, NM_DEVICE_IP4_CONFIG_CHANGED, (GCallback) device_ip4_config_changed, priv);
g_signal_connect (device, NM_DEVICE_IP6_CONFIG_CHANGED, (GCallback) device_ip6_config_changed, priv);
+ g_signal_connect (device, NM_DEVICE_IP6_PREFIX_DELEGATED, (GCallback) device_ip6_prefix_delegated, priv);
+ g_signal_connect (device, NM_DEVICE_IP6_SUBNET_NEEDED, (GCallback) device_ip6_subnet_needed, priv);
g_signal_connect (device, "notify::" NM_DEVICE_AUTOCONNECT, (GCallback) device_autoconnect_changed, priv);
g_signal_connect (device, NM_DEVICE_RECHECK_AUTO_ACTIVATE, (GCallback) device_recheck_auto_activate, priv);
}
@@ -1458,6 +1730,10 @@ device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
NMPolicyPrivate *priv = user_data;
NMPolicy *self = _PRIV_TO_SELF (priv);
+ /* XXX is this needed? The delegations are cleaned up
+ * on transition to deactivated too. */
+ ip6_remove_device_prefix_delegations (self, device);
+
/* Clear any idle callbacks for this device */
clear_pending_activate_check (self, device);
@@ -1886,6 +2162,8 @@ nm_policy_init (NMPolicy *self)
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
priv->devices = g_hash_table_new (NULL, NULL);
+ priv->ip6_prefix_delegations = g_array_new (FALSE, FALSE, sizeof (IP6PrefixDelegation));
+ g_array_set_clear_func (priv->ip6_prefix_delegations, clear_ip6_prefix_delegation);
}
static void
@@ -2006,6 +2284,8 @@ dispose (GObject *object)
g_signal_handlers_disconnect_by_data (priv->manager, priv);
}
+ g_array_free (priv->ip6_prefix_delegations, TRUE);
+
nm_assert (NM_IS_MANAGER (priv->manager));
G_OBJECT_CLASS (nm_policy_parent_class)->dispose (object);