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-01 10:03:12 +0100
commit57a4055dc0f78fac033eb0ce614dcd80b97468e0 (patch)
treecd3cfe374e92478af6c3fa7eb3cb7a47ea8c80e0
parent7a839370387010fd853bc85f8e853e51c5b21536 (diff)
downloadNetworkManager-lr/ipv6-nd.tar.gz
policy: delegate IPv6 prefixes to ipv6.method=shared connectionslr/ipv6-nd
-rw-r--r--src/nm-policy.c245
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);