diff options
author | Lubomir Rintel <lkundrak@v3.sk> | 2016-10-31 23:31:14 +0100 |
---|---|---|
committer | Lubomir Rintel <lkundrak@v3.sk> | 2016-11-01 10:03:12 +0100 |
commit | 57a4055dc0f78fac033eb0ce614dcd80b97468e0 (patch) | |
tree | cd3cfe374e92478af6c3fa7eb3cb7a47ea8c80e0 | |
parent | 7a839370387010fd853bc85f8e853e51c5b21536 (diff) | |
download | NetworkManager-lr/ipv6-nd.tar.gz |
policy: delegate IPv6 prefixes to ipv6.method=shared connectionslr/ipv6-nd
-rw-r--r-- | src/nm-policy.c | 245 |
1 files changed, 244 insertions, 1 deletions
diff --git a/src/nm-policy.c b/src/nm-policy.c index 6993438cf0..e392e957a2 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,216 @@ 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); +} + +static void +clear_ip6_prefix_delegation (gpointer data) +{ + IP6PrefixDelegation *delegation = data; + + 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)) + 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) { + _LOGD (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; + + 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)) { + if (nm_device_needs_ip6_subnet (nm_active_connection_get_device (iter->data))) + ip6_subnet_from_delegation (delegation, iter->data); + } +} + +static void +device_ip6_subnet_needed (NMDevice *device, + gpointer user_data) +{ + NMPolicyPrivate *priv = user_data; + NMPolicy *self = _PRIV_TO_SELF (priv); + + if (!priv->default_device6) { + /* We request the prefixes when the default IPv6 device is set. */ + _LOGD (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); +} + +/*****************************************************************************/ + static NMDevice * get_best_ip4_device (NMPolicy *self, gboolean fully_activated) { @@ -565,6 +777,22 @@ update_ip6_dns (NMPolicy *self, NMDnsManager *dns_mgr) } 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 (nm_device_needs_ip6_subnet (device)) + ip6_subnet_from_device (self, priv->default_device6, device); + } +} + +static void update_ip6_routing (NMPolicy *self, gboolean force_update) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self); @@ -615,8 +843,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 +1506,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 +1625,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 +1665,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 +1693,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 +2125,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 +2247,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); |