summary refs log tree commit diff
path: root/src/core/ndisc
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/ndisc')
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.c35
-rw-r--r--src/core/ndisc/nm-ndisc-private.h2
-rw-r--r--src/core/ndisc/nm-ndisc.c152
-rw-r--r--src/core/ndisc/nm-ndisc.h27
4 files changed, 178 insertions, 38 deletions
diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c
index c19dcc91..69213da6 100644
--- a/src/core/ndisc/nm-lndp-ndisc.c
+++ b/src/core/ndisc/nm-lndp-ndisc.c
@@ -401,6 +401,30 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
         }
     }
 
+#if HAVE_CLAT
+    /* PREF64 */
+    ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREF64) {
+        NMNDiscPref64 pref64;
+
+        pref64 = (NMNDiscPref64) {
+            .prefix             = *ndp_msg_opt_pref64_prefix(msg, offset),
+            .plen               = ndp_msg_opt_pref64_prefix_length(msg, offset),
+            .gateway            = gateway.address,
+            .gateway_preference = gateway.preference,
+            .expiry_msec =
+                _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_pref64_lifetime(msg, offset)),
+            .gateway_expiry_msec = gateway.expiry_msec,
+        };
+
+        /* libndp should only return lengths defined in RFC 8781 */
+        nm_assert(NM_IN_SET(pref64.plen, 96, 64, 56, 48, 40, 32));
+
+        if (nm_ndisc_add_pref64(ndisc, &pref64, now_msec)) {
+            changed |= NM_NDISC_CONFIG_PREF64;
+        }
+    }
+#endif
+
     nm_ndisc_ra_received(ndisc, now_msec, changed);
     return 0;
 }
@@ -463,7 +487,8 @@ send_ra(NMNDisc *ndisc, GError **error)
     struct in6_addr         *addr;
     struct ndp_msg          *msg;
     guint                    i;
-    nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE);
+    nm_auto_str_buf NMStrBuf sbuf     = NM_STR_BUF_INIT(0, FALSE);
+    gint64                   now_msec = nm_utils_get_monotonic_timestamp_msec();
 
     errsv = ndp_msg_new(&msg, NDP_MSG_RA);
     if (errsv) {
@@ -507,13 +532,9 @@ send_ra(NMNDisc *ndisc, GError **error)
         prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK;
         prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
         prefix->nd_opt_pi_valid_time =
-            htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
-                                                 address->expiry_msec,
-                                                 TRUE));
+            htonl(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_msec, TRUE));
         prefix->nd_opt_pi_preferred_time =
-            htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
-                                                 address->expiry_preferred_msec,
-                                                 TRUE));
+            htonl(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_preferred_msec, TRUE));
         prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0];
         prefix->nd_opt_pi_prefix.s6_addr32[1] = address->address.s6_addr32[1];
         prefix->nd_opt_pi_prefix.s6_addr32[2] = 0;
diff --git a/src/core/ndisc/nm-ndisc-private.h b/src/core/ndisc/nm-ndisc-private.h
index 1479e566..90ec032d 100644
--- a/src/core/ndisc/nm-ndisc-private.h
+++ b/src/core/ndisc/nm-ndisc-private.h
@@ -14,6 +14,7 @@ struct _NMNDiscDataInternal {
     NMNDiscData public;
     GArray *gateways;
     GArray *addresses;
+    GArray *pref64;
     GArray *routes;
     GArray *dns_servers;
     GArray *dns_domains;
@@ -28,6 +29,7 @@ gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gi
 gboolean
 nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec);
 gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec);
+gboolean nm_ndisc_add_pref64(NMNDisc *ndisc, const NMNDiscPref64 *new_item, gint64 now_msec);
 gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec);
 gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec);
 
diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index 1a2bf480..2056b5f8 100644
--- a/src/core/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -34,6 +34,7 @@
 #define _SIZE_MAX_ROUTES      1000u
 #define _SIZE_MAX_DNS_SERVERS 64u
 #define _SIZE_MAX_DNS_DOMAINS 64u
+#define _SIZE_MAX_PREF64      8u
 
 /*****************************************************************************/
 
@@ -109,7 +110,8 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex        *multi_idx,
                       int                       ifindex,
                       const NMNDiscData        *rdata,
                       NMSettingIP6ConfigPrivacy ip6_privacy,
-                      NMUtilsIPv6IfaceId       *token)
+                      NMUtilsIPv6IfaceId       *token,
+                      const char               *network_id)
 {
     nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
     guint32                                 ifa_flags;
@@ -211,13 +213,21 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex        *multi_idx,
     for (i = 0; i < rdata->dns_domains_n; i++)
         nm_l3_config_data_add_search(l3cd, AF_INET6, rdata->dns_domains[i].domain);
 
+    if (rdata->pref64_n > 0) {
+        nm_l3_config_data_set_pref64(l3cd, rdata->pref64[0].prefix, rdata->pref64[0].plen);
+    } else {
+        nm_l3_config_data_set_pref64_valid(l3cd, FALSE);
+    }
+
     nm_l3_config_data_set_ndisc_hop_limit(l3cd, rdata->hop_limit);
     nm_l3_config_data_set_ndisc_reachable_time_msec(l3cd, rdata->reachable_time_ms);
     nm_l3_config_data_set_ndisc_retrans_timer_msec(l3cd, rdata->retrans_timer_ms);
 
-    nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu);
+    nm_l3_config_data_set_ip6_mtu_ra(l3cd, rdata->mtu);
     if (token)
         nm_l3_config_data_set_ip6_token(l3cd, *token);
+    if (network_id)
+        nm_l3_config_data_set_network_id(l3cd, network_id);
 
     return g_steal_pointer(&l3cd);
 }
@@ -416,6 +426,7 @@ _data_complete(NMNDiscDataInternal *data)
     _SET(data, gateways);
     _SET(data, addresses);
     _SET(data, routes);
+    _SET(data, pref64);
     _SET(data, dns_servers);
     _SET(data, dns_domains);
 #undef _SET
@@ -437,7 +448,8 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
                                  nm_l3cfg_get_ifindex(priv->config.l3cfg),
                                  rdata,
                                  priv->config.ip6_privacy,
-                                 priv->iid_is_token ? &priv->iid : NULL);
+                                 priv->iid_is_token ? &priv->iid : NULL,
+                                 priv->config.network_id);
     l3cd = nm_l3_config_data_seal(l3cd);
 
     if (!nm_l3_config_data_equal(priv->l3cd, l3cd))
@@ -761,6 +773,59 @@ nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec
 }
 
 gboolean
+nm_ndisc_add_pref64(NMNDisc *ndisc, const NMNDiscPref64 *new_item, gint64 now_msec)
+{
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    guint                i;
+    guint                insert_idx = G_MAXUINT;
+
+    for (i = 0; i < rdata->pref64->len;) {
+        NMNDiscPref64 *item = &nm_g_array_index(rdata->pref64, NMNDiscPref64, i);
+
+        if (item->plen == new_item->plen && IN6_ARE_ADDR_EQUAL(&item->prefix, &new_item->prefix)
+            && IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway)) {
+            if (new_item->expiry_msec <= now_msec) {
+                g_array_remove_index(rdata->pref64, i);
+                return TRUE;
+            }
+
+            if (item->gateway_preference != new_item->gateway_preference) {
+                g_array_remove_index(rdata->pref64, i);
+                continue;
+            }
+
+            item->gateway_expiry_msec = new_item->gateway_expiry_msec;
+
+            if (item->expiry_msec == new_item->expiry_msec)
+                return FALSE;
+
+            item->expiry_msec = new_item->expiry_msec;
+            return TRUE;
+        }
+
+        /* Put before less preferable gateways. */
+        if (_preference_to_priority(item->gateway_preference)
+                < _preference_to_priority(new_item->gateway_preference)
+            && insert_idx == G_MAXUINT)
+            insert_idx = i;
+
+        i++;
+    }
+
+    if (rdata->pref64->len >= _SIZE_MAX_PREF64)
+        return FALSE;
+
+    if (new_item->expiry_msec <= now_msec)
+        return FALSE;
+
+    g_array_insert_val(rdata->pref64,
+                       insert_idx == G_MAXUINT ? rdata->pref64->len : insert_idx,
+                       *new_item);
+
+    return TRUE;
+}
+
+gboolean
 nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec)
 {
     NMNDiscPrivate      *priv;
@@ -1059,7 +1124,8 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd)
     const NMPObject   *obj;
     guint              len;
     guint              i;
-    gint32             fake_now = NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000;
+    gint64             now_msec = nm_utils_get_monotonic_timestamp_msec();
+    gint32             now      = now_msec / 1000;
 
     nm_assert(NM_IS_NDISC(ndisc));
     nm_assert(nm_ndisc_get_node_type(ndisc) == NM_NDISC_NODE_TYPE_ROUTER);
@@ -1082,16 +1148,15 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd)
         lifetime = nmp_utils_lifetime_get(addr->timestamp,
                                           addr->lifetime,
                                           addr->preferred,
-                                          &fake_now,
+                                          &now,
                                           &preferred);
         if (!lifetime)
             continue;
 
         a = (NMNDiscAddress) {
-            .address     = addr->address,
-            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime),
-            .expiry_preferred_msec =
-                _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred),
+            .address               = addr->address,
+            .expiry_msec           = _nm_ndisc_lifetime_to_expiry(now_msec, lifetime),
+            .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred),
         };
 
         if (nm_ndisc_add_address(ndisc, &a, 0, FALSE))
@@ -1111,8 +1176,7 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd)
 
         n = (NMNDiscDNSServer) {
             .address     = a.addr6,
-            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
-                                                        NM_NDISC_ROUTER_LIFETIME),
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, NM_NDISC_ROUTER_LIFETIME),
         };
         if (nm_ndisc_add_dns_server(ndisc, &n, G_MININT64))
             changed = TRUE;
@@ -1127,8 +1191,7 @@ nm_ndisc_set_config(NMNDisc *ndisc, const NML3ConfigData *l3cd)
 
         n = (NMNDiscDNSDomain) {
             .domain      = (char *) strvarr[i],
-            .expiry_msec = _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
-                                                        NM_NDISC_ROUTER_LIFETIME),
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, NM_NDISC_ROUTER_LIFETIME),
         };
         if (nm_ndisc_add_dns_domain(ndisc, &n, G_MININT64))
             changed = TRUE;
@@ -1400,6 +1463,17 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
               nm_icmpv6_router_pref_to_string(route->preference, str_pref, sizeof(str_pref)),
               get_exp(str_exp, now_msec, route));
     }
+    for (i = 0; i < rdata->pref64->len; i++) {
+        const NMNDiscPref64 *pref64 = &nm_g_array_index(rdata->pref64, NMNDiscPref64, i);
+        char                 addrstr2[NM_INET_ADDRSTRLEN];
+
+        _LOGD("  pref64 %s/%u via %s exp %s",
+              nm_inet6_ntop(&pref64->prefix, addrstr),
+              pref64->plen,
+              nm_inet6_ntop(&pref64->gateway, addrstr2),
+              get_exp(str_exp, now_msec, pref64));
+    }
+
     for (i = 0; i < rdata->dns_servers->len; i++) {
         const NMNDiscDNSServer *dns_server =
             &nm_g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
@@ -1486,7 +1560,7 @@ clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint
     }
 
     if (i != j) {
-        *changed = NM_NDISC_CONFIG_ADDRESSES;
+        *changed |= NM_NDISC_CONFIG_ADDRESSES;
         g_array_set_size(rdata->addresses, j);
     }
 
@@ -1520,11 +1594,50 @@ clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64
         g_array_set_size(rdata->routes, j);
     }
 
-    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_ROUTES))
+    if (_array_set_size_max(rdata->routes, _SIZE_MAX_ROUTES))
         *changed |= NM_NDISC_CONFIG_ROUTES;
 }
 
 static void
+clean_pref64(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
+{
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscPref64       *arr;
+    guint                i;
+    guint                j;
+
+    if (rdata->pref64->len == 0)
+        return;
+
+    arr = &nm_g_array_first(rdata->pref64, NMNDiscPref64);
+
+    for (i = 0, j = 0; i < rdata->pref64->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)
+            || !expiry_next(now_msec,
+                            arr[i].gateway_expiry_msec,
+                            next_msec)) { /* no gateway no party */
+            if (i == 0) {
+                /* Emit the changed signal only when the first PREF64 expires,
+                 * because only the first item is exported into the l3cd. Changes
+                 * in other PREF64s are not relevant. */
+                *changed |= NM_NDISC_CONFIG_PREF64;
+            }
+            continue;
+        }
+
+        if (i != j)
+            arr[j] = arr[i];
+        j++;
+    }
+
+    if (i != j) {
+        g_array_set_size(rdata->pref64, j);
+    }
+
+    _array_set_size_max(rdata->pref64, _SIZE_MAX_PREF64);
+}
+
+static void
 clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
@@ -1550,7 +1663,7 @@ clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi
         g_array_set_size(rdata->dns_servers, j);
     }
 
-    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_SERVERS))
+    if (_array_set_size_max(rdata->dns_servers, _SIZE_MAX_DNS_SERVERS))
         *changed |= NM_NDISC_CONFIG_DNS_SERVERS;
 }
 
@@ -1580,12 +1693,12 @@ clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi
         j++;
     }
 
-    if (i != 0) {
+    if (i != j) {
         *changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
         g_array_set_size(rdata->dns_domains, j);
     }
 
-    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_DOMAINS))
+    if (_array_set_size_max(rdata->dns_domains, _SIZE_MAX_DNS_DOMAINS))
         *changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
 }
 
@@ -1600,6 +1713,7 @@ check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
     clean_gateways(ndisc, now_msec, &changed, &next_msec);
     clean_addresses(ndisc, now_msec, &changed, &next_msec);
     clean_routes(ndisc, now_msec, &changed, &next_msec);
+    clean_pref64(ndisc, now_msec, &changed, &next_msec);
     clean_dns_servers(ndisc, now_msec, &changed, &next_msec);
     clean_dns_domains(ndisc, now_msec, &changed, &next_msec);
 
@@ -1919,6 +2033,7 @@ nm_ndisc_init(NMNDisc *ndisc)
     rdata->gateways    = g_array_new(FALSE, FALSE, sizeof(NMNDiscGateway));
     rdata->addresses   = g_array_new(FALSE, FALSE, sizeof(NMNDiscAddress));
     rdata->routes      = g_array_new(FALSE, FALSE, sizeof(NMNDiscRoute));
+    rdata->pref64      = g_array_new(FALSE, FALSE, sizeof(NMNDiscPref64));
     rdata->dns_servers = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSServer));
     rdata->dns_domains = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSDomain));
     g_array_set_clear_func(rdata->dns_domains, dns_domain_free);
@@ -1951,6 +2066,7 @@ finalize(GObject *object)
     g_array_unref(rdata->gateways);
     g_array_unref(rdata->addresses);
     g_array_unref(rdata->routes);
+    g_array_unref(rdata->pref64);
     g_array_unref(rdata->dns_servers);
     g_array_unref(rdata->dns_domains);
 
diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h
index 8f1a12a2..cdd9a676 100644
--- a/src/core/ndisc/nm-ndisc.h
+++ b/src/core/ndisc/nm-ndisc.h
@@ -50,18 +50,6 @@ const char *nm_ndisc_dhcp_level_to_string(NMNDiscDHCPLevel level);
  * unit of it is milliseconds. But of course, infinity has not really a unit. */
 #define NM_NDISC_EXPIRY_INFINITY G_MAXINT64
 
-/* in common cases, the expiry_msec tracks the timestamp in nm_utils_get_monotonic_timestamp_mses()
- * timestamp when the item expires.
- *
- * When we configure an NMNDiscAddress to be announced via the router advertisement,
- * then that address does not have a fixed expiry point in time, instead, the expiry
- * really contains the lifetime from the moment when we send the router advertisement.
- * In that case, the expiry_msec is more a "lifetime" that starts counting at timestamp
- * zero.
- *
- * The unit is milliseconds (but of course, the timestamp is zero, so it doesn't really matter). */
-#define NM_NDISC_EXPIRY_BASE_TIMESTAMP ((gint64) 0)
-
 static inline gint64
 _nm_ndisc_lifetime_to_expiry(gint64 now_msec, guint32 lifetime)
 {
@@ -119,6 +107,15 @@ typedef struct _NMNDiscRoute {
     bool               duplicate : 1;
 } NMNDiscRoute;
 
+typedef struct _NMNDiscPref64 {
+    struct in6_addr    prefix;
+    struct in6_addr    gateway;
+    gint64             expiry_msec;
+    gint64             gateway_expiry_msec;
+    NMIcmpv6RouterPref gateway_preference;
+    guint8             plen;
+} NMNDiscPref64;
+
 typedef struct {
     struct in6_addr address;
     gint64          expiry_msec;
@@ -141,6 +138,7 @@ typedef enum {
     NM_NDISC_CONFIG_MTU            = 1 << 7,
     NM_NDISC_CONFIG_REACHABLE_TIME = 1 << 8,
     NM_NDISC_CONFIG_RETRANS_TIMER  = 1 << 9,
+    NM_NDISC_CONFIG_PREF64         = 1 << 10,
 } NMNDiscConfigMap;
 
 typedef enum {
@@ -188,12 +186,14 @@ typedef struct {
     guint gateways_n;
     guint addresses_n;
     guint routes_n;
+    guint pref64_n;
     guint dns_servers_n;
     guint dns_domains_n;
 
     const NMNDiscGateway   *gateways;
     const NMNDiscAddress   *addresses;
     const NMNDiscRoute     *routes;
+    const NMNDiscPref64    *pref64;
     const NMNDiscDNSServer *dns_servers;
     const NMNDiscDNSDomain *dns_domains;
 } NMNDiscData;
@@ -282,6 +282,7 @@ struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex        *multi_id
                                               int                       ifindex,
                                               const NMNDiscData        *rdata,
                                               NMSettingIP6ConfigPrivacy ip6_privacy,
-                                              NMUtilsIPv6IfaceId       *token);
+                                              NMUtilsIPv6IfaceId       *token,
+                                              const char               *network_id);
 
 #endif /* __NETWORKMANAGER_NDISC_H__ */