ndisc: track expiry of Router Advertisements in milliseconds

Elements of RAs have a lifetime. Previously we would track both
the timestamp (when we received the RA) and the lifetime.

However, we are mainly interested in the expiry time. So tracking the
expiry in form of timestamp and lifetime is redundant and cumbersome
to use.

Consider also the cases nm_ndisc_add_address() were we mangle the expiry.
In that case, the timestamp becomes meaningless or it's not clear what
the timestamp should be.

Also, there are no real cases where we actually need the receive timestamp.
Note that when we convert the times to NMPlatformIP6Address, we again need
to synthesize a base time stamp. But here too, it's NMPlatformIP6Address
fault of doing this pointless split of timestamp and lifetime.

While at it, increase the precision to milliseconds. As we receive
lifetimes with seconds precision, one might think that seconds precision
is enough for tracking the timeouts. However it just leads to ugly
uncertainty about rounding, when we can track times with sufficient
precision without downside. For example, before configuring an
address in kernel, we also need to calculate a remaining lifetime
with a lower precision. By having the exact values, we can do so
more accurately. At least, in theory. Of course NMPlatformIP6Address
itself has only precision of seconds, we already loose the information
before. However, NMNDisc no longer has that problem.
This commit is contained in:
Thomas Haller 2021-01-21 18:19:15 +01:00
parent 03c6d8280c
commit 4c2035347e
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
9 changed files with 910 additions and 666 deletions

View file

@ -5148,26 +5148,26 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
static void static void
ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self) ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
{ {
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gint32 now; gs_unref_array GArray *addresses = NULL;
GArray * addresses, *dns_servers, *dns_domains; gs_unref_array GArray *dns_servers = NULL;
guint len, i; gs_unref_array GArray * dns_domains = NULL;
guint len;
guint i;
const NMDedupMultiHeadEntry *head_entry; const NMDedupMultiHeadEntry *head_entry;
NMDedupMultiIter ipconf_iter; NMDedupMultiIter ipconf_iter;
if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER) if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
return; return;
now = nm_utils_get_monotonic_timestamp_sec();
head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6); head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6);
addresses = addresses =
g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0); g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0);
nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) { nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj); const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj);
NMNDiscAddress * ndisc_addr; NMNDiscAddress * ndisc_addr;
guint32 lifetime, preferred; guint32 lifetime;
gint32 base; guint32 preferred;
if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address)) if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
continue; continue;
@ -5178,31 +5178,21 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
if (addr->plen != 64) if (addr->plen != 64)
continue; continue;
/* resolve the timestamps relative to a new base.
*
* Note that for convenience, platform @addr might have timestamp and/or
* lifetime unset. We don't allow that flexibility for ndisc and require
* well defined timestamps. */
if (addr->timestamp) {
nm_assert(addr->timestamp < G_MAXINT32);
base = addr->timestamp;
} else
base = now;
lifetime = nm_utils_lifetime_get(addr->timestamp, lifetime = nm_utils_lifetime_get(addr->timestamp,
addr->lifetime, addr->lifetime,
addr->preferred, addr->preferred,
base, NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
&preferred); &preferred);
if (!lifetime) if (!lifetime)
continue; continue;
g_array_set_size(addresses, addresses->len + 1); g_array_set_size(addresses, addresses->len + 1);
ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1); ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
ndisc_addr->address = addr->address; ndisc_addr->address = addr->address;
ndisc_addr->timestamp = base; ndisc_addr->expiry_msec =
ndisc_addr->lifetime = lifetime; _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime);
ndisc_addr->preferred = preferred; ndisc_addr->expiry_preferred_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred);
} }
len = nm_ip6_config_get_num_nameservers(priv->ip_config_6); len = nm_ip6_config_get_num_nameservers(priv->ip_config_6);
@ -5212,10 +5202,10 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i); const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i);
NMNDiscDNSServer * ndisc_nameserver; NMNDiscDNSServer * ndisc_nameserver;
ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i); ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i);
ndisc_nameserver->address = *nameserver; ndisc_nameserver->address = *nameserver;
ndisc_nameserver->timestamp = now; ndisc_nameserver->expiry_msec =
ndisc_nameserver->lifetime = NM_NDISC_ROUTER_LIFETIME; _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
} }
len = nm_ip6_config_get_num_searches(priv->ip_config_6); len = nm_ip6_config_get_num_searches(priv->ip_config_6);
@ -5225,16 +5215,13 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
const char * search = nm_ip6_config_get_search(priv->ip_config_6, i); const char * search = nm_ip6_config_get_search(priv->ip_config_6, i);
NMNDiscDNSDomain *ndisc_search; NMNDiscDNSDomain *ndisc_search;
ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i); ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
ndisc_search->domain = (char *) search; ndisc_search->domain = (char *) search;
ndisc_search->timestamp = now; ndisc_search->expiry_msec =
ndisc_search->lifetime = NM_NDISC_ROUTER_LIFETIME; _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
} }
nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains); nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains);
g_array_unref(addresses);
g_array_unref(dns_servers);
g_array_unref(dns_domains);
} }
static void static void

View file

@ -30,11 +30,10 @@ typedef struct {
typedef struct { typedef struct {
struct in6_addr network; struct in6_addr network;
int plen;
struct in6_addr gateway; struct in6_addr gateway;
guint32 timestamp; gint64 expiry_msec;
guint32 lifetime; gint64 expiry_preferred_msec;
guint32 preferred; int plen;
NMIcmpv6RouterPref preference; NMIcmpv6RouterPref preference;
} FakePrefix; } FakePrefix;
@ -128,8 +127,7 @@ void
nm_fake_ndisc_add_gateway(NMFakeNDisc * self, nm_fake_ndisc_add_gateway(NMFakeNDisc * self,
guint ra_id, guint ra_id,
const char * addr, const char * addr,
guint32 timestamp, gint64 expiry_msec,
guint32 lifetime,
NMIcmpv6RouterPref preference) NMIcmpv6RouterPref preference)
{ {
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@ -137,12 +135,12 @@ nm_fake_ndisc_add_gateway(NMFakeNDisc * self,
NMNDiscGateway * gw; NMNDiscGateway * gw;
g_assert(ra); g_assert(ra);
g_array_set_size(ra->gateways, ra->gateways->len + 1);
gw = &g_array_index(ra->gateways, NMNDiscGateway, ra->gateways->len - 1); gw = nm_g_array_append_new(ra->gateways, NMNDiscGateway);
g_assert(inet_pton(AF_INET6, addr, &gw->address) == 1); if (inet_pton(AF_INET6, addr, &gw->address) != 1)
gw->timestamp = timestamp; g_assert_not_reached();
gw->lifetime = lifetime; gw->expiry_msec = expiry_msec;
gw->preference = preference; gw->preference = preference;
} }
void void
@ -151,9 +149,8 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
const char * network, const char * network,
guint plen, guint plen,
const char * gateway, const char * gateway,
guint32 timestamp, gint64 expiry_msec,
guint32 lifetime, gint64 expiry_preferred_msec,
guint32 preferred,
NMIcmpv6RouterPref preference) NMIcmpv6RouterPref preference)
{ {
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@ -161,54 +158,52 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
FakePrefix * prefix; FakePrefix * prefix;
g_assert(ra); g_assert(ra);
g_array_set_size(ra->prefixes, ra->prefixes->len + 1);
prefix = &g_array_index(ra->prefixes, FakePrefix, ra->prefixes->len - 1); prefix = nm_g_array_append_new(ra->prefixes, FakePrefix);
memset(prefix, 0, sizeof(*prefix)); *prefix = (FakePrefix){
g_assert(inet_pton(AF_INET6, network, &prefix->network) == 1); .plen = plen,
g_assert(inet_pton(AF_INET6, gateway, &prefix->gateway) == 1); .expiry_msec = expiry_msec,
prefix->plen = plen; .expiry_preferred_msec = expiry_preferred_msec,
prefix->timestamp = timestamp; .preference = preference,
prefix->lifetime = lifetime; };
prefix->preferred = preferred; if (inet_pton(AF_INET6, network, &prefix->network) != 1)
prefix->preference = preference; g_assert_not_reached();
if (inet_pton(AF_INET6, gateway, &prefix->gateway) != 1)
g_assert_not_reached();
} }
void void
nm_fake_ndisc_add_dns_server(NMFakeNDisc *self, nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
guint ra_id, guint ra_id,
const char * address, const char * address,
guint32 timestamp, gint64 expiry_msec)
guint32 lifetime)
{ {
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
FakeRa * ra = find_ra(priv->ras, ra_id); FakeRa * ra = find_ra(priv->ras, ra_id);
NMNDiscDNSServer * dns; NMNDiscDNSServer * dns;
g_assert(ra); g_assert(ra);
g_array_set_size(ra->dns_servers, ra->dns_servers->len + 1);
dns = &g_array_index(ra->dns_servers, NMNDiscDNSServer, ra->dns_servers->len - 1); dns = nm_g_array_append_new(ra->dns_servers, NMNDiscDNSServer);
g_assert(inet_pton(AF_INET6, address, &dns->address) == 1);
dns->timestamp = timestamp; dns->expiry_msec = expiry_msec;
dns->lifetime = lifetime; if (inet_pton(AF_INET6, address, &dns->address) != 1)
g_assert_not_reached();
} }
void void
nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, guint ra_id, const char *domain, gint64 expiry_msec)
guint ra_id,
const char * domain,
guint32 timestamp,
guint32 lifetime)
{ {
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self); NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
FakeRa * ra = find_ra(priv->ras, ra_id); FakeRa * ra = find_ra(priv->ras, ra_id);
NMNDiscDNSDomain * dns; NMNDiscDNSDomain * dns;
g_assert(ra); g_assert(ra);
g_array_set_size(ra->dns_domains, ra->dns_domains->len + 1);
dns = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, ra->dns_domains->len - 1); dns = nm_g_array_append_new(ra->dns_domains, NMNDiscDNSDomain);
dns->domain = g_strdup(domain);
dns->timestamp = timestamp; dns->domain = g_strdup(domain);
dns->lifetime = lifetime; dns->expiry_msec = expiry_msec;
} }
gboolean gboolean
@ -230,13 +225,13 @@ send_rs(NMNDisc *ndisc, GError **error)
static gboolean static gboolean
receive_ra(gpointer user_data) receive_ra(gpointer user_data)
{ {
NMFakeNDisc * self = user_data; NMFakeNDisc * self = user_data;
NMFakeNDiscPrivate * priv = NM_FAKE_NDISC_GET_PRIVATE(self); NMFakeNDiscPrivate * priv = NM_FAKE_NDISC_GET_PRIVATE(self);
NMNDisc * ndisc = NM_NDISC(self); NMNDisc * ndisc = NM_NDISC(self);
NMNDiscDataInternal *rdata = ndisc->rdata; NMNDiscDataInternal *rdata = ndisc->rdata;
FakeRa * ra = priv->ras->data; FakeRa * ra = priv->ras->data;
NMNDiscConfigMap changed = 0; NMNDiscConfigMap changed = 0;
gint32 now = nm_utils_get_monotonic_timestamp_sec(); const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint i; guint i;
NMNDiscDHCPLevel dhcp_level; NMNDiscDHCPLevel dhcp_level;
@ -251,53 +246,51 @@ receive_ra(gpointer user_data)
} }
for (i = 0; i < ra->gateways->len; i++) { for (i = 0; i < ra->gateways->len; i++) {
NMNDiscGateway *item = &g_array_index(ra->gateways, NMNDiscGateway, i); const NMNDiscGateway *item = &g_array_index(ra->gateways, NMNDiscGateway, i);
if (nm_ndisc_add_gateway(ndisc, item)) if (nm_ndisc_add_gateway(ndisc, item, now_msec))
changed |= NM_NDISC_CONFIG_GATEWAYS; changed |= NM_NDISC_CONFIG_GATEWAYS;
} }
for (i = 0; i < ra->prefixes->len; i++) { for (i = 0; i < ra->prefixes->len; i++) {
FakePrefix * item = &g_array_index(ra->prefixes, FakePrefix, i); FakePrefix * item = &g_array_index(ra->prefixes, FakePrefix, i);
NMNDiscRoute route = { const NMNDiscRoute route = {
.network = item->network, .network = item->network,
.plen = item->plen, .plen = item->plen,
.gateway = item->gateway, .gateway = item->gateway,
.timestamp = item->timestamp, .expiry_msec = item->expiry_msec,
.lifetime = item->lifetime, .preference = item->preference,
.preference = item->preference,
}; };
g_assert(route.plen > 0 && route.plen <= 128); g_assert(route.plen > 0 && route.plen <= 128);
if (nm_ndisc_add_route(ndisc, &route)) if (nm_ndisc_add_route(ndisc, &route, now_msec))
changed |= NM_NDISC_CONFIG_ROUTES; changed |= NM_NDISC_CONFIG_ROUTES;
if (item->plen == 64) { if (item->plen == 64) {
NMNDiscAddress address = { const NMNDiscAddress address = {
.address = item->network, .address = item->network,
.timestamp = item->timestamp, .expiry_msec = item->expiry_msec,
.lifetime = item->lifetime, .expiry_preferred_msec = item->expiry_preferred_msec,
.preferred = item->preferred, .dad_counter = 0,
.dad_counter = 0,
}; };
if (nm_ndisc_complete_and_add_address(ndisc, &address, now)) if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec))
changed |= NM_NDISC_CONFIG_ADDRESSES; changed |= NM_NDISC_CONFIG_ADDRESSES;
} }
} }
for (i = 0; i < ra->dns_servers->len; i++) { for (i = 0; i < ra->dns_servers->len; i++) {
NMNDiscDNSServer *item = &g_array_index(ra->dns_servers, NMNDiscDNSServer, i); const NMNDiscDNSServer *item = &g_array_index(ra->dns_servers, NMNDiscDNSServer, i);
if (nm_ndisc_add_dns_server(ndisc, item)) if (nm_ndisc_add_dns_server(ndisc, item, now_msec))
changed |= NM_NDISC_CONFIG_DNS_SERVERS; changed |= NM_NDISC_CONFIG_DNS_SERVERS;
} }
for (i = 0; i < ra->dns_domains->len; i++) { for (i = 0; i < ra->dns_domains->len; i++) {
NMNDiscDNSDomain *item = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, i); const NMNDiscDNSDomain *item = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, i);
if (nm_ndisc_add_dns_domain(ndisc, item)) if (nm_ndisc_add_dns_domain(ndisc, item, now_msec))
changed |= NM_NDISC_CONFIG_DNS_DOMAINS; changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
} }
@ -314,7 +307,7 @@ receive_ra(gpointer user_data)
priv->ras = g_slist_remove(priv->ras, priv->ras->data); priv->ras = g_slist_remove(priv->ras, priv->ras->data);
fake_ra_free(ra); fake_ra_free(ra);
nm_ndisc_ra_received(NM_NDISC(self), now, changed); nm_ndisc_ra_received(NM_NDISC(self), now_msec, changed);
/* Schedule next RA */ /* Schedule next RA */
if (priv->ras) { if (priv->ras) {

View file

@ -35,8 +35,7 @@ guint nm_fake_ndisc_add_ra(NMFakeNDisc * self,
void nm_fake_ndisc_add_gateway(NMFakeNDisc * self, void nm_fake_ndisc_add_gateway(NMFakeNDisc * self,
guint ra_id, guint ra_id,
const char * addr, const char * addr,
guint32 timestamp, gint64 expiry_msec,
guint32 lifetime,
NMIcmpv6RouterPref preference); NMIcmpv6RouterPref preference);
void nm_fake_ndisc_add_prefix(NMFakeNDisc * self, void nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
@ -44,22 +43,19 @@ void nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
const char * network, const char * network,
guint plen, guint plen,
const char * gateway, const char * gateway,
guint32 timestamp, gint64 expiry_msec,
guint32 lifetime, gint64 expiry_preferred_msec,
guint32 preferred,
NMIcmpv6RouterPref preference); NMIcmpv6RouterPref preference);
void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self, void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
guint ra_id, guint ra_id,
const char * address, const char * address,
guint32 timestamp, gint64 expiry_msec);
guint32 lifetime);
void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
guint ra_id, guint ra_id,
const char * domain, const char * domain,
guint32 timestamp, gint64 expiry_msec);
guint32 lifetime);
void nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self); void nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self);

View file

@ -117,7 +117,7 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
NMNDiscConfigMap changed = 0; NMNDiscConfigMap changed = 0;
struct ndp_msgra * msgra = ndp_msgra(msg); struct ndp_msgra * msgra = ndp_msgra(msg);
struct in6_addr gateway_addr; struct in6_addr gateway_addr;
gint32 now = nm_utils_get_monotonic_timestamp_sec(); const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
int offset; int offset;
int hop_limit; int hop_limit;
guint32 val; guint32 val;
@ -133,7 +133,9 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
* single time when the configuration is finished and updates can * single time when the configuration is finished and updates can
* come at any time. * come at any time.
*/ */
_LOGD("received router advertisement at %d", (int) now); _LOGD("received router advertisement at timestamp %" G_GINT64_FORMAT ".%03d seconds",
now_msec / 1000,
(int) (now_msec % 1000));
gateway_addr = *ndp_msg_addrto(msg); gateway_addr = *ndp_msg_addrto(msg);
if (IN6_IS_ADDR_UNSPECIFIED(&gateway_addr)) if (IN6_IS_ADDR_UNSPECIFIED(&gateway_addr))
@ -175,13 +177,18 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
*/ */
{ {
const NMNDiscGateway gateway = { const NMNDiscGateway gateway = {
.address = gateway_addr, .address = gateway_addr,
.timestamp = now, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msgra_router_lifetime(msgra)),
.lifetime = ndp_msgra_router_lifetime(msgra), .preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)),
.preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)),
}; };
if (nm_ndisc_add_gateway(ndisc, &gateway)) /* https://tools.ietf.org/html/rfc2461#section-4.2
* > A Lifetime of 0 indicates that the router is not a
* > default router and SHOULD NOT appear on the default
* > router list.
* We handle that by tracking a gateway that expires right now. */
if (nm_ndisc_add_gateway(ndisc, &gateway, now_msec))
changed |= NM_NDISC_CONFIG_GATEWAYS; changed |= NM_NDISC_CONFIG_GATEWAYS;
} }
@ -204,64 +211,71 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
if (ndp_msg_opt_prefix_flag_on_link(msg, offset)) { if (ndp_msg_opt_prefix_flag_on_link(msg, offset)) {
const NMNDiscRoute route = { const NMNDiscRoute route = {
.network = r_network, .network = r_network,
.plen = r_plen, .plen = r_plen,
.timestamp = now, .expiry_msec =
.lifetime = ndp_msg_opt_prefix_valid_time(msg, offset), _nm_ndisc_lifetime_to_expiry(now_msec,
ndp_msg_opt_prefix_valid_time(msg, offset)),
}; };
if (nm_ndisc_add_route(ndisc, &route)) if (nm_ndisc_add_route(ndisc, &route, now_msec))
changed |= NM_NDISC_CONFIG_ROUTES; changed |= NM_NDISC_CONFIG_ROUTES;
} }
/* Address */ /* Address */
if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) { if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) {
NMNDiscAddress address = { const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset);
.address = r_network, const guint32 preferred_time =
.timestamp = now, NM_MIN(ndp_msg_opt_prefix_preferred_time(msg, offset), valid_time);
.lifetime = ndp_msg_opt_prefix_valid_time(msg, offset), const NMNDiscAddress address = {
.preferred = ndp_msg_opt_prefix_preferred_time(msg, offset), .address = r_network,
.expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, valid_time),
.expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred_time),
}; };
if (address.preferred <= address.lifetime) { if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec))
if (nm_ndisc_complete_and_add_address(ndisc, &address, now)) changed |= NM_NDISC_CONFIG_ADDRESSES;
changed |= NM_NDISC_CONFIG_ADDRESSES;
}
} }
} }
ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) { ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) {
NMNDiscRoute route = { guint8 plen = ndp_msg_opt_route_prefix_len(msg, offset);
.gateway = gateway_addr, struct in6_addr network;
.plen = ndp_msg_opt_route_prefix_len(msg, offset),
.timestamp = now,
.lifetime = ndp_msg_opt_route_lifetime(msg, offset),
.preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)),
};
if (route.plen == 0 || route.plen > 128) if (plen == 0 || plen > 128)
continue; continue;
/* Routers through this particular gateway */ nm_utils_ip6_address_clear_host_address(&network,
nm_utils_ip6_address_clear_host_address(&route.network,
ndp_msg_opt_route_prefix(msg, offset), ndp_msg_opt_route_prefix(msg, offset),
route.plen); plen);
if (nm_ndisc_add_route(ndisc, &route))
changed |= NM_NDISC_CONFIG_ROUTES; {
const NMNDiscRoute route = {
.network = network,
.gateway = gateway_addr,
.plen = plen,
.expiry_msec =
_nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_route_lifetime(msg, offset)),
.preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)),
};
/* Routers through this particular gateway */
if (nm_ndisc_add_route(ndisc, &route, now_msec))
changed |= NM_NDISC_CONFIG_ROUTES;
}
} }
/* DNS information */
ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_RDNSS) { ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_RDNSS) {
struct in6_addr *addr; struct in6_addr *addr;
int addr_index; int addr_index;
ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) { ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) {
NMNDiscDNSServer dns_server = { const NMNDiscDNSServer dns_server = {
.address = *addr, .address = *addr,
.timestamp = now, .expiry_msec =
.lifetime = ndp_msg_opt_rdnss_lifetime(msg, offset), _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_rdnss_lifetime(msg, offset)),
}; };
if (nm_ndisc_add_dns_server(ndisc, &dns_server)) if (nm_ndisc_add_dns_server(ndisc, &dns_server, now_msec))
changed |= NM_NDISC_CONFIG_DNS_SERVERS; changed |= NM_NDISC_CONFIG_DNS_SERVERS;
} }
} }
@ -270,13 +284,13 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
int domain_index; int domain_index;
ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) { ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) {
NMNDiscDNSDomain dns_domain = { const NMNDiscDNSDomain dns_domain = {
.domain = domain, .domain = domain,
.timestamp = now, .expiry_msec =
.lifetime = ndp_msg_opt_dnssl_lifetime(msg, offset), _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_dnssl_lifetime(msg, offset)),
}; };
if (nm_ndisc_add_dns_domain(ndisc, &dns_domain)) if (nm_ndisc_add_dns_domain(ndisc, &dns_domain, now_msec))
changed |= NM_NDISC_CONFIG_DNS_DOMAINS; changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
} }
} }
@ -316,7 +330,7 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
} }
} }
nm_ndisc_ra_received(ndisc, now, changed); nm_ndisc_ra_received(ndisc, now_msec, changed);
return 0; return 0;
} }
@ -374,7 +388,6 @@ send_ra(NMNDisc *ndisc, GError **error)
{ {
NMLndpNDiscPrivate * priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); NMLndpNDiscPrivate * priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
NMNDiscDataInternal * rdata = ndisc->rdata; NMNDiscDataInternal * rdata = ndisc->rdata;
gint32 now = nm_utils_get_monotonic_timestamp_sec();
int errsv; int errsv;
struct in6_addr * addr; struct in6_addr * addr;
struct ndp_msg * msg; struct ndp_msg * msg;
@ -404,18 +417,9 @@ send_ra(NMNDisc *ndisc, GError **error)
/* The device let us know about all addresses that the device got /* The device let us know about all addresses that the device got
* whose prefixes are suitable for delegating. Let's announce them. */ * whose prefixes are suitable for delegating. Let's announce them. */
for (i = 0; i < rdata->addresses->len; i++) { for (i = 0; i < rdata->addresses->len; i++) {
const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i); const NMNDiscAddress * address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
guint32 age = NM_CLAMP((gint64) now - (gint64) address->timestamp, 0, G_MAXUINT32 - 1);
guint32 lifetime = address->lifetime;
guint32 preferred = address->preferred;
struct nd_opt_prefix_info *prefix; struct nd_opt_prefix_info *prefix;
/* Clamp the life times if they're not forever. */
if (lifetime != NM_NDISC_INFINITY)
lifetime = lifetime > age ? lifetime - age : 0;
if (preferred != NM_NDISC_INFINITY)
preferred = preferred > age ? preferred - age : 0;
prefix = _ndp_msg_add_option(msg, sizeof(*prefix)); prefix = _ndp_msg_add_option(msg, sizeof(*prefix));
if (!prefix) { if (!prefix) {
/* Maybe we could sent separate RAs, but why bother... */ /* Maybe we could sent separate RAs, but why bother... */
@ -428,8 +432,14 @@ send_ra(NMNDisc *ndisc, GError **error)
prefix->nd_opt_pi_prefix_len = 64; prefix->nd_opt_pi_prefix_len = 64;
prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; 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_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
prefix->nd_opt_pi_valid_time = htonl(lifetime); prefix->nd_opt_pi_valid_time =
prefix->nd_opt_pi_preferred_time = htonl(preferred); htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
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));
prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0]; 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[1] = address->address.s6_addr32[1];
prefix->nd_opt_pi_prefix.s6_addr32[2] = 0; prefix->nd_opt_pi_prefix.s6_addr32[2] = 0;

View file

@ -21,14 +21,15 @@ struct _NMNDiscDataInternal {
typedef struct _NMNDiscDataInternal NMNDiscDataInternal; typedef struct _NMNDiscDataInternal NMNDiscDataInternal;
void nm_ndisc_ra_received(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed); void nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed);
void nm_ndisc_rs_received(NMNDisc *ndisc); void nm_ndisc_rs_received(NMNDisc *ndisc);
gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new); gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec);
gboolean nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s); gboolean
gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new); nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec);
gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new); gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec);
gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new); 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);
/*****************************************************************************/ /*****************************************************************************/

View file

@ -41,7 +41,9 @@ struct _NMNDiscPrivate {
gint32 last_rs; gint32 last_rs;
gint32 last_ra; gint32 last_ra;
}; };
guint timeout_id; /* prefix/dns/etc lifetime timeout */
GSource *timeout_expire_source;
NMUtilsIPv6IfaceId iid; NMUtilsIPv6IfaceId iid;
/* immutable values: */ /* immutable values: */
@ -84,7 +86,8 @@ G_DEFINE_TYPE(NMNDisc, nm_ndisc, G_TYPE_OBJECT)
/*****************************************************************************/ /*****************************************************************************/
static void _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed); static void _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed);
static gboolean timeout_expire_cb(gpointer user_data);
/*****************************************************************************/ /*****************************************************************************/
@ -102,6 +105,7 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex * multi_idx,
guint32 ifa_flags; guint32 ifa_flags;
guint8 plen; guint8 plen;
guint i; guint i;
const gint32 now_sec = nm_utils_get_monotonic_timestamp_sec();
l3cd = nm_l3_config_data_new(multi_idx, ifindex); l3cd = nm_l3_config_data_new(multi_idx, ifindex);
@ -129,12 +133,17 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex * multi_idx,
NMPlatformIP6Address a; NMPlatformIP6Address a;
a = (NMPlatformIP6Address){ a = (NMPlatformIP6Address){
.ifindex = ifindex, .ifindex = ifindex,
.address = ndisc_addr->address, .address = ndisc_addr->address,
.plen = plen, .plen = plen,
.timestamp = ndisc_addr->timestamp, .timestamp = now_sec,
.lifetime = ndisc_addr->lifetime, .lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) now_sec) * 1000,
.preferred = MIN(ndisc_addr->lifetime, ndisc_addr->preferred), ndisc_addr->expiry_msec,
TRUE),
.preferred = _nm_ndisc_lifetime_from_expiry(
((gint64) now_sec) * 1000,
NM_MIN(ndisc_addr->expiry_msec, ndisc_addr->expiry_preferred_msec),
TRUE),
.addr_source = NM_IP_CONFIG_SOURCE_NDISC, .addr_source = NM_IP_CONFIG_SOURCE_NDISC,
.n_ifa_flags = ifa_flags, .n_ifa_flags = ifa_flags,
}; };
@ -220,68 +229,40 @@ _preference_to_priority(NMIcmpv6RouterPref pref)
/*****************************************************************************/ /*****************************************************************************/
/* we rely on the fact, that _EXPIRY_INFINITY > any other valid gint64 timestamps. */
#define _EXPIRY_INFINITY G_MAXINT64
static gint64
get_expiry_time(guint32 timestamp, guint32 lifetime)
{
nm_assert(timestamp > 0);
nm_assert(timestamp <= G_MAXINT32);
if (lifetime == NM_NDISC_INFINITY)
return _EXPIRY_INFINITY;
return ((gint64) timestamp) + ((gint64) lifetime);
}
#define get_expiry(item) \
({ \
typeof(item) _item = (item); \
nm_assert(_item); \
get_expiry_time(_item->timestamp, _item->lifetime); \
})
#define get_expiry_preferred(item) \
({ \
typeof(item) _item = (item); \
nm_assert(_item); \
get_expiry_time(_item->timestamp, _item->preferred); \
})
static gboolean static gboolean
expiry_next(gint32 now_s, gint64 expiry_timestamp, gint32 *nextevent) expiry_next(gint64 now_msec, gint64 expiry_msec, gint64 *next_msec)
{ {
gint32 e; if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
if (expiry_timestamp == _EXPIRY_INFINITY)
return TRUE; return TRUE;
e = MIN(expiry_timestamp, ((gint64)(G_MAXINT32 - 1)));
if (now_s >= e) if (expiry_msec <= now_msec) {
/* expired. */
return FALSE; return FALSE;
if (nextevent) {
if (*nextevent > e)
*nextevent = e;
} }
if (next_msec) {
if (*next_msec > expiry_msec)
*next_msec = expiry_msec;
}
/* the timestamp is good (not yet expired) */
return TRUE; return TRUE;
} }
static const char * static const char *
_get_exp(char *buf, gsize buf_size, gint64 now_ns, gint64 expiry_time) _get_exp(char *buf, gsize buf_size, gint64 now_msec, gint64 expiry_time)
{ {
int l; int l;
if (expiry_time == _EXPIRY_INFINITY) if (expiry_time == NM_NDISC_EXPIRY_INFINITY)
return "permanent"; return "permanent";
l = g_snprintf(buf, l = g_snprintf(buf, buf_size, "%.3f", ((double) (expiry_time - now_msec)) / 1000);
buf_size,
"%.4f",
((double) ((expiry_time * NM_UTILS_NSEC_PER_SEC) - now_ns))
/ ((double) NM_UTILS_NSEC_PER_SEC));
nm_assert(l < buf_size); nm_assert(l < buf_size);
return buf; return buf;
} }
#define get_exp(buf, now_ns, item) _get_exp((buf), G_N_ELEMENTS(buf), (now_ns), (get_expiry(item))) #define get_exp(buf, now_msec, item) \
_get_exp((buf), G_N_ELEMENTS(buf), (now_msec), (item)->expiry_msec)
/*****************************************************************************/ /*****************************************************************************/
@ -352,17 +333,16 @@ _ASSERT_data_gateways(const NMNDiscDataInternal *data)
const NMNDiscGateway *item = &g_array_index(data->gateways, NMNDiscGateway, i); const NMNDiscGateway *item = &g_array_index(data->gateways, NMNDiscGateway, i);
nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&item->address)); nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&item->address));
nm_assert(item->timestamp > 0 && item->timestamp <= G_MAXINT32);
for (j = 0; j < i; j++) { for (j = 0; j < i; j++) {
const NMNDiscGateway *item2 = &g_array_index(data->gateways, NMNDiscGateway, j); const NMNDiscGateway *item2 = &g_array_index(data->gateways, NMNDiscGateway, j);
nm_assert(!IN6_ARE_ADDR_EQUAL(&item->address, &item2->address)); nm_assert(!IN6_ARE_ADDR_EQUAL(&item->address, &item2->address));
} }
nm_assert(item->lifetime > 0); if (i > 0) {
if (i > 0)
nm_assert(_preference_to_priority(item_prev->preference) nm_assert(_preference_to_priority(item_prev->preference)
>= _preference_to_priority(item->preference)); >= _preference_to_priority(item->preference));
}
item_prev = item; item_prev = item;
} }
@ -408,7 +388,7 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
/*****************************************************************************/ /*****************************************************************************/
gboolean gboolean
nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new) nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec)
{ {
NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
guint i; guint i;
@ -417,41 +397,43 @@ nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new)
for (i = 0; i < rdata->gateways->len;) { for (i = 0; i < rdata->gateways->len;) {
NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i); NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) { if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
if (new->lifetime == 0) { if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->gateways, i); g_array_remove_index(rdata->gateways, i);
_ASSERT_data_gateways(rdata); _ASSERT_data_gateways(rdata);
return TRUE; return TRUE;
} }
if (item->preference != new->preference) { if (item->preference != new_item->preference) {
g_array_remove_index(rdata->gateways, i); g_array_remove_index(rdata->gateways, i);
continue; continue;
} }
if (get_expiry(item) == get_expiry(new)) if (item->expiry_msec == new_item->expiry_msec)
return FALSE; return FALSE;
*item = *new; item->expiry_msec = new_item->expiry_msec;
_ASSERT_data_gateways(rdata); _ASSERT_data_gateways(rdata);
return TRUE; return TRUE;
} }
/* Put before less preferable gateways. */ /* Put before less preferable gateways. */
if (_preference_to_priority(item->preference) < _preference_to_priority(new->preference) if (_preference_to_priority(item->preference)
< _preference_to_priority(new_item->preference)
&& insert_idx == G_MAXUINT) && insert_idx == G_MAXUINT)
insert_idx = i; insert_idx = i;
i++; i++;
} }
if (new->lifetime) { if (new_item->expiry_msec <= now_msec)
g_array_insert_val(rdata->gateways, return FALSE;
insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
*new); g_array_insert_val(rdata->gateways,
} insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
*new_item);
_ASSERT_data_gateways(rdata); _ASSERT_data_gateways(rdata);
return !!new->lifetime; return TRUE;
} }
/** /**
@ -504,25 +486,27 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
return TRUE; return TRUE;
} }
_LOGW("complete-address: can't generate a new EUI-64 address"); _LOGW("complete-address: can't generate a new_item EUI-64 address");
return FALSE; return FALSE;
} }
static gboolean static gboolean
nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gboolean from_ra) nm_ndisc_add_address(NMNDisc * ndisc,
const NMNDiscAddress *new_item,
gint64 now_msec,
gboolean from_ra)
{ {
NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc); NMNDiscPrivate * priv = NM_NDISC_GET_PRIVATE(ndisc);
NMNDiscDataInternal *rdata = &priv->rdata; NMNDiscDataInternal *rdata = &priv->rdata;
NMNDiscAddress new2; NMNDiscAddress * new2;
NMNDiscAddress * existing = NULL; NMNDiscAddress * existing = NULL;
guint i; guint i;
nm_assert(new); nm_assert(new_item);
nm_assert(new->timestamp > 0 && new->timestamp < G_MAXINT32); nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new_item->address));
nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new->address)); nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new_item->address));
nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new->address)); nm_assert(new_item->expiry_preferred_msec <= new_item->expiry_msec);
nm_assert(new->preferred <= new->lifetime); nm_assert((!!from_ra) == (now_msec > 0));
nm_assert(!from_ra || now_s > 0);
for (i = 0; i < rdata->addresses->len; i++) { for (i = 0; i < rdata->addresses->len; i++) {
NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i); NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i);
@ -530,12 +514,12 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
if (from_ra) { if (from_ra) {
/* RFC4862 5.5.3.d, we find an existing address with the same prefix. /* RFC4862 5.5.3.d, we find an existing address with the same prefix.
* (note that all prefixes at this point have implicitly length /64). */ * (note that all prefixes at this point have implicitly length /64). */
if (memcmp(&item->address, &new->address, 8) == 0) { if (memcmp(&item->address, &new_item->address, 8) == 0) {
existing = item; existing = item;
break; break;
} }
} else { } else {
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) { if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
existing = item; existing = item;
break; break;
} }
@ -543,67 +527,60 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
} }
if (existing) { if (existing) {
gint64 new_expiry_preferred_msec;
gint64 new_expiry_msec;
if (from_ra) { if (from_ra) {
const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */ if (new_item->expiry_msec == NM_NDISC_EXPIRY_INFINITY)
gint64 old_expiry_lifetime, old_expiry_preferred; new_expiry_msec = NM_NDISC_EXPIRY_INFINITY;
old_expiry_lifetime = get_expiry(existing);
old_expiry_preferred = get_expiry_preferred(existing);
if (new->lifetime == NM_NDISC_INFINITY)
existing->lifetime = NM_NDISC_INFINITY;
else { else {
gint64 new_lifetime, remaining_lifetime; const gint64 NDISC_PREFIX_LFT_MIN_MSEC = 7200 * 1000; /* RFC4862 5.5.3.e */
gint64 new_lifetime;
gint64 existing_lifetime;
new_lifetime = new_item->expiry_msec - now_msec;
if (existing->expiry_msec == NM_NDISC_EXPIRY_INFINITY)
existing_lifetime = G_MAXINT64;
else
existing_lifetime = existing->expiry_msec - now_msec;
/* see RFC4862 5.5.3.e */ /* see RFC4862 5.5.3.e */
if (existing->lifetime == NM_NDISC_INFINITY) if (new_lifetime >= NDISC_PREFIX_LFT_MIN_MSEC
remaining_lifetime = G_MAXINT64; || new_lifetime >= existing_lifetime) {
else /* either extend the lifetime of the new_item lifetime is longer than
remaining_lifetime = ((gint64) existing->timestamp) * NDISC_PREFIX_LFT_MIN_MSEC. */
+ ((gint64) existing->lifetime) - ((gint64) now_s); new_expiry_msec = new_item->expiry_msec;
new_lifetime = } else if (existing_lifetime <= NDISC_PREFIX_LFT_MIN_MSEC) {
((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s);
if (new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN
|| new_lifetime > remaining_lifetime) {
existing->timestamp = now_s;
existing->lifetime = CLAMP(new_lifetime, (gint64) 0, (gint64)(G_MAXUINT32 - 1));
} else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) {
/* keep the current lifetime. */ /* keep the current lifetime. */
new_expiry_msec = existing->expiry_msec;
} else { } else {
existing->timestamp = now_s; /* trim the current lifetime to NDISC_PREFIX_LFT_MIN_MSEC. */
existing->lifetime = NM_NDISC_PREFIX_LFT_MIN; new_expiry_msec = now_msec + NDISC_PREFIX_LFT_MIN_MSEC;
} }
} }
if (new->preferred == NM_NDISC_INFINITY) { new_expiry_preferred_msec =
nm_assert(existing->lifetime == NM_NDISC_INFINITY); NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
existing->preferred = new->preferred; new_expiry_preferred_msec = NM_MIN(new_expiry_preferred_msec, new_expiry_msec);
} else { } else {
existing->preferred = NM_CLAMP(((gint64) new->timestamp) + ((gint64) new->preferred) if (new_item->expiry_msec <= now_msec) {
- ((gint64) existing->timestamp), g_array_remove_index(rdata->addresses, i);
0, return TRUE;
G_MAXUINT32 - 1);
if (existing->lifetime != NM_NDISC_INFINITY)
existing->preferred = MIN(existing->preferred, existing->lifetime);
} }
return old_expiry_lifetime != get_expiry(existing) new_expiry_msec = new_item->expiry_msec;
|| old_expiry_preferred != get_expiry_preferred(existing); new_expiry_preferred_msec =
NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
} }
if (new->lifetime == 0) { /* the dad_counter does not get modified. */
g_array_remove_index(rdata->addresses, i); if (new_expiry_msec == existing->expiry_msec
return TRUE; && new_expiry_preferred_msec == existing->expiry_preferred_msec) {
}
if (get_expiry(existing) == get_expiry(new)
&& get_expiry_preferred(existing) == get_expiry_preferred(new))
return FALSE; return FALSE;
}
existing->timestamp = new->timestamp; existing->expiry_msec = new_expiry_msec;
existing->lifetime = new->lifetime; existing->expiry_preferred_msec = new_expiry_preferred_msec;
existing->preferred = new->preferred;
return TRUE; return TRUE;
} }
@ -614,36 +591,42 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
if (priv->max_addresses && rdata->addresses->len >= priv->max_addresses) if (priv->max_addresses && rdata->addresses->len >= priv->max_addresses)
return FALSE; return FALSE;
if (new->lifetime == 0) if (new_item->expiry_msec <= now_msec)
return FALSE; return FALSE;
new2 = nm_g_array_append_new(rdata->addresses, NMNDiscAddress);
*new2 = *new_item;
new2->expiry_preferred_msec = NM_MIN(new2->expiry_preferred_msec, new2->expiry_msec);
if (from_ra) { if (from_ra) {
new2 = *new; new2->dad_counter = 0;
new2.dad_counter = 0; if (!complete_address(ndisc, new2)) {
if (!complete_address(ndisc, &new2)) g_array_set_size(rdata->addresses, rdata->addresses->len - 1);
return FALSE; return FALSE;
new = &new2; }
} }
g_array_append_val(rdata->addresses, *new);
return TRUE; return TRUE;
} }
gboolean gboolean
nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s) nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec)
{ {
return nm_ndisc_add_address(ndisc, new, now_s, TRUE); return nm_ndisc_add_address(ndisc, new_item, now_msec, TRUE);
} }
gboolean gboolean
nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new) nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec)
{ {
NMNDiscPrivate * priv; NMNDiscPrivate * priv;
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata;
guint i; guint i;
guint insert_idx = G_MAXUINT; guint insert_idx = G_MAXUINT;
gboolean changed = FALSE;
if (new->plen == 0 || new->plen > 128) { if (new_item->plen == 0 || new_item->plen > 128) {
/* Only expect non-default routes. The router has no idea what the /* Only expect non-default routes. The router has no idea what the
* local configuration or user preferences are, so sending routes * local configuration or user preferences are, so sending routes
* with a prefix length of 0 must be ignored by NMNDisc. * with a prefix length of 0 must be ignored by NMNDisc.
@ -660,41 +643,48 @@ nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new)
for (i = 0; i < rdata->routes->len;) { for (i = 0; i < rdata->routes->len;) {
NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i); NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
if (IN6_ARE_ADDR_EQUAL(&item->network, &new->network) && item->plen == new->plen) { if (IN6_ARE_ADDR_EQUAL(&item->network, &new_item->network)
if (new->lifetime == 0) { && item->plen == new_item->plen) {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->routes, i); g_array_remove_index(rdata->routes, i);
return TRUE; return TRUE;
} }
if (item->preference != new->preference) { if (item->preference != new_item->preference) {
g_array_remove_index(rdata->routes, i); g_array_remove_index(rdata->routes, i);
changed = TRUE;
continue; continue;
} }
if (get_expiry(item) == get_expiry(new) if (item->expiry_msec == new_item->expiry_msec
&& IN6_ARE_ADDR_EQUAL(&item->gateway, &new->gateway)) && IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway))
return FALSE; return FALSE;
*item = *new; item->expiry_msec = new_item->expiry_msec;
item->gateway = new_item->gateway;
return TRUE; return TRUE;
} }
/* Put before less preferable routes. */ /* Put before less preferable routes. */
if (_preference_to_priority(item->preference) < _preference_to_priority(new->preference) if (_preference_to_priority(item->preference)
< _preference_to_priority(new_item->preference)
&& insert_idx == G_MAXUINT) && insert_idx == G_MAXUINT)
insert_idx = i; insert_idx = i;
i++; i++;
} }
if (new->lifetime) { if (new_item->expiry_msec <= now_msec) {
g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new); nm_assert(!changed);
return FALSE;
} }
return !!new->lifetime;
g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new_item);
return TRUE;
} }
gboolean gboolean
nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new) nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec)
{ {
NMNDiscPrivate * priv; NMNDiscPrivate * priv;
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata;
@ -706,28 +696,30 @@ nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new)
for (i = 0; i < rdata->dns_servers->len; i++) { for (i = 0; i < rdata->dns_servers->len; i++) {
NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) { if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
if (new->lifetime == 0) { if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->dns_servers, i); g_array_remove_index(rdata->dns_servers, i);
return TRUE; return TRUE;
} }
if (get_expiry(item) == get_expiry(new)) if (item->expiry_msec == new_item->expiry_msec)
return FALSE; return FALSE;
*item = *new; item->expiry_msec = new_item->expiry_msec;
return TRUE; return TRUE;
} }
} }
if (new->lifetime) if (new_item->expiry_msec <= now_msec)
g_array_append_val(rdata->dns_servers, *new); return FALSE;
return !!new->lifetime;
g_array_append_val(rdata->dns_servers, *new_item);
return TRUE;
} }
/* Copies new->domain if 'new' is added to the dns_domains list */ /* Copies new_item->domain if 'new_item' is added to the dns_domains list */
gboolean gboolean
nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new) nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec)
{ {
NMNDiscPrivate * priv; NMNDiscPrivate * priv;
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata;
@ -740,43 +732,45 @@ nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new)
for (i = 0; i < rdata->dns_domains->len; i++) { for (i = 0; i < rdata->dns_domains->len; i++) {
item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
if (!g_strcmp0(item->domain, new->domain)) { if (nm_streq(item->domain, new_item->domain)) {
if (new->lifetime == 0) { if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->dns_domains, i); g_array_remove_index(rdata->dns_domains, i);
return TRUE; return TRUE;
} }
if (get_expiry(item) == get_expiry(new)) if (item->expiry_msec == new_item->expiry_msec)
return FALSE; return FALSE;
item->timestamp = new->timestamp; item->expiry_msec = new_item->expiry_msec;
item->lifetime = new->lifetime;
return TRUE; return TRUE;
} }
} }
if (new->lifetime) { if (new_item->expiry_msec <= now_msec)
g_array_append_val(rdata->dns_domains, *new); return FALSE;
item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, rdata->dns_domains->len - 1);
item->domain = g_strdup(new->domain); item = nm_g_array_append_new(rdata->dns_domains, NMNDiscDNSDomain);
} *item = (NMNDiscDNSDomain){
return !!new->lifetime; .domain = g_strdup(new_item->domain),
.expiry_msec = new_item->expiry_msec,
};
return TRUE;
} }
/*****************************************************************************/ /*****************************************************************************/
#define _MAYBE_WARN(...) \ #define _MAYBE_WARN(...) \
G_STMT_START \ G_STMT_START \
{ \ { \
gboolean _different_message; \ gboolean _different_message; \
\ \
_different_message = g_strcmp0(priv->last_error, error->message) != 0; \ _different_message = !nm_streq0(priv->last_error, error->message); \
_NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \ _NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \
if (_different_message) { \ if (_different_message) { \
nm_clear_g_free(&priv->last_error); \ nm_clear_g_free(&priv->last_error); \
priv->last_error = g_strdup(error->message); \ priv->last_error = g_strdup(error->message); \
} \ } \
} \ } \
G_STMT_END G_STMT_END
static gboolean static gboolean
@ -937,12 +931,16 @@ nm_ndisc_set_config(NMNDisc * ndisc,
} }
for (i = 0; i < dns_servers->len; i++) { for (i = 0; i < dns_servers->len; i++) {
if (nm_ndisc_add_dns_server(ndisc, &g_array_index(dns_servers, NMNDiscDNSServer, i))) if (nm_ndisc_add_dns_server(ndisc,
&g_array_index(dns_servers, NMNDiscDNSServer, i),
G_MININT64))
changed = TRUE; changed = TRUE;
} }
for (i = 0; i < dns_domains->len; i++) { for (i = 0; i < dns_domains->len; i++) {
if (nm_ndisc_add_dns_domain(ndisc, &g_array_index(dns_domains, NMNDiscDNSDomain, i))) if (nm_ndisc_add_dns_domain(ndisc,
&g_array_index(dns_domains, NMNDiscDNSDomain, i),
G_MININT64))
changed = TRUE; changed = TRUE;
} }
@ -1098,7 +1096,7 @@ nm_ndisc_stop(NMNDisc *ndisc)
nm_clear_g_source(&priv->send_rs_id); nm_clear_g_source(&priv->send_rs_id);
nm_clear_g_source(&priv->send_ra_id); nm_clear_g_source(&priv->send_ra_id);
nm_clear_g_free(&priv->last_error); nm_clear_g_free(&priv->last_error);
nm_clear_g_source(&priv->timeout_id); nm_clear_g_source_inst(&priv->timeout_expire_source);
priv->solicitations_left = 0; priv->solicitations_left = 0;
priv->announcements_left = 0; priv->announcements_left = 0;
@ -1180,15 +1178,15 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata;
guint i; guint i;
char changedstr[CONFIG_MAP_MAX_STR]; char changedstr[CONFIG_MAP_MAX_STR];
char addrstr[INET6_ADDRSTRLEN]; char addrstr[NM_UTILS_INET_ADDRSTRLEN];
char str_pref[35]; char str_pref[35];
char str_exp[100]; char str_exp[100];
gint64 now_ns; gint64 now_msec;
if (!_LOGD_ENABLED()) if (!_LOGD_ENABLED())
return; return;
now_ns = nm_utils_get_monotonic_timestamp_nsec(); now_msec = nm_utils_get_monotonic_timestamp_msec();
priv = NM_NDISC_GET_PRIVATE(ndisc); priv = NM_NDISC_GET_PRIVATE(ndisc);
rdata = &priv->rdata; rdata = &priv->rdata;
@ -1205,199 +1203,245 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
_LOGD(" retrans timer : %u", (guint) rdata->public.retrans_timer_ms); _LOGD(" retrans timer : %u", (guint) rdata->public.retrans_timer_ms);
for (i = 0; i < rdata->gateways->len; i++) { for (i = 0; i < rdata->gateways->len; i++) {
NMNDiscGateway *gateway = &g_array_index(rdata->gateways, NMNDiscGateway, i); const NMNDiscGateway *gateway = &g_array_index(rdata->gateways, NMNDiscGateway, i);
inet_ntop(AF_INET6, &gateway->address, addrstr, sizeof(addrstr));
_LOGD(" gateway %s pref %s exp %s", _LOGD(" gateway %s pref %s exp %s",
addrstr, _nm_utils_inet6_ntop(&gateway->address, addrstr),
nm_icmpv6_router_pref_to_string(gateway->preference, str_pref, sizeof(str_pref)), nm_icmpv6_router_pref_to_string(gateway->preference, str_pref, sizeof(str_pref)),
get_exp(str_exp, now_ns, gateway)); get_exp(str_exp, now_msec, gateway));
} }
for (i = 0; i < rdata->addresses->len; i++) { for (i = 0; i < rdata->addresses->len; i++) {
const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i); const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
inet_ntop(AF_INET6, &address->address, addrstr, sizeof(addrstr)); _LOGD(" address %s exp %s",
_LOGD(" address %s exp %s", addrstr, get_exp(str_exp, now_ns, address)); _nm_utils_inet6_ntop(&address->address, addrstr),
get_exp(str_exp, now_msec, address));
} }
for (i = 0; i < rdata->routes->len; i++) { for (i = 0; i < rdata->routes->len; i++) {
NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i); const NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i);
char sbuf[NM_UTILS_INET_ADDRSTRLEN]; char sbuf[NM_UTILS_INET_ADDRSTRLEN];
inet_ntop(AF_INET6, &route->network, addrstr, sizeof(addrstr));
_LOGD(" route %s/%u via %s pref %s exp %s", _LOGD(" route %s/%u via %s pref %s exp %s",
addrstr, _nm_utils_inet6_ntop(&route->network, addrstr),
(guint) route->plen, (guint) route->plen,
_nm_utils_inet6_ntop(&route->gateway, sbuf), _nm_utils_inet6_ntop(&route->gateway, sbuf),
nm_icmpv6_router_pref_to_string(route->preference, str_pref, sizeof(str_pref)), nm_icmpv6_router_pref_to_string(route->preference, str_pref, sizeof(str_pref)),
get_exp(str_exp, now_ns, route)); get_exp(str_exp, now_msec, route));
} }
for (i = 0; i < rdata->dns_servers->len; i++) { for (i = 0; i < rdata->dns_servers->len; i++) {
NMNDiscDNSServer *dns_server = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); const NMNDiscDNSServer *dns_server =
&g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
inet_ntop(AF_INET6, &dns_server->address, addrstr, sizeof(addrstr)); _LOGD(" dns_server %s exp %s",
_LOGD(" dns_server %s exp %s", addrstr, get_exp(str_exp, now_ns, dns_server)); _nm_utils_inet6_ntop(&dns_server->address, addrstr),
get_exp(str_exp, now_msec, dns_server));
} }
for (i = 0; i < rdata->dns_domains->len; i++) { for (i = 0; i < rdata->dns_domains->len; i++) {
NMNDiscDNSDomain *dns_domain = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); const NMNDiscDNSDomain *dns_domain =
&g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
_LOGD(" dns_domain %s exp %s", dns_domain->domain, get_exp(str_exp, now_ns, dns_domain)); _LOGD(" dns_domain %s exp %s", dns_domain->domain, get_exp(str_exp, now_msec, dns_domain));
} }
} }
/*****************************************************************************/
static void static void
clean_gateways(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent) clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{ {
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
NMNDiscGateway * arr;
guint i; guint i;
guint j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; if (rdata->gateways->len == 0)
return;
for (i = 0; i < rdata->gateways->len;) { arr = &g_array_index(rdata->gateways, NMNDiscGateway, 0);
NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
if (!expiry_next(now, get_expiry(item), nextevent)) { for (i = 0, j = 0; i < rdata->gateways->len; i++) {
g_array_remove_index(rdata->gateways, i); if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
*changed |= NM_NDISC_CONFIG_GATEWAYS;
continue; continue;
} if (i != j)
arr[j] = arr[i];
j++;
}
i++; if (i != j) {
*changed |= NM_NDISC_CONFIG_GATEWAYS;
g_array_set_size(rdata->gateways, j);
} }
_ASSERT_data_gateways(rdata); _ASSERT_data_gateways(rdata);
} }
static void static void
clean_addresses(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent) clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{ {
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
NMNDiscAddress * arr;
guint i; guint i;
guint j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; if (rdata->addresses->len == 0)
return;
for (i = 0; i < rdata->addresses->len;) { arr = &g_array_index(rdata->addresses, NMNDiscAddress, 0);
const NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i);
if (!expiry_next(now, get_expiry(item), nextevent)) { for (i = 0, j = 0; i < rdata->addresses->len; i++) {
g_array_remove_index(rdata->addresses, i); if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
*changed |= NM_NDISC_CONFIG_ADDRESSES;
continue; continue;
} if (i != j)
arr[j] = arr[i];
j++;
}
i++; if (i != j) {
*changed = NM_NDISC_CONFIG_ADDRESSES;
g_array_set_size(rdata->addresses, j);
} }
} }
static void static void
clean_routes(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent) clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{ {
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
NMNDiscRoute * arr;
guint i; guint i;
guint j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; if (rdata->routes->len == 0)
return;
for (i = 0; i < rdata->routes->len;) { arr = &g_array_index(rdata->routes, NMNDiscRoute, 0);
NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
if (!expiry_next(now, get_expiry(item), nextevent)) { for (i = 0, j = 0; i < rdata->routes->len; i++) {
g_array_remove_index(rdata->routes, i); if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
*changed |= NM_NDISC_CONFIG_ROUTES;
continue; continue;
} if (i != j)
arr[j] = arr[i];
j++;
}
i++; if (i != j) {
*changed |= NM_NDISC_CONFIG_ROUTES;
g_array_set_size(rdata->routes, j);
} }
} }
static void static void
clean_dns_servers(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent) clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{ {
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
NMNDiscDNSServer * arr;
guint i; guint i;
guint j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; if (rdata->dns_servers->len == 0)
return;
for (i = 0; i < rdata->dns_servers->len;) { arr = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, 0);
NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
if (!expiry_next(now, get_expiry(item), nextevent)) { for (i = 0, j = 0; i < rdata->dns_servers->len; i++) {
g_array_remove_index(rdata->dns_servers, i); if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
continue; continue;
} if (i != j)
arr[j] = arr[i];
j++;
}
i++; if (i != j) {
*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
g_array_set_size(rdata->dns_servers, j);
} }
} }
static void static void
clean_dns_domains(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent) clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{ {
NMNDiscDataInternal *rdata; NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
NMNDiscDNSDomain * arr;
guint i; guint i;
guint j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; if (rdata->dns_domains->len == 0)
return;
for (i = 0; i < rdata->dns_domains->len;) { arr = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, 0);
NMNDiscDNSDomain *item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
if (!expiry_next(now, get_expiry(item), nextevent)) { for (i = 0, j = 0; i < rdata->dns_domains->len; i++) {
g_array_remove_index(rdata->dns_domains, i); if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
continue; continue;
if (i != j) {
g_free(arr[j].domain);
arr[j] = arr[i];
arr[i].domain = NULL;
} }
i++; j++;
}
if (i != 0) {
*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
g_array_set_size(rdata->dns_domains, j);
} }
} }
static gboolean timeout_cb(gpointer user_data);
static void static void
check_timestamps(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed) check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
{ {
NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
/* Use a magic date in the distant future (~68 years) */ gint64 next_msec = G_MAXINT64;
gint32 nextevent = G_MAXINT32;
nm_clear_g_source(&priv->timeout_id); _LOGT("router-data: check for changed router advertisement data");
clean_gateways(ndisc, now, &changed, &nextevent); clean_gateways(ndisc, now_msec, &changed, &next_msec);
clean_addresses(ndisc, now, &changed, &nextevent); clean_addresses(ndisc, now_msec, &changed, &next_msec);
clean_routes(ndisc, now, &changed, &nextevent); clean_routes(ndisc, now_msec, &changed, &next_msec);
clean_dns_servers(ndisc, now, &changed, &nextevent); clean_dns_servers(ndisc, now_msec, &changed, &next_msec);
clean_dns_domains(ndisc, now, &changed, &nextevent); clean_dns_domains(ndisc, now_msec, &changed, &next_msec);
if (nextevent != G_MAXINT32) { nm_assert(next_msec > now_msec);
if (nextevent <= now)
g_return_if_reached(); nm_clear_g_source_inst(&priv->timeout_expire_source);
_LOGD("scheduling next now/lifetime check: %d seconds", (int) (nextevent - now));
priv->timeout_id = g_timeout_add_seconds(nextevent - now, timeout_cb, ndisc); if (next_msec == NM_NDISC_EXPIRY_INFINITY)
_LOGD("router-data: next lifetime expiration will happen: never");
else {
const gint64 timeout_msec = NM_MIN(next_msec - now_msec, ((gint64) G_MAXINT32));
const guint TIMEOUT_APPROX_THRESHOLD_SEC = 10000;
_LOGD("router-data: next lifetime expiration will happen: in %s%.3f seconds",
(timeout_msec / 1000) >= TIMEOUT_APPROX_THRESHOLD_SEC ? " about" : "",
((double) timeout_msec) / 1000);
priv->timeout_expire_source = nm_g_timeout_add_source_approx(timeout_msec,
TIMEOUT_APPROX_THRESHOLD_SEC,
timeout_expire_cb,
ndisc);
} }
if (changed) if (changed != NM_NDISC_CONFIG_NONE)
nm_ndisc_emit_config_change(ndisc, changed); nm_ndisc_emit_config_change(ndisc, changed);
} }
static gboolean static gboolean
timeout_cb(gpointer user_data) timeout_expire_cb(gpointer user_data)
{ {
NMNDisc *self = user_data; check_timestamps(user_data, nm_utils_get_monotonic_timestamp_msec(), NM_NDISC_CONFIG_NONE);
return G_SOURCE_CONTINUE;
NM_NDISC_GET_PRIVATE(self)->timeout_id = 0;
check_timestamps(self, nm_utils_get_monotonic_timestamp_sec(), 0);
return G_SOURCE_REMOVE;
} }
void void
nm_ndisc_ra_received(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed) nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
{ {
NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
nm_clear_g_source_inst(&priv->ra_timeout_source); nm_clear_g_source_inst(&priv->ra_timeout_source);
nm_clear_g_source(&priv->send_rs_id); nm_clear_g_source(&priv->send_rs_id);
nm_clear_g_free(&priv->last_error); nm_clear_g_free(&priv->last_error);
check_timestamps(ndisc, now, changed); check_timestamps(ndisc, now_msec, changed);
} }
void void
@ -1525,7 +1569,7 @@ dispose(GObject *object)
nm_clear_g_source(&priv->send_ra_id); nm_clear_g_source(&priv->send_ra_id);
nm_clear_g_free(&priv->last_error); nm_clear_g_free(&priv->last_error);
nm_clear_g_source(&priv->timeout_id); nm_clear_g_source_inst(&priv->timeout_expire_source);
G_OBJECT_CLASS(nm_ndisc_parent_class)->dispose(object); G_OBJECT_CLASS(nm_ndisc_parent_class)->dispose(object);
} }

View file

@ -48,48 +48,91 @@ typedef enum {
NM_NDISC_DHCP_LEVEL_MANAGED NM_NDISC_DHCP_LEVEL_MANAGED
} NMNDiscDHCPLevel; } NMNDiscDHCPLevel;
/* we rely on the fact that NM_NDISC_INFINITY is the largest possible #define NM_NDISC_INFINITY_U32 ((uint32_t) -1)
* time duration (G_MAXUINT32) and that the range of finite values
* goes from 0 to G_MAXUINT32-1. */
#define NM_NDISC_INFINITY G_MAXUINT32
struct _NMNDiscGateway { /* It's important that this is G_MAXINT64, so that we can meaningfully do
* MIN(e1, e2) to find the minimum expiry time (and properly handle if any
* of them is infinity).
*
* While usually you assign this to "expiry_msec", you might say the
* 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)
{
if (lifetime == NM_NDISC_INFINITY_U32)
return NM_NDISC_EXPIRY_INFINITY;
return now_msec + (((gint64) lifetime) * 1000);
}
static inline gint64
_nm_ndisc_lifetime_from_expiry(gint64 now_msec, gint64 expiry_msec, gboolean ceil)
{
gint64 diff;
if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
return NM_NDISC_INFINITY_U32;
/* we don't expect nor handle integer overflow. The time stamp and expiry
* should be reasonably small so that it cannot happen. */
diff = expiry_msec - now_msec;
if (diff <= 0)
return 0;
if (ceil) {
/* we ceil() towards the next full second (instead of floor()). */
diff += 999;
}
return NM_MIN(diff / 1000, (gint64)(G_MAXUINT32 - 1));
}
/*****************************************************************************/
typedef struct _NMNDiscGateway {
struct in6_addr address; struct in6_addr address;
guint32 timestamp; gint64 expiry_msec;
guint32 lifetime;
NMIcmpv6RouterPref preference; NMIcmpv6RouterPref preference;
}; } NMNDiscGateway;
typedef struct _NMNDiscGateway NMNDiscGateway;
struct _NMNDiscAddress { typedef struct _NMNDiscAddress {
struct in6_addr address; struct in6_addr address;
gint64 expiry_msec;
gint64 expiry_preferred_msec;
guint8 dad_counter; guint8 dad_counter;
guint32 timestamp; } NMNDiscAddress;
guint32 lifetime;
guint32 preferred;
};
typedef struct _NMNDiscAddress NMNDiscAddress;
struct _NMNDiscRoute { typedef struct _NMNDiscRoute {
struct in6_addr network; struct in6_addr network;
guint8 plen;
struct in6_addr gateway; struct in6_addr gateway;
guint32 timestamp; gint64 expiry_msec;
guint32 lifetime;
NMIcmpv6RouterPref preference; NMIcmpv6RouterPref preference;
}; guint8 plen;
typedef struct _NMNDiscRoute NMNDiscRoute; } NMNDiscRoute;
typedef struct { typedef struct {
struct in6_addr address; struct in6_addr address;
guint32 timestamp; gint64 expiry_msec;
guint32 lifetime;
} NMNDiscDNSServer; } NMNDiscDNSServer;
typedef struct { typedef struct {
char * domain; char * domain;
guint32 timestamp; gint64 expiry_msec;
guint32 lifetime;
} NMNDiscDNSDomain; } NMNDiscDNSDomain;
typedef enum { typedef enum {
@ -113,6 +156,7 @@ typedef enum {
} NMNDiscNodeType; } NMNDiscNodeType;
#define NM_NDISC_MAX_ADDRESSES_DEFAULT 16 #define NM_NDISC_MAX_ADDRESSES_DEFAULT 16
#define NM_NDISC_MAX_RTR_SOLICITATION_DELAY 1 /* RFC4861 MAX_RTR_SOLICITATION_DELAY */
#define NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT 3 /* RFC4861 MAX_RTR_SOLICITATIONS */ #define NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT 3 /* RFC4861 MAX_RTR_SOLICITATIONS */
#define NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT 4 /* RFC4861 RTR_SOLICITATION_INTERVAL */ #define NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT 4 /* RFC4861 RTR_SOLICITATION_INTERVAL */
#define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT 3 /* RFC4861 MAX_INITIAL_RTR_ADVERTISEMENTS */ #define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT 3 /* RFC4861 MAX_INITIAL_RTR_ADVERTISEMENTS */

View file

@ -14,6 +14,8 @@
#include "nm-test-utils-core.h" #include "nm-test-utils-core.h"
/*****************************************************************************/
static NMFakeNDisc * static NMFakeNDisc *
ndisc_new(void) ndisc_new(void)
{ {
@ -26,19 +28,20 @@ ndisc_new(void)
iid.id_u8[7] = 1; iid.id_u8[7] = 1;
nm_ndisc_set_iid(ndisc, iid); nm_ndisc_set_iid(ndisc, iid);
g_assert(ndisc); g_assert(ndisc);
return NM_FAKE_NDISC(ndisc); return NM_FAKE_NDISC(ndisc);
} }
/*****************************************************************************/
static void static void
match_gateway(const NMNDiscData *rdata, match_gateway(const NMNDiscData *rdata,
guint idx, guint idx,
const char * addr, const char * addr,
guint32 ts, gint64 expiry_msec,
guint32 lt,
NMIcmpv6RouterPref pref) NMIcmpv6RouterPref pref)
{ {
const NMNDiscGateway *gw; const NMNDiscGateway *gw;
char buf[INET6_ADDRSTRLEN];
g_assert(rdata); g_assert(rdata);
g_assert_cmpint(idx, <, rdata->gateways_n); g_assert_cmpint(idx, <, rdata->gateways_n);
@ -46,63 +49,57 @@ match_gateway(const NMNDiscData *rdata,
gw = &rdata->gateways[idx]; gw = &rdata->gateways[idx];
g_assert_cmpstr(inet_ntop(AF_INET6, &gw->address, buf, sizeof(buf)), ==, addr); nmtst_assert_ip6_address(&gw->address, addr);
g_assert_cmpint(gw->timestamp, ==, ts); g_assert_cmpint(gw->expiry_msec, ==, expiry_msec);
g_assert_cmpint(gw->lifetime, ==, lt);
g_assert_cmpint(gw->preference, ==, pref); g_assert_cmpint(gw->preference, ==, pref);
} }
#define match_address(rdata, idx, addr, ts, lt, pref) \ #define match_address(rdata, idx, addr, _expiry_msec, _expiry_preferred_msec) \
G_STMT_START \ G_STMT_START \
{ \ { \
const NMNDiscData * _rdata = (rdata); \ const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \ guint _idx = (idx); \
const NMNDiscAddress *_a; \ const NMNDiscAddress *_a; \
guint _ts = (ts); \ \
\ g_assert(_rdata); \
g_assert(_rdata); \ g_assert_cmpint(_idx, <, _rdata->addresses_n); \
g_assert_cmpint(_idx, <, _rdata->addresses_n); \ g_assert(_rdata->addresses); \
g_assert(_rdata->addresses); \ \
\ _a = &_rdata->addresses[_idx]; \
_a = &_rdata->addresses[_idx]; \ \
\ nmtst_assert_ip6_address(&_a->address, (addr)); \
nmtst_assert_ip6_address(&_a->address, (addr)); \ g_assert_cmpint(_a->expiry_msec, ==, (_expiry_msec)); \
g_assert_cmpint(_a->timestamp, <=, _ts + 1); \ g_assert_cmpint(_a->expiry_preferred_msec, ==, (_expiry_preferred_msec)); \
g_assert_cmpint((int) _a->timestamp, >=, (int) _ts - 1); \ } \
g_assert_cmpint(_a->timestamp + _a->lifetime, ==, _ts + (lt)); \
g_assert_cmpint(_a->timestamp + _a->preferred, ==, _ts + (pref)); \
} \
G_STMT_END G_STMT_END
#define match_route(rdata, idx, nw, pl, gw, ts, lt, pref) \ #define match_route(rdata, idx, nw, pl, gw, _expiry_msec, pref) \
G_STMT_START \ G_STMT_START \
{ \ { \
const NMNDiscData * _rdata = (rdata); \ const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \ guint _idx = (idx); \
const NMNDiscRoute *_r; \ const NMNDiscRoute *_r; \
int _plen = (pl); \ int _plen = (pl); \
\ \
g_assert(_rdata); \ g_assert(_rdata); \
g_assert_cmpint(_idx, <, _rdata->routes_n); \ g_assert_cmpint(_idx, <, _rdata->routes_n); \
g_assert(_rdata->routes); \ g_assert(_rdata->routes); \
g_assert(_plen > 0 && _plen <= 128); \ g_assert(_plen > 0 && _plen <= 128); \
\ \
_r = &_rdata->routes[idx]; \ _r = &_rdata->routes[idx]; \
\ \
nmtst_assert_ip6_address(&_r->network, (nw)); \ nmtst_assert_ip6_address(&_r->network, (nw)); \
g_assert_cmpint((int) _r->plen, ==, _plen); \ g_assert_cmpint((int) _r->plen, ==, _plen); \
nmtst_assert_ip6_address(&_r->gateway, (gw)); \ nmtst_assert_ip6_address(&_r->gateway, (gw)); \
g_assert_cmpint(_r->timestamp, ==, (ts)); \ g_assert_cmpint(_r->expiry_msec, ==, (_expiry_msec)); \
g_assert_cmpint(_r->lifetime, ==, (lt)); \ g_assert_cmpint(_r->preference, ==, (pref)); \
g_assert_cmpint(_r->preference, ==, (pref)); \ } \
} \
G_STMT_END G_STMT_END
static void static void
match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt) match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, gint64 expiry_msec)
{ {
const NMNDiscDNSServer *dns; const NMNDiscDNSServer *dns;
char buf[INET6_ADDRSTRLEN];
g_assert(rdata); g_assert(rdata);
g_assert_cmpint(idx, <, rdata->dns_servers_n); g_assert_cmpint(idx, <, rdata->dns_servers_n);
@ -110,13 +107,12 @@ match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, guint32
dns = &rdata->dns_servers[idx]; dns = &rdata->dns_servers[idx];
g_assert_cmpstr(inet_ntop(AF_INET6, &dns->address, buf, sizeof(buf)), ==, addr); nmtst_assert_ip6_address(&dns->address, addr);
g_assert_cmpint(dns->timestamp, ==, ts); g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
g_assert_cmpint(dns->lifetime, ==, lt);
} }
static void static void
match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, guint32 ts, guint32 lt) match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, gint64 expiry_msec)
{ {
const NMNDiscDNSDomain *dns; const NMNDiscDNSDomain *dns;
@ -127,67 +123,96 @@ match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, guint3
dns = &rdata->dns_domains[idx]; dns = &rdata->dns_domains[idx];
g_assert_cmpstr(dns->domain, ==, domain); g_assert_cmpstr(dns->domain, ==, domain);
g_assert_cmpint(dns->timestamp, ==, ts); g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
g_assert_cmpint(dns->lifetime, ==, lt);
} }
/*****************************************************************************/
typedef struct { typedef struct {
GMainLoop *loop; GMainLoop *loop;
gint64 timestamp_msec_1;
guint counter; guint counter;
guint rs_counter; guint rs_counter;
guint32 timestamp1; gint64 first_solicit_msec;
guint32 first_solicit;
guint32 timeout_id; guint32 timeout_id;
} TestData; } TestData;
/*****************************************************************************/
static void static void
test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data) test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
{ {
NMNDiscConfigMap changed = changed_int; NMNDiscConfigMap changed = changed_int;
g_assert_cmpint(changed, switch (data->counter++) {
==, case 0:
NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS g_assert_cmpint(changed,
| NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES ==,
| NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
| NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU); | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF); | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10); match_gateway(rdata,
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10); 0,
match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10); "fe80::1",
data->timestamp_msec_1 + 10000,
NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_address(rdata,
0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1 + 10000);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10);
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000);
match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 3500);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
data->counter++; break;
g_main_loop_quit(data->loop); case 1:
g_main_loop_quit(data->loop);
break;
default:
g_assert_not_reached();
}
} }
static void static void
test_simple(void) test_simple(void)
{ {
NMFakeNDisc *ndisc = ndisc_new(); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
guint32 now = nm_utils_get_monotonic_timestamp_sec(); gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now}; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10); nm_fake_ndisc_add_prefix(ndisc,
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10); id,
"2001:db8:a:a::",
64,
"fe80::1",
now_msec + 10000,
now_msec + 10000,
10);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 3500);
g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data); g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data);
nm_ndisc_start(NM_NDISC(ndisc)); nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000); nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 1); g_assert_cmpint(data.counter, ==, 2);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
} }
/*****************************************************************************/
static void static void
test_everything_rs_sent(NMNDisc *ndisc, TestData *data) test_everything_rs_sent(NMNDisc *ndisc, TestData *data)
{ {
@ -208,11 +233,19 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
| NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
| NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
| NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU); | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); match_gateway(rdata,
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); 0,
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10); "fe80::1",
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10); data->timestamp_msec_1 + 10000,
match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10); NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_address(rdata,
0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1 + 10000);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10);
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000);
match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 10000);
} else if (data->counter == 1) { } else if (data->counter == 1) {
g_assert_cmpint(changed, g_assert_cmpint(changed,
==, ==,
@ -221,16 +254,28 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
| NM_NDISC_CONFIG_DNS_DOMAINS); | NM_NDISC_CONFIG_DNS_DOMAINS);
g_assert_cmpint(rdata->gateways_n, ==, 1); g_assert_cmpint(rdata->gateways_n, ==, 1);
match_gateway(rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); match_gateway(rdata,
0,
"fe80::2",
data->timestamp_msec_1 + 10000,
NM_ICMPV6_ROUTER_PREF_MEDIUM);
g_assert_cmpint(rdata->addresses_n, ==, 2); g_assert_cmpint(rdata->addresses_n, ==, 2);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0); match_address(rdata,
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10); 0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1);
match_address(rdata,
1,
"2001:db8:a:b::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1 + 10000);
g_assert_cmpint(rdata->routes_n, ==, 1); g_assert_cmpint(rdata->routes_n, ==, 1);
match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10); match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 10000, 10);
g_assert_cmpint(rdata->dns_servers_n, ==, 1); g_assert_cmpint(rdata->dns_servers_n, ==, 1);
match_dns_server(rdata, 0, "2001:db8:c:c::2", data->timestamp1, 10); match_dns_server(rdata, 0, "2001:db8:c:c::2", data->timestamp_msec_1 + 10000);
g_assert_cmpint(rdata->dns_domains_n, ==, 1); g_assert_cmpint(rdata->dns_domains_n, ==, 1);
match_dns_domain(rdata, 0, "foobar2.com", data->timestamp1, 10); match_dns_domain(rdata, 0, "foobar2.com", data->timestamp_msec_1 + 10000);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
g_main_loop_quit(data->loop); g_main_loop_quit(data->loop);
@ -243,31 +288,49 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
static void static void
test_everything(void) test_everything(void)
{ {
NMFakeNDisc *ndisc = ndisc_new(); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
guint32 now = nm_utils_get_monotonic_timestamp_sec(); gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now}; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10); nm_fake_ndisc_add_prefix(ndisc,
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10); id,
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10); "2001:db8:a:a::",
64,
"fe80::1",
now_msec + 10000,
now_msec + 10000,
10);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 10000);
/* expire everything from the first RA in the second */ /* expire everything from the first RA in the second */
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 0, NM_ICMPV6_ROUTER_PREF_MEDIUM); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 0, 0, 0); nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now_msec, now_msec, 0);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 0); nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 0); nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec);
/* and add some new stuff */ /* and add some new stuff */
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10); nm_fake_ndisc_add_prefix(ndisc,
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now, 10); id,
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now, 10); "2001:db8:a:b::",
64,
"fe80::2",
now_msec + 10000,
now_msec + 10000,
10);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now_msec + 10000);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now_msec + 10000);
g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_everything_changed), &data); g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_everything_changed), &data);
g_signal_connect(ndisc, NM_FAKE_NDISC_RS_SENT, G_CALLBACK(test_everything_rs_sent), &data); g_signal_connect(ndisc, NM_FAKE_NDISC_RS_SENT, G_CALLBACK(test_everything_rs_sent), &data);
@ -276,9 +339,6 @@ test_everything(void)
nmtst_main_loop_run_assert(data.loop, 15000); nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 2); g_assert_cmpint(data.counter, ==, 2);
g_assert_cmpint(data.rs_counter, ==, 1); g_assert_cmpint(data.rs_counter, ==, 1);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
} }
static void static void
@ -296,14 +356,30 @@ test_preference_order_cb(NMNDisc * ndisc,
| NM_NDISC_CONFIG_ROUTES); | NM_NDISC_CONFIG_ROUTES);
g_assert_cmpint(rdata->gateways_n, ==, 2); g_assert_cmpint(rdata->gateways_n, ==, 2);
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_HIGH); match_gateway(rdata,
match_gateway(rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_LOW); 0,
"fe80::1",
data->timestamp_msec_1 + 10000,
NM_ICMPV6_ROUTER_PREF_HIGH);
match_gateway(rdata,
1,
"fe80::2",
data->timestamp_msec_1 + 11000,
NM_ICMPV6_ROUTER_PREF_LOW);
g_assert_cmpint(rdata->addresses_n, ==, 2); g_assert_cmpint(rdata->addresses_n, ==, 2);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); match_address(rdata,
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10); 0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1 + 10000);
match_address(rdata,
1,
"2001:db8:a:b::1",
data->timestamp_msec_1 + 11000,
data->timestamp_msec_1 + 10000);
g_assert_cmpint(rdata->routes_n, ==, 2); g_assert_cmpint(rdata->routes_n, ==, 2);
match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10); match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5); match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
g_main_loop_quit(data->loop); g_main_loop_quit(data->loop);
@ -315,31 +391,46 @@ test_preference_order_cb(NMNDisc * ndisc,
static void static void
test_preference_order(void) test_preference_order(void)
{ {
NMFakeNDisc *ndisc = ndisc_new(); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
guint32 now = nm_utils_get_monotonic_timestamp_sec(); gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now}; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
/* Test insertion order of gateways */ /* Test insertion order of gateways */
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_HIGH); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_HIGH);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5); nm_fake_ndisc_add_prefix(ndisc,
id,
"2001:db8:a:a::",
64,
"fe80::1",
now_msec + 10000,
now_msec + 10000,
5);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_LOW); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_LOW);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10); nm_fake_ndisc_add_prefix(ndisc,
id,
"2001:db8:a:b::",
64,
"fe80::2",
now_msec + 11000,
now_msec + 10000,
10);
g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data); g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data);
nm_ndisc_start(NM_NDISC(ndisc)); nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000); nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 2); g_assert_cmpint(data.counter, ==, 2);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
} }
static void static void
@ -356,14 +447,30 @@ test_preference_changed_cb(NMNDisc * ndisc,
NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
| NM_NDISC_CONFIG_ROUTES); | NM_NDISC_CONFIG_ROUTES);
g_assert_cmpint(rdata->gateways_n, ==, 2); g_assert_cmpint(rdata->gateways_n, ==, 2);
match_gateway(rdata, 0, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); match_gateway(rdata,
match_gateway(rdata, 1, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_LOW); 0,
"fe80::2",
data->timestamp_msec_1 + 11000,
NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_gateway(rdata,
1,
"fe80::1",
data->timestamp_msec_1 + 10000,
NM_ICMPV6_ROUTER_PREF_LOW);
g_assert_cmpint(rdata->addresses_n, ==, 2); g_assert_cmpint(rdata->addresses_n, ==, 2);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); match_address(rdata,
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10); 0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 10000,
data->timestamp_msec_1 + 10000);
match_address(rdata,
1,
"2001:db8:a:b::1",
data->timestamp_msec_1 + 11000,
data->timestamp_msec_1 + 11000);
g_assert_cmpint(rdata->routes_n, ==, 2); g_assert_cmpint(rdata->routes_n, ==, 2);
match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10); match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5); match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5);
} else if (data->counter == 2) { } else if (data->counter == 2) {
g_assert_cmpint(changed, g_assert_cmpint(changed,
==, ==,
@ -371,14 +478,30 @@ test_preference_changed_cb(NMNDisc * ndisc,
| NM_NDISC_CONFIG_ROUTES); | NM_NDISC_CONFIG_ROUTES);
g_assert_cmpint(rdata->gateways_n, ==, 2); g_assert_cmpint(rdata->gateways_n, ==, 2);
match_gateway(rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_ICMPV6_ROUTER_PREF_HIGH); match_gateway(rdata,
match_gateway(rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); 0,
"fe80::1",
data->timestamp_msec_1 + 12000,
NM_ICMPV6_ROUTER_PREF_HIGH);
match_gateway(rdata,
1,
"fe80::2",
data->timestamp_msec_1 + 11000,
NM_ICMPV6_ROUTER_PREF_MEDIUM);
g_assert_cmpint(rdata->addresses_n, ==, 2); g_assert_cmpint(rdata->addresses_n, ==, 2);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 3, 9, 9); match_address(rdata,
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10); 0,
"2001:db8:a:a::1",
data->timestamp_msec_1 + 12000,
data->timestamp_msec_1 + 12000);
match_address(rdata,
1,
"2001:db8:a:b::1",
data->timestamp_msec_1 + 11000,
data->timestamp_msec_1 + 11000);
g_assert_cmpint(rdata->routes_n, ==, 2); g_assert_cmpint(rdata->routes_n, ==, 2);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15); match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 12000, 15);
match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10); match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc))); g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
g_main_loop_quit(data->loop); g_main_loop_quit(data->loop);
@ -390,10 +513,14 @@ test_preference_changed_cb(NMNDisc * ndisc,
static void static void
test_preference_changed(void) test_preference_changed(void)
{ {
NMFakeNDisc *ndisc = ndisc_new(); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
guint32 now = nm_utils_get_monotonic_timestamp_sec(); gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now}; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
/* Test that when a low-preference and medium gateway send advertisements, /* Test that when a low-preference and medium gateway send advertisements,
* that if the low-preference gateway switches to high-preference, we do * that if the low-preference gateway switches to high-preference, we do
@ -402,18 +529,39 @@ test_preference_changed(void)
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5); nm_fake_ndisc_add_prefix(ndisc,
id,
"2001:db8:a:a::",
64,
"fe80::1",
now_msec + 10000,
now_msec + 10000,
5);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10); nm_fake_ndisc_add_prefix(ndisc,
id,
"2001:db8:a:b::",
64,
"fe80::2",
now_msec + 11000,
now_msec + 11000,
10);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", ++now, 10, NM_ICMPV6_ROUTER_PREF_HIGH); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 12000, NM_ICMPV6_ROUTER_PREF_HIGH);
nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 15); nm_fake_ndisc_add_prefix(ndisc,
id,
"2001:db8:a:a::",
64,
"fe80::1",
now_msec + 12000,
now_msec + 12000,
15);
g_signal_connect(ndisc, g_signal_connect(ndisc,
NM_NDISC_CONFIG_RECEIVED, NM_NDISC_CONFIG_RECEIVED,
@ -423,16 +571,15 @@ test_preference_changed(void)
nm_ndisc_start(NM_NDISC(ndisc)); nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000); nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 3); g_assert_cmpint(data.counter, ==, 3);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
} }
/*****************************************************************************/
static void static void
test_dns_solicit_loop_changed(NMNDisc * ndisc, _test_dns_solicit_loop_changed(NMNDisc * ndisc,
const NMNDiscData *rdata, const NMNDiscData *rdata,
guint changed_int, guint changed_int,
TestData * data) TestData * data)
{ {
data->counter++; data->counter++;
} }
@ -446,14 +593,14 @@ success_timeout(TestData *data)
} }
static void static void
test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data) _test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
{ {
guint32 now = nm_utils_get_monotonic_timestamp_sec(); const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; guint id;
if (data->rs_counter > 0 && data->rs_counter < 6) { if (data->rs_counter > 0 && data->rs_counter < 6) {
if (data->rs_counter == 1) { if (data->rs_counter == 1) {
data->first_solicit = now; data->first_solicit_msec = now_msec;
/* Kill the test after 10 seconds if it hasn't failed yet */ /* Kill the test after 10 seconds if it hasn't failed yet */
data->timeout_id = g_timeout_add_seconds(10, (GSourceFunc) success_timeout, data); data->timeout_id = g_timeout_add_seconds(10, (GSourceFunc) success_timeout, data);
} }
@ -464,12 +611,16 @@ test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
*/ */
id = nm_fake_ndisc_add_ra(ndisc, 0, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 0, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM); nm_fake_ndisc_add_gateway(ndisc,
id,
"fe80::1",
now_msec + 10000,
NM_ICMPV6_ROUTER_PREF_MEDIUM);
nm_fake_ndisc_emit_new_ras(ndisc); nm_fake_ndisc_emit_new_ras(ndisc);
} else if (data->rs_counter >= 6) { } else if (data->rs_counter >= 6) {
/* Fail if we've sent too many solicitations in the past 4 seconds */ /* Fail if we've sent too many solicitations in the past 4 seconds */
g_assert_cmpint(now - data->first_solicit, >, 4); g_assert_cmpint(now_msec - data->first_solicit_msec, >, 4000);
g_source_remove(data->timeout_id); g_source_remove(data->timeout_id);
g_main_loop_quit(data->loop); g_main_loop_quit(data->loop);
} }
@ -479,10 +630,14 @@ test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
static void static void
test_dns_solicit_loop(void) test_dns_solicit_loop(void)
{ {
NMFakeNDisc *ndisc = ndisc_new(); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
guint32 now = nm_utils_get_monotonic_timestamp_sec(); gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now, 0}; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id; TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
g_test_skip("The solicitation behavior is wrong and need fixing. This test is not working too"); g_test_skip("The solicitation behavior is wrong and need fixing. This test is not working too");
return; return;
@ -496,26 +651,25 @@ test_dns_solicit_loop(void)
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500); id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id); g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW); nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 6); nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 6000);
g_signal_connect(ndisc, g_signal_connect(ndisc,
NM_NDISC_CONFIG_RECEIVED, NM_NDISC_CONFIG_RECEIVED,
G_CALLBACK(test_dns_solicit_loop_changed), G_CALLBACK(_test_dns_solicit_loop_changed),
&data); &data);
g_signal_connect(ndisc, g_signal_connect(ndisc,
NM_FAKE_NDISC_RS_SENT, NM_FAKE_NDISC_RS_SENT,
G_CALLBACK(test_dns_solicit_loop_rs_sent), G_CALLBACK(_test_dns_solicit_loop_rs_sent),
&data); &data);
nm_ndisc_start(NM_NDISC(ndisc)); nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 20000); nmtst_main_loop_run_assert(data.loop, 20000);
g_assert_cmpint(data.counter, ==, 3); g_assert_cmpint(data.counter, ==, 3);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
} }
/*****************************************************************************/
NMTST_DEFINE(); NMTST_DEFINE();
int int

View file

@ -1497,6 +1497,7 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self,
NMIP6ConfigPrivate *priv; NMIP6ConfigPrivate *priv;
guint i; guint i;
gboolean changed = FALSE; gboolean changed = FALSE;
gint32 base_time_sec;
g_return_if_fail(NM_IS_IP6_CONFIG(self)); g_return_if_fail(NM_IS_IP6_CONFIG(self));
@ -1504,6 +1505,16 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self,
g_return_if_fail(priv->ifindex > 0); g_return_if_fail(priv->ifindex > 0);
/* the base-timestamp doesn't matter it's only an anchor for the
* expiry. However, try to re-use the same base-time for a while
* by rounding it to 10000 seconds.
*
* That is because we deduplicate and NMPlatformIP6Address instances
* so using the same timestamps is preferable. */
base_time_sec = nm_utils_get_monotonic_timestamp_sec();
base_time_sec = (base_time_sec / 10000) * 10000;
base_time_sec = NM_MAX(1, base_time_sec);
nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_addresses); nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_addresses);
for (i = 0; i < addresses_n; i++) { for (i = 0; i < addresses_n; i++) {
@ -1516,9 +1527,13 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self,
a->ifindex = priv->ifindex; a->ifindex = priv->ifindex;
a->address = ndisc_addr->address; a->address = ndisc_addr->address;
a->plen = plen; a->plen = plen;
a->timestamp = ndisc_addr->timestamp; a->timestamp = base_time_sec,
a->lifetime = ndisc_addr->lifetime; a->lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000,
a->preferred = MIN(ndisc_addr->lifetime, ndisc_addr->preferred); ndisc_addr->expiry_msec,
TRUE),
a->preferred = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000,
ndisc_addr->expiry_preferred_msec,
TRUE),
a->addr_source = NM_IP_CONFIG_SOURCE_NDISC; a->addr_source = NM_IP_CONFIG_SOURCE_NDISC;
a->n_ifa_flags = ifa_flags; a->n_ifa_flags = ifa_flags;