platform: fix preferred and valid lifetimes for addresses from netlink/kernel

The kernel tells the address lifetimes in the 'struct ifa_cacheinfo'
attribute. This contains two timestamps (cstamp and tstamp) and two
relative lifetimes (ifa_prefered and ifa_valid).

The timestamps are equal to clock_gettime(CLOCK_MONOTONIC) scale in
1/100th of a second (wrapping every 497 days).

The preferred/valid times are re-adjusted everytime when sending the
message and count down as the time goes by. In other words, they are
anchored relatively to the moment of when kernel creates the netlink
message.

As platform is caching the rtnl_addr object, the information of *when* the
lifetimes started counting is not available.

This patch fixes reading these values by hacking the libnl object
when it gets received, so that valid and preferred are instead absolute
expiration timestamps in scale nm_utils_get_monotonic_timestamp_s() --
which NM internally is used for address timestamps.

There are two minor downsides to this hack:
- the valid and preferred properties of a cached rtnl_addr object have
  an unexpected meaning, i.e. they are absolute and in a different time
  scale.
- later when converting rtnl_addr to NMPlatformIPAddress, the base
  timestamp is set to "1", i.e. an NMPlatformIPAddress has no knowledge
  of when the address was created or last modified. The timestamp
  property of NMPlatformIPAddress is solely there to anchor the relative
  timestamps lifetime and preferred. Do not use it for anything
  else.
  Another reason the timestamp property is meaningless is that
  its scale nm_utils_get_monotonic_timestamp_s() starts counting at
  process start. So addresses that existed before would have a negative
  or zero timestamp, which we avoid. This in turn could be solved by either
  allowing negative timestamps or by shifting
  nm_utils_get_monotonic_timestamp_*(). Both is viable, but not
  necessary (ATM), because the age of an address has no other apparent
  use then to anchor the relative timestamps.
  Another implication is, that we potentially could get rid of the
  timestamp completely, and insteat make preferred and lifetime be
  absolute expiries.

This will be fixed properly later, by not caching libnl objects but  instead
native NMPlatform objects. For those we have full control over their properties.

https://bugzilla.gnome.org/show_bug.cgi?id=727382

Signed-off-by: Thomas Haller <thaller@redhat.com>
This commit is contained in:
Thomas Haller
2014-04-02 13:35:21 +02:00
parent 8f8b247e34
commit 8310a039d8
2 changed files with 115 additions and 9 deletions

View File

@@ -136,6 +136,53 @@ _nl_has_capability (int capability)
/******************************************************************/ /******************************************************************/
static guint32
_get_expiry (guint32 now_s, guint32 lifetime_s)
{
gint64 t = ((gint64) now_s) + ((gint64) lifetime_s);
return MIN (t, NM_PLATFORM_LIFETIME_PERMANENT - 1);
}
/* The rtnl_addr object contains relative lifetimes @valid and @preferred
* that count in seconds, starting from the moment when the kernel constructed
* the netlink message.
*
* There is also a field rtnl_addr_last_update_time(), which is the absolute
* time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
* was modified (wrapping every 497 days).
* Immediately at the time when the address was last modified, #NOW and @last_update_time
* are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
* However, this is not true in general. As time goes by, whenever kernel sends a new address
* via netlink, the lifetimes keep counting down.
*
* As we cache the rtnl_addr object we must know the absolute expiries.
* As a hack, modify the relative timestamps valid and preferred into absolute
* timestamps of scale nm_utils_get_monotonic_timestamp_s().
**/
static void
_rtnl_addr_hack_lifetimes_rel_to_abs (struct rtnl_addr *rtnladdr)
{
guint32 a_valid = rtnl_addr_get_valid_lifetime (rtnladdr);
guint32 a_preferred = rtnl_addr_get_preferred_lifetime (rtnladdr);
guint32 now;
if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
a_preferred == NM_PLATFORM_LIFETIME_PERMANENT)
return;
now = (guint32) nm_utils_get_monotonic_timestamp_s ();
if (a_preferred > a_valid)
a_preferred = a_valid;
if (a_valid != NM_PLATFORM_LIFETIME_PERMANENT)
rtnl_addr_set_valid_lifetime (rtnladdr, _get_expiry (now, a_valid));
rtnl_addr_set_preferred_lifetime (rtnladdr, _get_expiry (now, a_preferred));
}
/******************************************************************/
/* libnl library workarounds and additions */ /* libnl library workarounds and additions */
/* Automatic deallocation of local variables */ /* Automatic deallocation of local variables */
@@ -402,6 +449,9 @@ get_kernel_object (struct nl_sock *sock, struct nl_object *needle)
nl_cache_free (cache); nl_cache_free (cache);
if (object && (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS))
_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
if (object) if (object)
debug ("get_kernel_object for type %d returned %p", type, object); debug ("get_kernel_object for type %d returned %p", type, object);
else else
@@ -982,6 +1032,53 @@ hack_empty_master_iff_lower_up (NMPlatform *platform, struct nl_object *object)
rtnl_link_unset_flags (rtnllink, IFF_LOWER_UP); rtnl_link_unset_flags (rtnllink, IFF_LOWER_UP);
} }
static void
_init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr *rtnladdr)
{
guint32 a_valid = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
guint32 a_preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
/* the meaning of the valid and preferred lifetimes is different from the
* original meaning. See _rtnl_addr_hack_lifetimes_rel_to_abs().
* Beware: this function expects hacked rtnl_addr objects.
*/
if (a_valid == NM_PLATFORM_LIFETIME_PERMANENT &&
a_preferred == NM_PLATFORM_LIFETIME_PERMANENT) {
address->timestamp = 0;
address->lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
address->preferred = NM_PLATFORM_LIFETIME_PERMANENT;
return;
}
/* The valies are hacked and absolute expiry times. They must
* be positive and preferred<=valid. */
g_assert (a_preferred <= a_valid &&
a_valid > 0 &&
a_preferred > 0);
/* The correct timestamp value would probably be rtnl_addr_get_last_update_time()
* (after converting into the proper time scale).
* But this is relatively complicated to convert and we don't actually need it.
* Especially, because rtnl_addr_get_last_update_time() might be negative in
* nm_utils_get_monotonic_timestamp_s() scale -- which we want to avoid.
*
* Remember: timestamp has no meaning, beyond anchoring the relative lifetimes
* at some point in time. We choose this point to be "1 second".
*/
address->timestamp = 1;
/* account for the timestamp==1 by incrementing valid/preferred -- unless
* it is NM_PLATFORM_LIFETIME_PERMANENT or already NM_PLATFORM_LIFETIME_PERMANENT-1
* (i.e. the largest non-permanent lifetime). */
if (a_valid < NM_PLATFORM_LIFETIME_PERMANENT - 1)
a_valid += 1;
if (a_preferred < NM_PLATFORM_LIFETIME_PERMANENT - 1)
a_preferred += 1;
address->lifetime = a_valid;
address->preferred = a_preferred + 1;
}
static gboolean static gboolean
init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr) init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
{ {
@@ -995,9 +1092,7 @@ init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
address->ifindex = rtnl_addr_get_ifindex (rtnladdr); address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
address->plen = rtnl_addr_get_prefixlen (rtnladdr); address->plen = rtnl_addr_get_prefixlen (rtnladdr);
address->timestamp = nm_utils_get_monotonic_timestamp_s (); _init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
address->lifetime = rtnl_addr_get_valid_lifetime (rtnladdr);
address->preferred = rtnl_addr_get_preferred_lifetime (rtnladdr);
if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) { if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
g_return_val_if_reached (FALSE); g_return_val_if_reached (FALSE);
return FALSE; return FALSE;
@@ -1028,9 +1123,7 @@ init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
address->ifindex = rtnl_addr_get_ifindex (rtnladdr); address->ifindex = rtnl_addr_get_ifindex (rtnladdr);
address->plen = rtnl_addr_get_prefixlen (rtnladdr); address->plen = rtnl_addr_get_prefixlen (rtnladdr);
address->timestamp = nm_utils_get_monotonic_timestamp_s (); _init_ip_address_lifetime ((NMPlatformIPAddress *) address, rtnladdr);
address->lifetime = rtnl_addr_get_valid_lifetime (rtnladdr);
address->preferred = rtnl_addr_get_preferred_lifetime (rtnladdr);
address->flags = rtnl_addr_get_flags (rtnladdr); address->flags = rtnl_addr_get_flags (rtnladdr);
if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) { if (!nladdr || nl_addr_get_len (nladdr) != sizeof (address->address)) {
g_return_val_if_reached (FALSE); g_return_val_if_reached (FALSE);
@@ -3180,6 +3273,12 @@ build_rtnl_addr (int family,
rtnl_addr_set_prefixlen (rtnladdr, plen); rtnl_addr_set_prefixlen (rtnladdr, plen);
if (lifetime) { if (lifetime) {
/* note that here we set the relative timestamps (ticking from *now*).
* Contrary to the rtnl_addr objects from our cache, which have absolute
* timestamps (see _rtnl_addr_hack_lifetimes_rel_to_abs()).
*
* This is correct, because we only use build_rtnl_addr() for
* add_object(), delete_object() and cache search (ip_address_exists). */
rtnl_addr_set_valid_lifetime (rtnladdr, lifetime); rtnl_addr_set_valid_lifetime (rtnladdr, lifetime);
rtnl_addr_set_preferred_lifetime (rtnladdr, preferred); rtnl_addr_set_preferred_lifetime (rtnladdr, preferred);
} }

View File

@@ -165,9 +165,16 @@ typedef struct {
#define __NMPlatformIPAddress_COMMON \ #define __NMPlatformIPAddress_COMMON \
__NMPlatformObject_COMMON; \ __NMPlatformObject_COMMON; \
NMPlatformSource source; \ NMPlatformSource source; \
guint32 timestamp; /* nm_utils_get_monotonic_timestamp_s() */ \ \
guint32 lifetime; /* seconds */ \ /* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*().
guint32 preferred; /* seconds */ \ * This value is mainly used to anchor the relative lifetime and preferred values.
* For addresses originating from DHCP it might be set to nm_utils_get_monotonic_timestamp_s()
* of when the lease was received. For addresses from platform/kernel it is set to 1.
* For permanent addresses it is mostly set to 0.
*/ \
guint32 timestamp; \
guint32 lifetime; /* seconds since timestamp */ \
guint32 preferred; /* seconds since timestamp */ \
int plen; \ int plen; \
; ;