diff options
Diffstat (limited to 'src/libnm-platform/nmp-plobj.c')
| -rw-r--r-- | src/libnm-platform/nmp-plobj.c | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/src/libnm-platform/nmp-plobj.c b/src/libnm-platform/nmp-plobj.c new file mode 100644 index 00000000..c27aa1fc --- /dev/null +++ b/src/libnm-platform/nmp-plobj.c @@ -0,0 +1,637 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-plobj.h" + +#include "nm-compat-headers/linux/if_addr.h" + +#include "libnm-glib-aux/nm-time-utils.h" +#include "nm-platform-utils.h" + +/*****************************************************************************/ + +#define TO_STRING_IFA_FLAGS_BUF_SIZE 256 + +static const char * +_to_string_ifa_flags(guint32 ifa_flags, char *buf, gsize size) +{ +#define S_FLAGS_PREFIX " flags " + nm_assert(buf && size >= TO_STRING_IFA_FLAGS_BUF_SIZE && size > NM_STRLEN(S_FLAGS_PREFIX)); + + if (!ifa_flags) + buf[0] = '\0'; + else { + nm_platform_addr_flags2str(ifa_flags, + &buf[NM_STRLEN(S_FLAGS_PREFIX)], + size - NM_STRLEN(S_FLAGS_PREFIX)); + if (buf[NM_STRLEN(S_FLAGS_PREFIX)] == '\0') + buf[0] = '\0'; + else + memcpy(buf, S_FLAGS_PREFIX, NM_STRLEN(S_FLAGS_PREFIX)); + } + return buf; +} + +#define _to_string_dev(arr, ifindex) \ + ({ \ + const int _ifindex = (ifindex); \ + \ + _ifindex ? nm_sprintf_buf((arr), " dev %d", ifindex) : nm_str_truncate((arr)); \ + }) + +static const char * +_lifetime_to_string(guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size) +{ + if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT) + return "forever"; + + g_snprintf(buf, + buf_size, + "%usec", + nmp_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now)); + return buf; +} + +static const char * +_lifetime_summary_to_string(gint32 now, + guint32 timestamp, + guint32 preferred, + guint32 lifetime, + char *buf, + size_t buf_size) +{ + g_snprintf(buf, + buf_size, + " lifetime %d-%u[%u,%u]", + (signed) now, + (unsigned) timestamp, + (unsigned) preferred, + (unsigned) lifetime); + return buf; +} + +static int +_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b) +{ + guint32 lifetime_a; + guint32 lifetime_b; + guint32 preferred_a; + guint32 preferred_b; + gint32 now = 0; + + lifetime_a = + nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, &now, &preferred_a); + lifetime_b = + nmp_utils_lifetime_get(b->timestamp, b->lifetime, b->preferred, &now, &preferred_b); + + NM_CMP_DIRECT(lifetime_a, lifetime_b); + NM_CMP_DIRECT(preferred_a, preferred_b); + return 0; +} + +/*****************************************************************************/ + +void +nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->addr_source, + obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u), + obj->timestamp, + obj->lifetime, + obj->preferred, + obj->n_ifa_flags, + obj->plen, + obj->address, + obj->peer_address, + NM_HASH_COMBINE_BOOLS(guint8, + obj->use_ip4_broadcast_address, + obj->a_acd_not_ready, + obj->a_force_commit)); + nm_hash_update_strarr(h, obj->label); +} + +int +nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, + const NMPlatformIP4Address *b, + NMPlatformIPAddressCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD(a, b, address); + + switch (cmp_type) { + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID: + /* for IPv4 addresses, you can add the same local address with differing peer-address + * (IFA_ADDRESS), provided that their net-part differs. */ + NM_CMP_DIRECT_IP4_ADDR_SAME_PREFIX(a->peer_address, b->peer_address, a->plen); + return 0; + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, peer_address); + NM_CMP_FIELD_STR(a, b, label); + if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) { + NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a, + (const NMPlatformIPAddress *) b)); + + /* Most flags are set by kernel. We only compare the ones that + * NetworkManager actively sets. + * + * NM actively only sets IFA_F_NOPREFIXROUTE (and IFA_F_MANAGETEMPADDR for IPv6), + * where nm_platform_ip_address_sync() sets IFA_F_NOPREFIXROUTE depending on + * NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE. + * There are thus no flags to compare for IPv4. */ + + NM_CMP_DIRECT(nm_platform_ip4_broadcast_address_from_addr(a), + nm_platform_ip4_broadcast_address_from_addr(b)); + } else { + NM_CMP_FIELD(a, b, timestamp); + NM_CMP_FIELD(a, b, lifetime); + NM_CMP_FIELD(a, b, preferred); + NM_CMP_FIELD(a, b, n_ifa_flags); + NM_CMP_FIELD(a, b, addr_source); + NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address); + if (a->use_ip4_broadcast_address) + NM_CMP_FIELD(a, b, broadcast_address); + NM_CMP_FIELD_UNSAFE(a, b, a_acd_not_ready); + NM_CMP_FIELD_UNSAFE(a, b, a_force_commit); + } + return 0; + } + return nm_assert_unreachable_val(0); +} + +/*****************************************************************************/ + +void +nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->addr_source, + obj->timestamp, + obj->lifetime, + obj->preferred, + obj->n_ifa_flags, + obj->plen, + obj->address, + obj->peer_address, + NM_HASH_COMBINE_BOOLS(guint8, obj->a_force_commit)); +} + +int +nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, + const NMPlatformIP6Address *b, + NMPlatformIPAddressCmpType cmp_type) +{ + const struct in6_addr *p_a, *p_b; + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD_IN6ADDR(a, b, address); + + switch (cmp_type) { + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID: + /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ + return 0; + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, plen); + p_a = nm_platform_ip6_address_get_peer(a); + p_b = nm_platform_ip6_address_get_peer(b); + NM_CMP_DIRECT_MEMCMP(p_a, p_b, sizeof(*p_a)); + if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) { + NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a, + (const NMPlatformIPAddress *) b)); + + /* Most flags are set by kernel. We only compare the ones that + * NetworkManager actively sets. + * + * NM actively only sets IFA_F_NOPREFIXROUTE and IFA_F_MANAGETEMPADDR, + * where nm_platform_ip_address_sync() sets IFA_F_NOPREFIXROUTE depending on + * NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE. + * We thus only care about IFA_F_MANAGETEMPADDR. */ + NM_CMP_DIRECT(a->n_ifa_flags & IFA_F_MANAGETEMPADDR, + b->n_ifa_flags & IFA_F_MANAGETEMPADDR); + } else { + NM_CMP_FIELD(a, b, timestamp); + NM_CMP_FIELD(a, b, lifetime); + NM_CMP_FIELD(a, b, preferred); + NM_CMP_FIELD(a, b, n_ifa_flags); + NM_CMP_FIELD(a, b, addr_source); + NM_CMP_FIELD_UNSAFE(a, b, a_force_commit); + } + return 0; + } + return nm_assert_unreachable_val(0); +} + +/*****************************************************************************/ + +static int +_address_pretty_sort_get_prio_4(in_addr_t addr) +{ + if (nm_ip4_addr_is_link_local(addr)) + return 0; + return 1; +} + +static int +_address_pretty_sort_get_prio_6(const struct in6_addr *addr) +{ + if (IN6_IS_ADDR_V4MAPPED(addr)) + return 0; + if (IN6_IS_ADDR_V4COMPAT(addr)) + return 1; + if (IN6_IS_ADDR_UNSPECIFIED(addr)) + return 2; + if (IN6_IS_ADDR_LOOPBACK(addr)) + return 3; + if (IN6_IS_ADDR_LINKLOCAL(addr)) + return 4; + if (IN6_IS_ADDR_SITELOCAL(addr)) + return 5; + return 6; +} + +int +nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1, + const NMPlatformIP4Address *a2) +{ + in_addr_t n1; + in_addr_t n2; + + nm_assert(a1); + nm_assert(a2); + + /* Sort by address type. For example link local will + * be sorted *after* a global address. */ + NM_CMP_DIRECT(_address_pretty_sort_get_prio_4(a2->address), + _address_pretty_sort_get_prio_4(a1->address)); + + /* Sort the addresses based on their source. */ + NM_CMP_DIRECT(a2->addr_source, a1->addr_source); + + NM_CMP_DIRECT((a2->label[0] == '\0'), (a1->label[0] == '\0')); + + /* Finally, sort addresses lexically. We compare only the + * network part so that the order of addresses in the same + * subnet (and thus also the primary/secondary role) is + * preserved. + */ + n1 = nm_ip4_addr_clear_host_address(a1->address, a1->plen); + n2 = nm_ip4_addr_clear_host_address(a2->address, a2->plen); + NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32)); + return 0; +} + +int +nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1, + const NMPlatformIP6Address *a2, + gboolean prefer_temp) +{ + gboolean ipv6_privacy1; + gboolean ipv6_privacy2; + + nm_assert(a1); + nm_assert(a2); + + /* tentative addresses are always sorted back... */ + /* sort tentative addresses after non-tentative. */ + NM_CMP_DIRECT(NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE), + NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_TENTATIVE)); + + /* Sort by address type. For example link local will + * be sorted *after* site local or global. */ + NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address), + _address_pretty_sort_get_prio_6(&a1->address)); + + ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY); + ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY); + if (ipv6_privacy1 || ipv6_privacy2) { + gboolean public1 = TRUE; + gboolean public2 = TRUE; + + if (ipv6_privacy1) { + if (a1->n_ifa_flags & IFA_F_SECONDARY) + public1 = prefer_temp; + else + public1 = !prefer_temp; + } + if (ipv6_privacy2) { + if (a2->n_ifa_flags & IFA_F_SECONDARY) + public2 = prefer_temp; + else + public2 = !prefer_temp; + } + + NM_CMP_DIRECT(public2, public1); + } + + /* Sort the addresses based on their source. */ + NM_CMP_DIRECT(a2->addr_source, a1->addr_source); + + /* sort permanent addresses before non-permanent. */ + NM_CMP_DIRECT(NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_PERMANENT), + NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_PERMANENT)); + + /* finally sort addresses lexically */ + NM_CMP_DIRECT_IN6ADDR(&a1->address, &a2->address); + NM_CMP_DIRECT_MEMCMP(a1, a2, sizeof(*a1)); + return 0; +} + +void +nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen) +{ + nm_assert(plen <= 32); + + addr->address = address; + addr->peer_address = address; + addr->plen = plen; +} + +const struct in6_addr * +nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr) +{ + if (IN6_IS_ADDR_UNSPECIFIED(&addr->peer_address) + || IN6_ARE_ADDR_EQUAL(&addr->peer_address, &addr->address)) + return &addr->address; + return &addr->peer_address; +} + +/*****************************************************************************/ + +/** + * nm_platform_ip4_address_to_string: + * @route: pointer to NMPlatformIP4Address address structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting an address struct into a string representation. + * + * Example output: "" + * + * Returns: a string representation of the address. + */ +const char * +nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len) +{ + char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE]; + char s_address[INET_ADDRSTRLEN]; + char s_peer[INET_ADDRSTRLEN]; + char str_dev[30]; + char str_label[32]; + char str_lft[30]; + char str_pref[30]; + char str_time[50]; + char s_source[50]; + char *str_peer = NULL; + const char *str_lft_p, *str_pref_p, *str_time_p; + gint32 now = nm_utils_get_monotonic_timestamp_sec(); + in_addr_t broadcast_address; + char str_broadcast[INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(address, &buf, &len)) + return buf; + + inet_ntop(AF_INET, &address->address, s_address, sizeof(s_address)); + + if (address->peer_address != address->address) { + inet_ntop(AF_INET, &address->peer_address, s_peer, sizeof(s_peer)); + str_peer = g_strconcat(" ptp ", s_peer, NULL); + } + + if (*address->label) + g_snprintf(str_label, sizeof(str_label), " label %s", address->label); + else + str_label[0] = 0; + + str_lft_p = _lifetime_to_string(address->timestamp, + address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_lft, + sizeof(str_lft)), + str_pref_p = + (address->lifetime == address->preferred) + ? str_lft_p + : (_lifetime_to_string(address->timestamp, + address->lifetime ? MIN(address->preferred, address->lifetime) + : NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_pref, + sizeof(str_pref))); + str_time_p = _lifetime_summary_to_string(now, + address->timestamp, + address->preferred, + address->lifetime, + str_time, + sizeof(str_time)); + + broadcast_address = nm_platform_ip4_broadcast_address_from_addr(address); + + g_snprintf( + buf, + len, + "%s/%d" + "%s%s" /* broadcast */ + " lft %s" + " pref %s" + "%s" /* time */ + "%s" /* peer */ + "%s" /* dev */ + "%s" /* flags */ + "%s" /* label */ + " src %s" + "%s" /* a_acd_not_ready */ + "%s" /* a_force_commit */ + "", + s_address, + address->plen, + broadcast_address != 0u || address->use_ip4_broadcast_address + ? (address->use_ip4_broadcast_address ? " brd " : " brd* ") + : "", + broadcast_address != 0u || address->use_ip4_broadcast_address + ? nm_inet4_ntop(broadcast_address, str_broadcast) + : "", + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + _to_string_dev(str_dev, address->ifindex), + _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)), + str_label, + nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), + address->a_acd_not_ready ? " ip4acd-not-ready" : "", + address->a_force_commit ? " force-commit" : ""); + g_free(str_peer); + return buf; +} + +/** + * nm_platform_ip6_address_to_string: + * @route: pointer to NMPlatformIP6Address address structure + * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used. + * @len: the size of the @buf. If @buf is %NULL, this argument is ignored. + * + * A method for converting an address struct into a string representation. + * + * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1" + * + * Returns: a string representation of the address. + */ +const char * +nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len) +{ + char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE]; + char s_address[INET6_ADDRSTRLEN]; + char s_peer[INET6_ADDRSTRLEN]; + char str_lft[30]; + char str_pref[30]; + char str_time[50]; + char s_source[50]; + char str_dev[30]; + char *str_peer = NULL; + const char *str_lft_p, *str_pref_p, *str_time_p; + gint32 now = nm_utils_get_monotonic_timestamp_sec(); + + if (!nm_utils_to_string_buffer_init_null(address, &buf, &len)) + return buf; + + inet_ntop(AF_INET6, &address->address, s_address, sizeof(s_address)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&address->peer_address)) { + inet_ntop(AF_INET6, &address->peer_address, s_peer, sizeof(s_peer)); + str_peer = g_strconcat(" ptp ", s_peer, NULL); + } + + str_lft_p = _lifetime_to_string(address->timestamp, + address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_lft, + sizeof(str_lft)), + str_pref_p = + (address->lifetime == address->preferred) + ? str_lft_p + : (_lifetime_to_string(address->timestamp, + address->lifetime ? MIN(address->preferred, address->lifetime) + : NM_PLATFORM_LIFETIME_PERMANENT, + now, + str_pref, + sizeof(str_pref))); + str_time_p = _lifetime_summary_to_string(now, + address->timestamp, + address->preferred, + address->lifetime, + str_time, + sizeof(str_time)); + + g_snprintf( + buf, + len, + "%s/%d lft %s pref %s%s%s%s%s src %s" + "%s" /* a_force_commit */ + "", + s_address, + address->plen, + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + _to_string_dev(str_dev, address->ifindex), + _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)), + nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), + address->a_force_commit ? " force-commit" : ""); + g_free(str_peer); + return buf; +} + +/*****************************************************************************/ + +gboolean +nm_platform_ip_address_match(int addr_family, + const NMPlatformIPAddress *address, + NMPlatformMatchFlags match_flag) +{ + nm_assert(!NM_FLAGS_ANY( + match_flag, + ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))); + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY)); + nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)); + + if (addr_family == AF_INET) { + if (nm_ip4_addr_is_link_local(((NMPlatformIP4Address *) address)->address)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL)) + return FALSE; + } else { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL)) + return FALSE; + } + } else { + if (IN6_IS_ADDR_LINKLOCAL(address->address_ptr)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL)) + return FALSE; + } else { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL)) + return FALSE; + } + } + + if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_DADFAILED)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED)) + return FALSE; + } else if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_OPTIMISTIC)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) + return FALSE; + } else if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_DEPRECATED)) { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED)) + return FALSE; + } else { + if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +const _NMPlatformVTableAddressUnion nm_platform_vtable_address = { + .v4 = + { + .is_ip4 = TRUE, + .obj_type = NMP_OBJECT_TYPE_IP4_ADDRESS, + .addr_family = AF_INET, + .sizeof_address = sizeof(NMPlatformIP4Address), + .address_cmp = + (int (*)(const NMPlatformIPXAddress *a, + const NMPlatformIPXAddress *b, + NMPlatformIPAddressCmpType cmp_type)) nm_platform_ip4_address_cmp, + .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address, + char *buf, + gsize len)) nm_platform_ip4_address_to_string, + }, + .v6 = + { + .is_ip4 = FALSE, + .obj_type = NMP_OBJECT_TYPE_IP6_ADDRESS, + .addr_family = AF_INET6, + .sizeof_address = sizeof(NMPlatformIP6Address), + .address_cmp = + (int (*)(const NMPlatformIPXAddress *a, + const NMPlatformIPXAddress *b, + NMPlatformIPAddressCmpType cmp_type)) nm_platform_ip6_address_cmp, + .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address, + char *buf, + gsize len)) nm_platform_ip6_address_to_string, + }, +}; |