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
ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
gint32 now;
GArray * addresses, *dns_servers, *dns_domains;
guint len, i;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_unref_array GArray *addresses = NULL;
gs_unref_array GArray *dns_servers = NULL;
gs_unref_array GArray * dns_domains = NULL;
guint len;
guint i;
const NMDedupMultiHeadEntry *head_entry;
NMDedupMultiIter ipconf_iter;
if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
return;
now = nm_utils_get_monotonic_timestamp_sec();
head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6);
addresses =
g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0);
nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj);
NMNDiscAddress * ndisc_addr;
guint32 lifetime, preferred;
gint32 base;
guint32 lifetime;
guint32 preferred;
if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
continue;
@ -5178,31 +5178,21 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
if (addr->plen != 64)
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,
addr->lifetime,
addr->preferred,
base,
NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
&preferred);
if (!lifetime)
continue;
g_array_set_size(addresses, addresses->len + 1);
ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
ndisc_addr->address = addr->address;
ndisc_addr->timestamp = base;
ndisc_addr->lifetime = lifetime;
ndisc_addr->preferred = preferred;
ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
ndisc_addr->address = addr->address;
ndisc_addr->expiry_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime);
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);
@ -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);
NMNDiscDNSServer * ndisc_nameserver;
ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i);
ndisc_nameserver->address = *nameserver;
ndisc_nameserver->timestamp = now;
ndisc_nameserver->lifetime = NM_NDISC_ROUTER_LIFETIME;
ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i);
ndisc_nameserver->address = *nameserver;
ndisc_nameserver->expiry_msec =
_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);
@ -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);
NMNDiscDNSDomain *ndisc_search;
ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
ndisc_search->domain = (char *) search;
ndisc_search->timestamp = now;
ndisc_search->lifetime = NM_NDISC_ROUTER_LIFETIME;
ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
ndisc_search->domain = (char *) search;
ndisc_search->expiry_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
}
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

View file

@ -30,11 +30,10 @@ typedef struct {
typedef struct {
struct in6_addr network;
int plen;
struct in6_addr gateway;
guint32 timestamp;
guint32 lifetime;
guint32 preferred;
gint64 expiry_msec;
gint64 expiry_preferred_msec;
int plen;
NMIcmpv6RouterPref preference;
} FakePrefix;
@ -128,8 +127,7 @@ void
nm_fake_ndisc_add_gateway(NMFakeNDisc * self,
guint ra_id,
const char * addr,
guint32 timestamp,
guint32 lifetime,
gint64 expiry_msec,
NMIcmpv6RouterPref preference)
{
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@ -137,12 +135,12 @@ nm_fake_ndisc_add_gateway(NMFakeNDisc * self,
NMNDiscGateway * gw;
g_assert(ra);
g_array_set_size(ra->gateways, ra->gateways->len + 1);
gw = &g_array_index(ra->gateways, NMNDiscGateway, ra->gateways->len - 1);
g_assert(inet_pton(AF_INET6, addr, &gw->address) == 1);
gw->timestamp = timestamp;
gw->lifetime = lifetime;
gw->preference = preference;
gw = nm_g_array_append_new(ra->gateways, NMNDiscGateway);
if (inet_pton(AF_INET6, addr, &gw->address) != 1)
g_assert_not_reached();
gw->expiry_msec = expiry_msec;
gw->preference = preference;
}
void
@ -151,9 +149,8 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
const char * network,
guint plen,
const char * gateway,
guint32 timestamp,
guint32 lifetime,
guint32 preferred,
gint64 expiry_msec,
gint64 expiry_preferred_msec,
NMIcmpv6RouterPref preference)
{
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@ -161,54 +158,52 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
FakePrefix * prefix;
g_assert(ra);
g_array_set_size(ra->prefixes, ra->prefixes->len + 1);
prefix = &g_array_index(ra->prefixes, FakePrefix, ra->prefixes->len - 1);
memset(prefix, 0, sizeof(*prefix));
g_assert(inet_pton(AF_INET6, network, &prefix->network) == 1);
g_assert(inet_pton(AF_INET6, gateway, &prefix->gateway) == 1);
prefix->plen = plen;
prefix->timestamp = timestamp;
prefix->lifetime = lifetime;
prefix->preferred = preferred;
prefix->preference = preference;
prefix = nm_g_array_append_new(ra->prefixes, FakePrefix);
*prefix = (FakePrefix){
.plen = plen,
.expiry_msec = expiry_msec,
.expiry_preferred_msec = expiry_preferred_msec,
.preference = preference,
};
if (inet_pton(AF_INET6, network, &prefix->network) != 1)
g_assert_not_reached();
if (inet_pton(AF_INET6, gateway, &prefix->gateway) != 1)
g_assert_not_reached();
}
void
nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
guint ra_id,
const char * address,
guint32 timestamp,
guint32 lifetime)
gint64 expiry_msec)
{
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
FakeRa * ra = find_ra(priv->ras, ra_id);
NMNDiscDNSServer * dns;
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);
g_assert(inet_pton(AF_INET6, address, &dns->address) == 1);
dns->timestamp = timestamp;
dns->lifetime = lifetime;
dns = nm_g_array_append_new(ra->dns_servers, NMNDiscDNSServer);
dns->expiry_msec = expiry_msec;
if (inet_pton(AF_INET6, address, &dns->address) != 1)
g_assert_not_reached();
}
void
nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
guint ra_id,
const char * domain,
guint32 timestamp,
guint32 lifetime)
nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, guint ra_id, const char *domain, gint64 expiry_msec)
{
NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
FakeRa * ra = find_ra(priv->ras, ra_id);
NMNDiscDNSDomain * dns;
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->domain = g_strdup(domain);
dns->timestamp = timestamp;
dns->lifetime = lifetime;
dns = nm_g_array_append_new(ra->dns_domains, NMNDiscDNSDomain);
dns->domain = g_strdup(domain);
dns->expiry_msec = expiry_msec;
}
gboolean
@ -230,13 +225,13 @@ send_rs(NMNDisc *ndisc, GError **error)
static gboolean
receive_ra(gpointer user_data)
{
NMFakeNDisc * self = user_data;
NMFakeNDiscPrivate * priv = NM_FAKE_NDISC_GET_PRIVATE(self);
NMNDisc * ndisc = NM_NDISC(self);
NMNDiscDataInternal *rdata = ndisc->rdata;
FakeRa * ra = priv->ras->data;
NMNDiscConfigMap changed = 0;
gint32 now = nm_utils_get_monotonic_timestamp_sec();
NMFakeNDisc * self = user_data;
NMFakeNDiscPrivate * priv = NM_FAKE_NDISC_GET_PRIVATE(self);
NMNDisc * ndisc = NM_NDISC(self);
NMNDiscDataInternal *rdata = ndisc->rdata;
FakeRa * ra = priv->ras->data;
NMNDiscConfigMap changed = 0;
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint i;
NMNDiscDHCPLevel dhcp_level;
@ -251,53 +246,51 @@ receive_ra(gpointer user_data)
}
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;
}
for (i = 0; i < ra->prefixes->len; i++) {
FakePrefix * item = &g_array_index(ra->prefixes, FakePrefix, i);
NMNDiscRoute route = {
.network = item->network,
.plen = item->plen,
.gateway = item->gateway,
.timestamp = item->timestamp,
.lifetime = item->lifetime,
.preference = item->preference,
FakePrefix * item = &g_array_index(ra->prefixes, FakePrefix, i);
const NMNDiscRoute route = {
.network = item->network,
.plen = item->plen,
.gateway = item->gateway,
.expiry_msec = item->expiry_msec,
.preference = item->preference,
};
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;
if (item->plen == 64) {
NMNDiscAddress address = {
.address = item->network,
.timestamp = item->timestamp,
.lifetime = item->lifetime,
.preferred = item->preferred,
.dad_counter = 0,
const NMNDiscAddress address = {
.address = item->network,
.expiry_msec = item->expiry_msec,
.expiry_preferred_msec = item->expiry_preferred_msec,
.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;
}
}
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;
}
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;
}
@ -314,7 +307,7 @@ receive_ra(gpointer user_data)
priv->ras = g_slist_remove(priv->ras, priv->ras->data);
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 */
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,
guint ra_id,
const char * addr,
guint32 timestamp,
guint32 lifetime,
gint64 expiry_msec,
NMIcmpv6RouterPref preference);
void nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
@ -44,22 +43,19 @@ void nm_fake_ndisc_add_prefix(NMFakeNDisc * self,
const char * network,
guint plen,
const char * gateway,
guint32 timestamp,
guint32 lifetime,
guint32 preferred,
gint64 expiry_msec,
gint64 expiry_preferred_msec,
NMIcmpv6RouterPref preference);
void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
guint ra_id,
const char * address,
guint32 timestamp,
guint32 lifetime);
gint64 expiry_msec);
void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
guint ra_id,
const char * domain,
guint32 timestamp,
guint32 lifetime);
gint64 expiry_msec);
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;
struct ndp_msgra * msgra = ndp_msgra(msg);
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 hop_limit;
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
* 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);
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 = {
.address = gateway_addr,
.timestamp = now,
.lifetime = ndp_msgra_router_lifetime(msgra),
.preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)),
.address = gateway_addr,
.expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msgra_router_lifetime(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;
}
@ -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)) {
const NMNDiscRoute route = {
.network = r_network,
.plen = r_plen,
.timestamp = now,
.lifetime = ndp_msg_opt_prefix_valid_time(msg, offset),
.network = r_network,
.plen = r_plen,
.expiry_msec =
_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;
}
/* Address */
if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) {
NMNDiscAddress address = {
.address = r_network,
.timestamp = now,
.lifetime = ndp_msg_opt_prefix_valid_time(msg, offset),
.preferred = ndp_msg_opt_prefix_preferred_time(msg, offset),
const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset);
const guint32 preferred_time =
NM_MIN(ndp_msg_opt_prefix_preferred_time(msg, offset), valid_time);
const NMNDiscAddress address = {
.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))
changed |= NM_NDISC_CONFIG_ADDRESSES;
}
if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec))
changed |= NM_NDISC_CONFIG_ADDRESSES;
}
}
ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) {
NMNDiscRoute route = {
.gateway = gateway_addr,
.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)),
};
guint8 plen = ndp_msg_opt_route_prefix_len(msg, offset);
struct in6_addr network;
if (route.plen == 0 || route.plen > 128)
if (plen == 0 || plen > 128)
continue;
/* Routers through this particular gateway */
nm_utils_ip6_address_clear_host_address(&route.network,
nm_utils_ip6_address_clear_host_address(&network,
ndp_msg_opt_route_prefix(msg, offset),
route.plen);
if (nm_ndisc_add_route(ndisc, &route))
changed |= NM_NDISC_CONFIG_ROUTES;
plen);
{
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) {
struct in6_addr *addr;
int addr_index;
ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) {
NMNDiscDNSServer dns_server = {
.address = *addr,
.timestamp = now,
.lifetime = ndp_msg_opt_rdnss_lifetime(msg, offset),
const NMNDiscDNSServer dns_server = {
.address = *addr,
.expiry_msec =
_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;
}
}
@ -270,13 +284,13 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
int domain_index;
ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) {
NMNDiscDNSDomain dns_domain = {
.domain = domain,
.timestamp = now,
.lifetime = ndp_msg_opt_dnssl_lifetime(msg, offset),
const NMNDiscDNSDomain dns_domain = {
.domain = domain,
.expiry_msec =
_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;
}
}
@ -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;
}
@ -374,7 +388,6 @@ send_ra(NMNDisc *ndisc, GError **error)
{
NMLndpNDiscPrivate * priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
NMNDiscDataInternal * rdata = ndisc->rdata;
gint32 now = nm_utils_get_monotonic_timestamp_sec();
int errsv;
struct in6_addr * addr;
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
* whose prefixes are suitable for delegating. Let's announce them. */
for (i = 0; i < rdata->addresses->len; 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;
const NMNDiscAddress * address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
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));
if (!prefix) {
/* 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_flags_reserved |= ND_OPT_PI_FLAG_ONLINK;
prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
prefix->nd_opt_pi_valid_time = htonl(lifetime);
prefix->nd_opt_pi_preferred_time = htonl(preferred);
prefix->nd_opt_pi_valid_time =
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[1] = address->address.s6_addr32[1];
prefix->nd_opt_pi_prefix.s6_addr32[2] = 0;

View file

@ -21,14 +21,15 @@ struct _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);
gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new);
gboolean nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s);
gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new);
gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new);
gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *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_item, gint64 now_msec);
gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec);
gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec);
gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec);
/*****************************************************************************/

View file

@ -41,7 +41,9 @@ struct _NMNDiscPrivate {
gint32 last_rs;
gint32 last_ra;
};
guint timeout_id; /* prefix/dns/etc lifetime timeout */
GSource *timeout_expire_source;
NMUtilsIPv6IfaceId iid;
/* 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;
guint8 plen;
guint i;
const gint32 now_sec = nm_utils_get_monotonic_timestamp_sec();
l3cd = nm_l3_config_data_new(multi_idx, ifindex);
@ -129,12 +133,17 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex * multi_idx,
NMPlatformIP6Address a;
a = (NMPlatformIP6Address){
.ifindex = ifindex,
.address = ndisc_addr->address,
.plen = plen,
.timestamp = ndisc_addr->timestamp,
.lifetime = ndisc_addr->lifetime,
.preferred = MIN(ndisc_addr->lifetime, ndisc_addr->preferred),
.ifindex = ifindex,
.address = ndisc_addr->address,
.plen = plen,
.timestamp = now_sec,
.lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) now_sec) * 1000,
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,
.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
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_timestamp == _EXPIRY_INFINITY)
if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
return TRUE;
e = MIN(expiry_timestamp, ((gint64)(G_MAXINT32 - 1)));
if (now_s >= e)
if (expiry_msec <= now_msec) {
/* expired. */
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;
}
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;
if (expiry_time == _EXPIRY_INFINITY)
if (expiry_time == NM_NDISC_EXPIRY_INFINITY)
return "permanent";
l = g_snprintf(buf,
buf_size,
"%.4f",
((double) ((expiry_time * NM_UTILS_NSEC_PER_SEC) - now_ns))
/ ((double) NM_UTILS_NSEC_PER_SEC));
l = g_snprintf(buf, buf_size, "%.3f", ((double) (expiry_time - now_msec)) / 1000);
nm_assert(l < buf_size);
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);
nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&item->address));
nm_assert(item->timestamp > 0 && item->timestamp <= G_MAXINT32);
for (j = 0; j < i; j++) {
const NMNDiscGateway *item2 = &g_array_index(data->gateways, NMNDiscGateway, j);
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)
>= _preference_to_priority(item->preference));
}
item_prev = item;
}
@ -408,7 +388,7 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
/*****************************************************************************/
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;
guint i;
@ -417,41 +397,43 @@ nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new)
for (i = 0; i < rdata->gateways->len;) {
NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
if (new->lifetime == 0) {
if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->gateways, i);
_ASSERT_data_gateways(rdata);
return TRUE;
}
if (item->preference != new->preference) {
if (item->preference != new_item->preference) {
g_array_remove_index(rdata->gateways, i);
continue;
}
if (get_expiry(item) == get_expiry(new))
if (item->expiry_msec == new_item->expiry_msec)
return FALSE;
*item = *new;
item->expiry_msec = new_item->expiry_msec;
_ASSERT_data_gateways(rdata);
return TRUE;
}
/* 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 = i;
i++;
}
if (new->lifetime) {
g_array_insert_val(rdata->gateways,
insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
*new);
}
if (new_item->expiry_msec <= now_msec)
return FALSE;
g_array_insert_val(rdata->gateways,
insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
*new_item);
_ASSERT_data_gateways(rdata);
return !!new->lifetime;
return TRUE;
}
/**
@ -504,25 +486,27 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
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;
}
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);
NMNDiscDataInternal *rdata = &priv->rdata;
NMNDiscAddress new2;
NMNDiscAddress * new2;
NMNDiscAddress * existing = NULL;
guint i;
nm_assert(new);
nm_assert(new->timestamp > 0 && new->timestamp < G_MAXINT32);
nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new->address));
nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new->address));
nm_assert(new->preferred <= new->lifetime);
nm_assert(!from_ra || now_s > 0);
nm_assert(new_item);
nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new_item->address));
nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new_item->address));
nm_assert(new_item->expiry_preferred_msec <= new_item->expiry_msec);
nm_assert((!!from_ra) == (now_msec > 0));
for (i = 0; i < rdata->addresses->len; 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) {
/* 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). */
if (memcmp(&item->address, &new->address, 8) == 0) {
if (memcmp(&item->address, &new_item->address, 8) == 0) {
existing = item;
break;
}
} else {
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
existing = item;
break;
}
@ -543,67 +527,60 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
}
if (existing) {
gint64 new_expiry_preferred_msec;
gint64 new_expiry_msec;
if (from_ra) {
const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
gint64 old_expiry_lifetime, old_expiry_preferred;
old_expiry_lifetime = get_expiry(existing);
old_expiry_preferred = get_expiry_preferred(existing);
if (new->lifetime == NM_NDISC_INFINITY)
existing->lifetime = NM_NDISC_INFINITY;
if (new_item->expiry_msec == NM_NDISC_EXPIRY_INFINITY)
new_expiry_msec = NM_NDISC_EXPIRY_INFINITY;
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 */
if (existing->lifetime == NM_NDISC_INFINITY)
remaining_lifetime = G_MAXINT64;
else
remaining_lifetime = ((gint64) existing->timestamp)
+ ((gint64) existing->lifetime) - ((gint64) now_s);
new_lifetime =
((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) {
if (new_lifetime >= NDISC_PREFIX_LFT_MIN_MSEC
|| new_lifetime >= existing_lifetime) {
/* either extend the lifetime of the new_item lifetime is longer than
* NDISC_PREFIX_LFT_MIN_MSEC. */
new_expiry_msec = new_item->expiry_msec;
} else if (existing_lifetime <= NDISC_PREFIX_LFT_MIN_MSEC) {
/* keep the current lifetime. */
new_expiry_msec = existing->expiry_msec;
} else {
existing->timestamp = now_s;
existing->lifetime = NM_NDISC_PREFIX_LFT_MIN;
/* trim the current lifetime to NDISC_PREFIX_LFT_MIN_MSEC. */
new_expiry_msec = now_msec + NDISC_PREFIX_LFT_MIN_MSEC;
}
}
if (new->preferred == NM_NDISC_INFINITY) {
nm_assert(existing->lifetime == NM_NDISC_INFINITY);
existing->preferred = new->preferred;
} else {
existing->preferred = NM_CLAMP(((gint64) new->timestamp) + ((gint64) new->preferred)
- ((gint64) existing->timestamp),
0,
G_MAXUINT32 - 1);
if (existing->lifetime != NM_NDISC_INFINITY)
existing->preferred = MIN(existing->preferred, existing->lifetime);
new_expiry_preferred_msec =
NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
new_expiry_preferred_msec = NM_MIN(new_expiry_preferred_msec, new_expiry_msec);
} else {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->addresses, i);
return TRUE;
}
return old_expiry_lifetime != get_expiry(existing)
|| old_expiry_preferred != get_expiry_preferred(existing);
new_expiry_msec = new_item->expiry_msec;
new_expiry_preferred_msec =
NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
}
if (new->lifetime == 0) {
g_array_remove_index(rdata->addresses, i);
return TRUE;
}
if (get_expiry(existing) == get_expiry(new)
&& get_expiry_preferred(existing) == get_expiry_preferred(new))
/* the dad_counter does not get modified. */
if (new_expiry_msec == existing->expiry_msec
&& new_expiry_preferred_msec == existing->expiry_preferred_msec) {
return FALSE;
}
existing->timestamp = new->timestamp;
existing->lifetime = new->lifetime;
existing->preferred = new->preferred;
existing->expiry_msec = new_expiry_msec;
existing->expiry_preferred_msec = new_expiry_preferred_msec;
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)
return FALSE;
if (new->lifetime == 0)
if (new_item->expiry_msec <= now_msec)
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) {
new2 = *new;
new2.dad_counter = 0;
if (!complete_address(ndisc, &new2))
new2->dad_counter = 0;
if (!complete_address(ndisc, new2)) {
g_array_set_size(rdata->addresses, rdata->addresses->len - 1);
return FALSE;
new = &new2;
}
}
g_array_append_val(rdata->addresses, *new);
return TRUE;
}
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
nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new)
nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec)
{
NMNDiscPrivate * priv;
NMNDiscDataInternal *rdata;
guint i;
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
* local configuration or user preferences are, so sending routes
* 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;) {
NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
if (IN6_ARE_ADDR_EQUAL(&item->network, &new->network) && item->plen == new->plen) {
if (new->lifetime == 0) {
if (IN6_ARE_ADDR_EQUAL(&item->network, &new_item->network)
&& item->plen == new_item->plen) {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->routes, i);
return TRUE;
}
if (item->preference != new->preference) {
if (item->preference != new_item->preference) {
g_array_remove_index(rdata->routes, i);
changed = TRUE;
continue;
}
if (get_expiry(item) == get_expiry(new)
&& IN6_ARE_ADDR_EQUAL(&item->gateway, &new->gateway))
if (item->expiry_msec == new_item->expiry_msec
&& IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway))
return FALSE;
*item = *new;
item->expiry_msec = new_item->expiry_msec;
item->gateway = new_item->gateway;
return TRUE;
}
/* 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 = i;
i++;
}
if (new->lifetime) {
g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new);
if (new_item->expiry_msec <= now_msec) {
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
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;
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++) {
NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
if (new->lifetime == 0) {
if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->dns_servers, i);
return TRUE;
}
if (get_expiry(item) == get_expiry(new))
if (item->expiry_msec == new_item->expiry_msec)
return FALSE;
*item = *new;
item->expiry_msec = new_item->expiry_msec;
return TRUE;
}
}
if (new->lifetime)
g_array_append_val(rdata->dns_servers, *new);
return !!new->lifetime;
if (new_item->expiry_msec <= now_msec)
return FALSE;
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
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;
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++) {
item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
if (!g_strcmp0(item->domain, new->domain)) {
if (new->lifetime == 0) {
if (nm_streq(item->domain, new_item->domain)) {
if (new_item->expiry_msec <= now_msec) {
g_array_remove_index(rdata->dns_domains, i);
return TRUE;
}
if (get_expiry(item) == get_expiry(new))
if (item->expiry_msec == new_item->expiry_msec)
return FALSE;
item->timestamp = new->timestamp;
item->lifetime = new->lifetime;
item->expiry_msec = new_item->expiry_msec;
return TRUE;
}
}
if (new->lifetime) {
g_array_append_val(rdata->dns_domains, *new);
item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, rdata->dns_domains->len - 1);
item->domain = g_strdup(new->domain);
}
return !!new->lifetime;
if (new_item->expiry_msec <= now_msec)
return FALSE;
item = nm_g_array_append_new(rdata->dns_domains, NMNDiscDNSDomain);
*item = (NMNDiscDNSDomain){
.domain = g_strdup(new_item->domain),
.expiry_msec = new_item->expiry_msec,
};
return TRUE;
}
/*****************************************************************************/
#define _MAYBE_WARN(...) \
G_STMT_START \
{ \
gboolean _different_message; \
\
_different_message = g_strcmp0(priv->last_error, error->message) != 0; \
_NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \
if (_different_message) { \
nm_clear_g_free(&priv->last_error); \
priv->last_error = g_strdup(error->message); \
} \
} \
#define _MAYBE_WARN(...) \
G_STMT_START \
{ \
gboolean _different_message; \
\
_different_message = !nm_streq0(priv->last_error, error->message); \
_NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \
if (_different_message) { \
nm_clear_g_free(&priv->last_error); \
priv->last_error = g_strdup(error->message); \
} \
} \
G_STMT_END
static gboolean
@ -937,12 +931,16 @@ nm_ndisc_set_config(NMNDisc * ndisc,
}
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;
}
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;
}
@ -1098,7 +1096,7 @@ nm_ndisc_stop(NMNDisc *ndisc)
nm_clear_g_source(&priv->send_rs_id);
nm_clear_g_source(&priv->send_ra_id);
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->announcements_left = 0;
@ -1180,15 +1178,15 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
NMNDiscDataInternal *rdata;
guint i;
char changedstr[CONFIG_MAP_MAX_STR];
char addrstr[INET6_ADDRSTRLEN];
char addrstr[NM_UTILS_INET_ADDRSTRLEN];
char str_pref[35];
char str_exp[100];
gint64 now_ns;
gint64 now_msec;
if (!_LOGD_ENABLED())
return;
now_ns = nm_utils_get_monotonic_timestamp_nsec();
now_msec = nm_utils_get_monotonic_timestamp_msec();
priv = NM_NDISC_GET_PRIVATE(ndisc);
rdata = &priv->rdata;
@ -1205,199 +1203,245 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
_LOGD(" retrans timer : %u", (guint) rdata->public.retrans_timer_ms);
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",
addrstr,
_nm_utils_inet6_ntop(&gateway->address, addrstr),
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++) {
const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
inet_ntop(AF_INET6, &address->address, addrstr, sizeof(addrstr));
_LOGD(" address %s exp %s", addrstr, get_exp(str_exp, now_ns, address));
_LOGD(" address %s exp %s",
_nm_utils_inet6_ntop(&address->address, addrstr),
get_exp(str_exp, now_msec, address));
}
for (i = 0; i < rdata->routes->len; i++) {
NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i);
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
const NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i);
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",
addrstr,
_nm_utils_inet6_ntop(&route->network, addrstr),
(guint) route->plen,
_nm_utils_inet6_ntop(&route->gateway, sbuf),
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++) {
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", addrstr, get_exp(str_exp, now_ns, dns_server));
_LOGD(" dns_server %s exp %s",
_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++) {
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
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 j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (rdata->gateways->len == 0)
return;
for (i = 0; i < rdata->gateways->len;) {
NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
arr = &g_array_index(rdata->gateways, NMNDiscGateway, 0);
if (!expiry_next(now, get_expiry(item), nextevent)) {
g_array_remove_index(rdata->gateways, i);
*changed |= NM_NDISC_CONFIG_GATEWAYS;
for (i = 0, j = 0; i < rdata->gateways->len; i++) {
if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
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);
}
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 j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (rdata->addresses->len == 0)
return;
for (i = 0; i < rdata->addresses->len;) {
const NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i);
arr = &g_array_index(rdata->addresses, NMNDiscAddress, 0);
if (!expiry_next(now, get_expiry(item), nextevent)) {
g_array_remove_index(rdata->addresses, i);
*changed |= NM_NDISC_CONFIG_ADDRESSES;
for (i = 0, j = 0; i < rdata->addresses->len; i++) {
if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
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
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 j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (rdata->routes->len == 0)
return;
for (i = 0; i < rdata->routes->len;) {
NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
arr = &g_array_index(rdata->routes, NMNDiscRoute, 0);
if (!expiry_next(now, get_expiry(item), nextevent)) {
g_array_remove_index(rdata->routes, i);
*changed |= NM_NDISC_CONFIG_ROUTES;
for (i = 0, j = 0; i < rdata->routes->len; i++) {
if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
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
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 j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (rdata->dns_servers->len == 0)
return;
for (i = 0; i < rdata->dns_servers->len;) {
NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
arr = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, 0);
if (!expiry_next(now, get_expiry(item), nextevent)) {
g_array_remove_index(rdata->dns_servers, i);
*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
for (i = 0, j = 0; i < rdata->dns_servers->len; i++) {
if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
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
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 j;
rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (rdata->dns_domains->len == 0)
return;
for (i = 0; i < rdata->dns_domains->len;) {
NMNDiscDNSDomain *item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
arr = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, 0);
if (!expiry_next(now, get_expiry(item), nextevent)) {
g_array_remove_index(rdata->dns_domains, i);
*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
for (i = 0, j = 0; i < rdata->dns_domains->len; i++) {
if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
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
check_timestamps(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
{
NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
/* Use a magic date in the distant future (~68 years) */
gint32 nextevent = G_MAXINT32;
NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
gint64 next_msec = G_MAXINT64;
nm_clear_g_source(&priv->timeout_id);
_LOGT("router-data: check for changed router advertisement data");
clean_gateways(ndisc, now, &changed, &nextevent);
clean_addresses(ndisc, now, &changed, &nextevent);
clean_routes(ndisc, now, &changed, &nextevent);
clean_dns_servers(ndisc, now, &changed, &nextevent);
clean_dns_domains(ndisc, now, &changed, &nextevent);
clean_gateways(ndisc, now_msec, &changed, &next_msec);
clean_addresses(ndisc, now_msec, &changed, &next_msec);
clean_routes(ndisc, now_msec, &changed, &next_msec);
clean_dns_servers(ndisc, now_msec, &changed, &next_msec);
clean_dns_domains(ndisc, now_msec, &changed, &next_msec);
if (nextevent != G_MAXINT32) {
if (nextevent <= now)
g_return_if_reached();
_LOGD("scheduling next now/lifetime check: %d seconds", (int) (nextevent - now));
priv->timeout_id = g_timeout_add_seconds(nextevent - now, timeout_cb, ndisc);
nm_assert(next_msec > now_msec);
nm_clear_g_source_inst(&priv->timeout_expire_source);
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);
}
static gboolean
timeout_cb(gpointer user_data)
timeout_expire_cb(gpointer user_data)
{
NMNDisc *self = user_data;
NM_NDISC_GET_PRIVATE(self)->timeout_id = 0;
check_timestamps(self, nm_utils_get_monotonic_timestamp_sec(), 0);
return G_SOURCE_REMOVE;
check_timestamps(user_data, nm_utils_get_monotonic_timestamp_msec(), NM_NDISC_CONFIG_NONE);
return G_SOURCE_CONTINUE;
}
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);
nm_clear_g_source_inst(&priv->ra_timeout_source);
nm_clear_g_source(&priv->send_rs_id);
nm_clear_g_free(&priv->last_error);
check_timestamps(ndisc, now, changed);
check_timestamps(ndisc, now_msec, changed);
}
void
@ -1525,7 +1569,7 @@ dispose(GObject *object)
nm_clear_g_source(&priv->send_ra_id);
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);
}

View file

@ -48,48 +48,91 @@ typedef enum {
NM_NDISC_DHCP_LEVEL_MANAGED
} NMNDiscDHCPLevel;
/* we rely on the fact that NM_NDISC_INFINITY is the largest possible
* time duration (G_MAXUINT32) and that the range of finite values
* goes from 0 to G_MAXUINT32-1. */
#define NM_NDISC_INFINITY G_MAXUINT32
#define NM_NDISC_INFINITY_U32 ((uint32_t) -1)
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;
guint32 timestamp;
guint32 lifetime;
gint64 expiry_msec;
NMIcmpv6RouterPref preference;
};
typedef struct _NMNDiscGateway NMNDiscGateway;
} NMNDiscGateway;
struct _NMNDiscAddress {
typedef struct _NMNDiscAddress {
struct in6_addr address;
gint64 expiry_msec;
gint64 expiry_preferred_msec;
guint8 dad_counter;
guint32 timestamp;
guint32 lifetime;
guint32 preferred;
};
typedef struct _NMNDiscAddress NMNDiscAddress;
} NMNDiscAddress;
struct _NMNDiscRoute {
typedef struct _NMNDiscRoute {
struct in6_addr network;
guint8 plen;
struct in6_addr gateway;
guint32 timestamp;
guint32 lifetime;
gint64 expiry_msec;
NMIcmpv6RouterPref preference;
};
typedef struct _NMNDiscRoute NMNDiscRoute;
guint8 plen;
} NMNDiscRoute;
typedef struct {
struct in6_addr address;
guint32 timestamp;
guint32 lifetime;
gint64 expiry_msec;
} NMNDiscDNSServer;
typedef struct {
char * domain;
guint32 timestamp;
guint32 lifetime;
char * domain;
gint64 expiry_msec;
} NMNDiscDNSDomain;
typedef enum {
@ -113,6 +156,7 @@ typedef enum {
} NMNDiscNodeType;
#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_SOLICITATION_INTERVAL_DEFAULT 4 /* RFC4861 RTR_SOLICITATION_INTERVAL */
#define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT 3 /* RFC4861 MAX_INITIAL_RTR_ADVERTISEMENTS */

View file

@ -14,6 +14,8 @@
#include "nm-test-utils-core.h"
/*****************************************************************************/
static NMFakeNDisc *
ndisc_new(void)
{
@ -26,19 +28,20 @@ ndisc_new(void)
iid.id_u8[7] = 1;
nm_ndisc_set_iid(ndisc, iid);
g_assert(ndisc);
return NM_FAKE_NDISC(ndisc);
}
/*****************************************************************************/
static void
match_gateway(const NMNDiscData *rdata,
guint idx,
const char * addr,
guint32 ts,
guint32 lt,
gint64 expiry_msec,
NMIcmpv6RouterPref pref)
{
const NMNDiscGateway *gw;
char buf[INET6_ADDRSTRLEN];
g_assert(rdata);
g_assert_cmpint(idx, <, rdata->gateways_n);
@ -46,63 +49,57 @@ match_gateway(const NMNDiscData *rdata,
gw = &rdata->gateways[idx];
g_assert_cmpstr(inet_ntop(AF_INET6, &gw->address, buf, sizeof(buf)), ==, addr);
g_assert_cmpint(gw->timestamp, ==, ts);
g_assert_cmpint(gw->lifetime, ==, lt);
nmtst_assert_ip6_address(&gw->address, addr);
g_assert_cmpint(gw->expiry_msec, ==, expiry_msec);
g_assert_cmpint(gw->preference, ==, pref);
}
#define match_address(rdata, idx, addr, ts, lt, pref) \
G_STMT_START \
{ \
const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \
const NMNDiscAddress *_a; \
guint _ts = (ts); \
\
g_assert(_rdata); \
g_assert_cmpint(_idx, <, _rdata->addresses_n); \
g_assert(_rdata->addresses); \
\
_a = &_rdata->addresses[_idx]; \
\
nmtst_assert_ip6_address(&_a->address, (addr)); \
g_assert_cmpint(_a->timestamp, <=, _ts + 1); \
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)); \
} \
#define match_address(rdata, idx, addr, _expiry_msec, _expiry_preferred_msec) \
G_STMT_START \
{ \
const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \
const NMNDiscAddress *_a; \
\
g_assert(_rdata); \
g_assert_cmpint(_idx, <, _rdata->addresses_n); \
g_assert(_rdata->addresses); \
\
_a = &_rdata->addresses[_idx]; \
\
nmtst_assert_ip6_address(&_a->address, (addr)); \
g_assert_cmpint(_a->expiry_msec, ==, (_expiry_msec)); \
g_assert_cmpint(_a->expiry_preferred_msec, ==, (_expiry_preferred_msec)); \
} \
G_STMT_END
#define match_route(rdata, idx, nw, pl, gw, ts, lt, pref) \
G_STMT_START \
{ \
const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \
const NMNDiscRoute *_r; \
int _plen = (pl); \
\
g_assert(_rdata); \
g_assert_cmpint(_idx, <, _rdata->routes_n); \
g_assert(_rdata->routes); \
g_assert(_plen > 0 && _plen <= 128); \
\
_r = &_rdata->routes[idx]; \
\
nmtst_assert_ip6_address(&_r->network, (nw)); \
g_assert_cmpint((int) _r->plen, ==, _plen); \
nmtst_assert_ip6_address(&_r->gateway, (gw)); \
g_assert_cmpint(_r->timestamp, ==, (ts)); \
g_assert_cmpint(_r->lifetime, ==, (lt)); \
g_assert_cmpint(_r->preference, ==, (pref)); \
} \
#define match_route(rdata, idx, nw, pl, gw, _expiry_msec, pref) \
G_STMT_START \
{ \
const NMNDiscData * _rdata = (rdata); \
guint _idx = (idx); \
const NMNDiscRoute *_r; \
int _plen = (pl); \
\
g_assert(_rdata); \
g_assert_cmpint(_idx, <, _rdata->routes_n); \
g_assert(_rdata->routes); \
g_assert(_plen > 0 && _plen <= 128); \
\
_r = &_rdata->routes[idx]; \
\
nmtst_assert_ip6_address(&_r->network, (nw)); \
g_assert_cmpint((int) _r->plen, ==, _plen); \
nmtst_assert_ip6_address(&_r->gateway, (gw)); \
g_assert_cmpint(_r->expiry_msec, ==, (_expiry_msec)); \
g_assert_cmpint(_r->preference, ==, (pref)); \
} \
G_STMT_END
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;
char buf[INET6_ADDRSTRLEN];
g_assert(rdata);
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];
g_assert_cmpstr(inet_ntop(AF_INET6, &dns->address, buf, sizeof(buf)), ==, addr);
g_assert_cmpint(dns->timestamp, ==, ts);
g_assert_cmpint(dns->lifetime, ==, lt);
nmtst_assert_ip6_address(&dns->address, addr);
g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
}
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;
@ -127,67 +123,96 @@ match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, guint3
dns = &rdata->dns_domains[idx];
g_assert_cmpstr(dns->domain, ==, domain);
g_assert_cmpint(dns->timestamp, ==, ts);
g_assert_cmpint(dns->lifetime, ==, lt);
g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
}
/*****************************************************************************/
typedef struct {
GMainLoop *loop;
gint64 timestamp_msec_1;
guint counter;
guint rs_counter;
guint32 timestamp1;
guint32 first_solicit;
gint64 first_solicit_msec;
guint32 timeout_id;
} TestData;
/*****************************************************************************/
static void
test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
{
NMNDiscConfigMap changed = changed_int;
g_assert_cmpint(changed,
==,
NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
| NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
| NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
| NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10);
switch (data->counter++) {
case 0:
g_assert_cmpint(changed,
==,
NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
| NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
| NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
| NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
match_gateway(rdata,
0,
"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)));
data->counter++;
g_main_loop_quit(data->loop);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
break;
case 1:
g_main_loop_quit(data->loop);
break;
default:
g_assert_not_reached();
}
}
static void
test_simple(void)
{
NMFakeNDisc *ndisc = ndisc_new();
guint32 now = nm_utils_get_monotonic_timestamp_sec();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now};
guint id;
nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
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);
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_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10);
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_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);
nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 1);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
g_assert_cmpint(data.counter, ==, 2);
}
/*****************************************************************************/
static void
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_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
| NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10);
match_gateway(rdata,
0,
"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 + 10000);
} else if (data->counter == 1) {
g_assert_cmpint(changed,
==,
@ -221,16 +254,28 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
| NM_NDISC_CONFIG_DNS_DOMAINS);
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);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0);
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10);
match_address(rdata,
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);
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);
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);
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_main_loop_quit(data->loop);
@ -243,31 +288,49 @@ test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_
static void
test_everything(void)
{
NMFakeNDisc *ndisc = ndisc_new();
guint32 now = nm_utils_get_monotonic_timestamp_sec();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now};
guint id;
nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
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);
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_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10);
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_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 */
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 0, 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_dns_server(ndisc, id, "2001:db8:c:c::1", now, 0);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 0);
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_msec, now_msec, 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_msec);
/* 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_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now, 10);
nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now, 10);
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_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_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);
g_assert_cmpint(data.counter, ==, 2);
g_assert_cmpint(data.rs_counter, ==, 1);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
}
static void
@ -296,14 +356,30 @@ test_preference_order_cb(NMNDisc * ndisc,
| NM_NDISC_CONFIG_ROUTES);
g_assert_cmpint(rdata->gateways_n, ==, 2);
match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
match_gateway(rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
match_gateway(rdata,
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);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
match_address(rdata,
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);
match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5);
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->timestamp_msec_1 + 10000, 5);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
g_main_loop_quit(data->loop);
@ -315,31 +391,46 @@ test_preference_order_cb(NMNDisc * ndisc,
static void
test_preference_order(void)
{
NMFakeNDisc *ndisc = ndisc_new();
guint32 now = nm_utils_get_monotonic_timestamp_sec();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now};
guint id;
nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
/* Test insertion order of gateways */
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, 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_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_msec + 10000,
now_msec + 10000,
5);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, 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_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_msec + 11000,
now_msec + 10000,
10);
g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data);
nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 2);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
}
static void
@ -356,14 +447,30 @@ test_preference_changed_cb(NMNDisc * ndisc,
NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
| NM_NDISC_CONFIG_ROUTES);
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, 1, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
match_gateway(rdata,
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);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
match_address(rdata,
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);
match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5);
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->timestamp_msec_1 + 10000, 5);
} else if (data->counter == 2) {
g_assert_cmpint(changed,
==,
@ -371,14 +478,30 @@ test_preference_changed_cb(NMNDisc * ndisc,
| NM_NDISC_CONFIG_ROUTES);
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, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
match_gateway(rdata,
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);
match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 3, 9, 9);
match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
match_address(rdata,
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);
match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15);
match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
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->timestamp_msec_1 + 11000, 10);
g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
g_main_loop_quit(data->loop);
@ -390,10 +513,14 @@ test_preference_changed_cb(NMNDisc * ndisc,
static void
test_preference_changed(void)
{
NMFakeNDisc *ndisc = ndisc_new();
guint32 now = nm_utils_get_monotonic_timestamp_sec();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now};
guint id;
nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
TestData data = {
.loop = loop,
.timestamp_msec_1 = now_msec,
};
guint id;
/* Test that when a low-preference and medium gateway send advertisements,
* 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);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, 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_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_msec + 10000,
now_msec + 10000,
5);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, 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_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_msec + 11000,
now_msec + 11000,
10);
id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", ++now, 10, 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_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_msec + 12000,
now_msec + 12000,
15);
g_signal_connect(ndisc,
NM_NDISC_CONFIG_RECEIVED,
@ -423,16 +571,15 @@ test_preference_changed(void)
nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 15000);
g_assert_cmpint(data.counter, ==, 3);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
}
/*****************************************************************************/
static void
test_dns_solicit_loop_changed(NMNDisc * ndisc,
const NMNDiscData *rdata,
guint changed_int,
TestData * data)
_test_dns_solicit_loop_changed(NMNDisc * ndisc,
const NMNDiscData *rdata,
guint changed_int,
TestData * data)
{
data->counter++;
}
@ -446,14 +593,14 @@ success_timeout(TestData *data)
}
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();
guint id;
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
guint id;
if (data->rs_counter > 0 && data->rs_counter < 6) {
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 */
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);
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);
} else if (data->rs_counter >= 6) {
/* 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_main_loop_quit(data->loop);
}
@ -479,10 +630,14 @@ test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
static void
test_dns_solicit_loop(void)
{
NMFakeNDisc *ndisc = ndisc_new();
guint32 now = nm_utils_get_monotonic_timestamp_sec();
TestData data = {g_main_loop_new(NULL, FALSE), 0, 0, now, 0};
guint id;
nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
gs_unref_object NMFakeNDisc *ndisc = ndisc_new();
const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
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");
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);
g_assert(id);
nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 6);
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_msec + 6000);
g_signal_connect(ndisc,
NM_NDISC_CONFIG_RECEIVED,
G_CALLBACK(test_dns_solicit_loop_changed),
G_CALLBACK(_test_dns_solicit_loop_changed),
&data);
g_signal_connect(ndisc,
NM_FAKE_NDISC_RS_SENT,
G_CALLBACK(test_dns_solicit_loop_rs_sent),
G_CALLBACK(_test_dns_solicit_loop_rs_sent),
&data);
nm_ndisc_start(NM_NDISC(ndisc));
nmtst_main_loop_run_assert(data.loop, 20000);
g_assert_cmpint(data.counter, ==, 3);
g_object_unref(ndisc);
g_main_loop_unref(data.loop);
}
/*****************************************************************************/
NMTST_DEFINE();
int

View file

@ -1497,6 +1497,7 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self,
NMIP6ConfigPrivate *priv;
guint i;
gboolean changed = FALSE;
gint32 base_time_sec;
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);
/* 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);
for (i = 0; i < addresses_n; i++) {
@ -1516,9 +1527,13 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self,
a->ifindex = priv->ifindex;
a->address = ndisc_addr->address;
a->plen = plen;
a->timestamp = ndisc_addr->timestamp;
a->lifetime = ndisc_addr->lifetime;
a->preferred = MIN(ndisc_addr->lifetime, ndisc_addr->preferred);
a->timestamp = base_time_sec,
a->lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000,
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->n_ifa_flags = ifa_flags;