diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnm-platform | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnm-platform')
28 files changed, 34880 insertions, 0 deletions
diff --git a/src/libnm-platform/meson.build b/src/libnm-platform/meson.build new file mode 100644 index 00000000..3e4f41a9 --- /dev/null +++ b/src/libnm-platform/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_platform = static_library( + 'nm-platform', + sources: [ + 'nm-linux-platform.c', + 'nm-netlink.c', + 'nm-platform-utils.c', + 'nm-platform.c', + 'nmp-netns.c', + 'nmp-object.c', + 'nmp-rules-manager.c', + 'wifi/nm-wifi-utils-nl80211.c', + 'wifi/nm-wifi-utils.c', + 'wpan/nm-wpan-utils.c', + ] + (enable_wext ? [ 'wifi/nm-wifi-utils-wext.c' ] : []), + include_directories: [ + src_inc, + top_inc, + ], + dependencies: [ + glib_dep, + libudev_dep, + ], +) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c new file mode 100644 index 00000000..bcf94200 --- /dev/null +++ b/src/libnm-platform/nm-linux-platform.c @@ -0,0 +1,9711 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-linux-platform.h" + +#include <arpa/inet.h> +#include <dlfcn.h> +#include <endian.h> +#include <fcntl.h> +#include <libudev.h> +#include <net/ethernet.h> +#include <linux/fib_rules.h> +#include <linux/ip.h> +#include <linux/if.h> +#include <linux/if_bridge.h> +#include <linux/if_link.h> +#include <linux/if_tun.h> +#include <linux/if_tunnel.h> +#include <linux/if_vlan.h> +#include <linux/ip6_tunnel.h> +#include <linux/tc_act/tc_mirred.h> +#include <netinet/icmp6.h> +#include <netinet/in.h> +#include <net/if_arp.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/statvfs.h> +#include <unistd.h> + +#include "libnm-glib-aux/nm-c-list.h" +#include "libnm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-netlink.h" +#include "libnm-platform/nm-platform-utils.h" +#include "libnm-platform/nmp-netns.h" +#include "libnm-platform/wifi/nm-wifi-utils-wext.h" +#include "libnm-platform/wifi/nm-wifi-utils.h" +#include "libnm-platform/wpan/nm-wpan-utils.h" +#include "libnm-std-aux/unaligned.h" +#include "libnm-udev-aux/nm-udev-utils.h" +#include "nm-platform-private.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +/* re-implement <linux/tc_act/tc_defact.h> to build against kernel + * headers that lack this. */ + +#include <linux/pkt_cls.h> + +struct tc_defact { + tc_gen; +}; + +enum { TCA_DEF_UNSPEC, TCA_DEF_TM, TCA_DEF_PARMS, TCA_DEF_DATA, TCA_DEF_PAD, __TCA_DEF_MAX }; +#define TCA_DEF_MAX (__TCA_DEF_MAX - 1) + +/*****************************************************************************/ + +/* Compat with older kernels. */ + +#define TCA_FQ_CODEL_CE_THRESHOLD 7 +#define TCA_FQ_CODEL_MEMORY_LIMIT 9 + +/*****************************************************************************/ + +#define VLAN_FLAG_MVRP 0x8 + +/*****************************************************************************/ + +#define IFQDISCSIZ 32 + +/*****************************************************************************/ + +#ifndef IFLA_PROMISCUITY + #define IFLA_PROMISCUITY 30 +#endif +#define IFLA_NUM_TX_QUEUES 31 +#define IFLA_NUM_RX_QUEUES 32 +#define IFLA_CARRIER 33 +#define IFLA_PHYS_PORT_ID 34 +#define IFLA_LINK_NETNSID 37 +#define __IFLA_MAX 39 + +#define IFLA_INET6_TOKEN 7 +#define IFLA_INET6_ADDR_GEN_MODE 8 +#define __IFLA_INET6_MAX 9 + +#define IFLA_VLAN_PROTOCOL 5 +#define __IFLA_VLAN_MAX 6 + +#define IFA_FLAGS 8 +#define __IFA_MAX 9 + +#define IFLA_MACVLAN_FLAGS 2 +#define __IFLA_MACVLAN_MAX 3 + +#define IFLA_IPTUN_LINK 1 +#define IFLA_IPTUN_LOCAL 2 +#define IFLA_IPTUN_REMOTE 3 +#define IFLA_IPTUN_TTL 4 +#define IFLA_IPTUN_TOS 5 +#define IFLA_IPTUN_ENCAP_LIMIT 6 +#define IFLA_IPTUN_FLOWINFO 7 +#define IFLA_IPTUN_FLAGS 8 +#define IFLA_IPTUN_PROTO 9 +#define IFLA_IPTUN_PMTUDISC 10 +#define __IFLA_IPTUN_MAX 19 +#ifndef IFLA_IPTUN_MAX + #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) +#endif + +#define IFLA_TUN_UNSPEC 0 +#define IFLA_TUN_OWNER 1 +#define IFLA_TUN_GROUP 2 +#define IFLA_TUN_TYPE 3 +#define IFLA_TUN_PI 4 +#define IFLA_TUN_VNET_HDR 5 +#define IFLA_TUN_PERSIST 6 +#define IFLA_TUN_MULTI_QUEUE 7 +#define IFLA_TUN_NUM_QUEUES 8 +#define IFLA_TUN_NUM_DISABLED_QUEUES 9 +#define __IFLA_TUN_MAX 10 +#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1) + +G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1)); +#define RTA_PREF 20 +#undef RTA_MAX +#define RTA_MAX (MAX((__RTA_MAX - 1), RTA_PREF)) + +#ifndef MACVLAN_FLAG_NOPROMISC + #define MACVLAN_FLAG_NOPROMISC 1 +#endif + +#define IP6_FLOWINFO_TCLASS_MASK 0x0FF00000 +#define IP6_FLOWINFO_TCLASS_SHIFT 20 +#define IP6_FLOWINFO_FLOWLABEL_MASK 0x000FFFFF + +#define IFLA_BR_VLAN_STATS_ENABLED 41 + +/*****************************************************************************/ + +/* Appeared in the kernel prior to 3.13 dated 19 January, 2014 */ +#ifndef ARPHRD_6LOWPAN + #define ARPHRD_6LOWPAN 825 +#endif + +/*****************************************************************************/ + +#define FRA_TUN_ID 12 +#define FRA_SUPPRESS_IFGROUP 13 +#define FRA_SUPPRESS_PREFIXLEN 14 +#define FRA_PAD 18 +#define FRA_L3MDEV 19 +#define FRA_UID_RANGE 20 +#define FRA_PROTOCOL 21 +#define FRA_IP_PROTO 22 +#define FRA_SPORT_RANGE 23 +#define FRA_DPORT_RANGE 24 + +/*****************************************************************************/ + +#define IFLA_MACSEC_UNSPEC 0 +#define IFLA_MACSEC_SCI 1 +#define IFLA_MACSEC_PORT 2 +#define IFLA_MACSEC_ICV_LEN 3 +#define IFLA_MACSEC_CIPHER_SUITE 4 +#define IFLA_MACSEC_WINDOW 5 +#define IFLA_MACSEC_ENCODING_SA 6 +#define IFLA_MACSEC_ENCRYPT 7 +#define IFLA_MACSEC_PROTECT 8 +#define IFLA_MACSEC_INC_SCI 9 +#define IFLA_MACSEC_ES 10 +#define IFLA_MACSEC_SCB 11 +#define IFLA_MACSEC_REPLAY_PROTECT 12 +#define IFLA_MACSEC_VALIDATION 13 +#define IFLA_MACSEC_PAD 14 +#define __IFLA_MACSEC_MAX 15 + +/*****************************************************************************/ + +#define WG_CMD_GET_DEVICE 0 +#define WG_CMD_SET_DEVICE 1 + +#define WGDEVICE_F_REPLACE_PEERS ((guint32) (1U << 0)) + +#define WGPEER_F_REMOVE_ME ((guint32) (1U << 0)) +#define WGPEER_F_REPLACE_ALLOWEDIPS ((guint32) (1U << 1)) + +#define WGDEVICE_A_UNSPEC 0 +#define WGDEVICE_A_IFINDEX 1 +#define WGDEVICE_A_IFNAME 2 +#define WGDEVICE_A_PRIVATE_KEY 3 +#define WGDEVICE_A_PUBLIC_KEY 4 +#define WGDEVICE_A_FLAGS 5 +#define WGDEVICE_A_LISTEN_PORT 6 +#define WGDEVICE_A_FWMARK 7 +#define WGDEVICE_A_PEERS 8 +#define WGDEVICE_A_MAX 8 + +#define WGPEER_A_UNSPEC 0 +#define WGPEER_A_PUBLIC_KEY 1 +#define WGPEER_A_PRESHARED_KEY 2 +#define WGPEER_A_FLAGS 3 +#define WGPEER_A_ENDPOINT 4 +#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5 +#define WGPEER_A_LAST_HANDSHAKE_TIME 6 +#define WGPEER_A_RX_BYTES 7 +#define WGPEER_A_TX_BYTES 8 +#define WGPEER_A_ALLOWEDIPS 9 +#define WGPEER_A_MAX 9 + +#define WGALLOWEDIP_A_UNSPEC 0 +#define WGALLOWEDIP_A_FAMILY 1 +#define WGALLOWEDIP_A_IPADDR 2 +#define WGALLOWEDIP_A_CIDR_MASK 3 +#define WGALLOWEDIP_A_MAX 3 + +/*****************************************************************************/ + +/* Redefine VF enums and structures that are not available on older kernels. */ + +#define IFLA_VF_UNSPEC 0 +#define IFLA_VF_MAC 1 +#define IFLA_VF_VLAN 2 +#define IFLA_VF_TX_RATE 3 +#define IFLA_VF_SPOOFCHK 4 +#define IFLA_VF_LINK_STATE 5 +#define IFLA_VF_RATE 6 +#define IFLA_VF_RSS_QUERY_EN 7 +#define IFLA_VF_STATS 8 +#define IFLA_VF_TRUST 9 +#define IFLA_VF_IB_NODE_GUID 10 +#define IFLA_VF_IB_PORT_GUID 11 +#define IFLA_VF_VLAN_LIST 12 + +#define IFLA_VF_VLAN_INFO_UNSPEC 0 +#define IFLA_VF_VLAN_INFO 1 + +/* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */ +struct _ifla_vf_setting { + guint32 vf; + guint32 setting; +}; + +struct _ifla_vf_rate { + guint32 vf; + guint32 min_tx_rate; + guint32 max_tx_rate; +}; + +struct _ifla_vf_vlan_info { + guint32 vf; + guint32 vlan; /* 0 - 4095, 0 disables VLAN filter */ + guint32 qos; + guint16 vlan_proto; /* VLAN protocol, either 802.1Q or 802.1ad */ +}; + +/*****************************************************************************/ + +/* Appeared in the kernel 4.0 dated April 12, 2015 */ +#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN + #define BRIDGE_VLAN_INFO_RANGE_BEGIN (1 << 3) /* VLAN is start of vlan range */ + #define BRIDGE_VLAN_INFO_RANGE_END (1 << 4) /* VLAN is end of vlan range */ +#endif + +/*****************************************************************************/ + +#define PSCHED_TIME_UNITS_PER_SEC 1000000 + +/*****************************************************************************/ + +typedef enum { + INFINIBAND_ACTION_CREATE_CHILD, + INFINIBAND_ACTION_DELETE_CHILD, +} InfinibandAction; + +typedef enum { + CHANGE_LINK_TYPE_UNSPEC, + CHANGE_LINK_TYPE_SET_MTU, + CHANGE_LINK_TYPE_SET_ADDRESS, +} ChangeLinkType; + +typedef struct { + union { + struct { + gconstpointer address; + gsize length; + } set_address; + }; +} ChangeLinkData; + +typedef enum { + _REFRESH_ALL_TYPE_FIRST = 0, + + REFRESH_ALL_TYPE_LINKS = 0, + REFRESH_ALL_TYPE_IP4_ADDRESSES = 1, + REFRESH_ALL_TYPE_IP6_ADDRESSES = 2, + REFRESH_ALL_TYPE_IP4_ROUTES = 3, + REFRESH_ALL_TYPE_IP6_ROUTES = 4, + REFRESH_ALL_TYPE_ROUTING_RULES_IP4 = 5, + REFRESH_ALL_TYPE_ROUTING_RULES_IP6 = 6, + REFRESH_ALL_TYPE_QDISCS = 7, + REFRESH_ALL_TYPE_TFILTERS = 8, + + _REFRESH_ALL_TYPE_NUM, +} RefreshAllType; + +typedef struct { + NMPObjectType obj_type; + + /* for NLM_F_DUMP, which address family to request. */ + int addr_family; +} RefreshAllInfo; + +typedef enum { + DELAYED_ACTION_TYPE_NONE = 0, + +#define F(val, name) ((sizeof(char[(((val)) == (name)) ? 1 : -1]) * 0) + (val)) + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS = 1 << F(0, REFRESH_ALL_TYPE_LINKS), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES = 1 << F(1, REFRESH_ALL_TYPE_IP4_ADDRESSES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES = 1 << F(2, REFRESH_ALL_TYPE_IP6_ADDRESSES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES = 1 << F(3, REFRESH_ALL_TYPE_IP4_ROUTES), + DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES = 1 << F(4, REFRESH_ALL_TYPE_IP6_ROUTES), + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 = 1 + << F(5, REFRESH_ALL_TYPE_ROUTING_RULES_IP4), + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6 = 1 + << F(6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6), + DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS = 1 << F(7, REFRESH_ALL_TYPE_QDISCS), + DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS = 1 << F(8, REFRESH_ALL_TYPE_TFILTERS), +#undef F + + DELAYED_ACTION_TYPE_REFRESH_LINK = 1 << 9, + DELAYED_ACTION_TYPE_MASTER_CONNECTED = 1 << 10, + DELAYED_ACTION_TYPE_READ_NETLINK = 1 << 11, + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE = 1 << 12, + + __DELAYED_ACTION_TYPE_MAX, + + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL = + DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + + DELAYED_ACTION_TYPE_REFRESH_ALL = + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + + DELAYED_ACTION_TYPE_MAX = __DELAYED_ACTION_TYPE_MAX - 1, +} DelayedActionType; + +#define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \ + for ((iflags) = (DelayedActionType) 0x1LL; ({ \ + gboolean _good = FALSE; \ + \ + nm_assert(nm_utils_is_power_of_two(iflags)); \ + \ + while ((iflags) <= DELAYED_ACTION_TYPE_MAX) { \ + if (NM_FLAGS_ANY((flags_all), (iflags))) { \ + _good = TRUE; \ + break; \ + } \ + (iflags) <<= 1; \ + } \ + _good; \ + }); \ + (iflags) <<= 1) + +typedef enum { + /* Negative values are errors from kernel. Add dummy member to + * make enum signed. */ + _WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT, + + WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0, + WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK, + WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS, +} WaitForNlResponseResult; + +typedef enum { + DELAYED_ACTION_RESPONSE_TYPE_VOID = 0, + DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS = 1, + DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET = 2, +} DelayedActionWaitForNlResponseType; + +typedef struct { + guint32 seq_number; + WaitForNlResponseResult seq_result; + DelayedActionWaitForNlResponseType response_type; + gint64 timeout_abs_ns; + WaitForNlResponseResult * out_seq_result; + char ** out_errmsg; + union { + int * out_refresh_all_in_progress; + NMPObject **out_route_get; + gpointer out_data; + } response; +} DelayedActionWaitForNlResponseData; + +/*****************************************************************************/ + +typedef struct { + struct nl_sock *genl; + + struct nl_sock *nlh; + + GSource *event_source; + + guint32 nlh_seq_next; +#if NM_MORE_LOGGING + guint32 nlh_seq_last_handled; +#endif + guint32 nlh_seq_last_seen; + + guint32 pruning[_REFRESH_ALL_TYPE_NUM]; + + GHashTable *sysctl_get_prev_values; + CList sysctl_list; + CList sysctl_clear_cache_lst; + + NMUdevClient *udev_client; + + struct { + /* which delayed actions are scheduled, as marked in @flags. + * Some types have additional arguments in the fields below. */ + DelayedActionType flags; + + /* counter that a refresh all action is in progress, separated + * by type. */ + int refresh_all_in_progress[_REFRESH_ALL_TYPE_NUM]; + + GPtrArray *list_master_connected; + GPtrArray *list_refresh_link; + GArray * list_wait_for_nl_response; + + int is_handling; + } delayed_action; + +} NMLinuxPlatformPrivate; + +struct _NMLinuxPlatform { + NMPlatform parent; + NMLinuxPlatformPrivate _priv; +}; + +struct _NMLinuxPlatformClass { + NMPlatformClass parent; +}; + +G_DEFINE_TYPE(NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM) + +#define NM_LINUX_PLATFORM_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform) + +static NMPlatform * +NM_LINUX_PLATFORM_FROM_PRIVATE(NMLinuxPlatformPrivate *priv) +{ + gpointer self; + + nm_assert(priv); + + self = (((char *) priv) - G_STRUCT_OFFSET(NMLinuxPlatform, _priv)); + nm_assert(NM_IS_LINUX_PLATFORM(self)); + return self; +} + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "platform-linux" +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG2_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG_err(errsv, level, ...) _LOG_err(errsv, level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG2(level, ...) _LOG(level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__) +#define _NMLOG2_err(errsv, level, ...) _LOG_err(errsv, level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__) + +#define _LOG_print(__level, __domain, __errsv, self, ...) \ + G_STMT_START \ + { \ + char __prefix[32]; \ + const char * __p_prefix = _NMLOG_PREFIX_NAME; \ + NMPlatform *const __self = (self); \ + \ + if (__self && nm_platform_get_log_with_ptr(__self)) { \ + g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + __domain, \ + __errsv, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define _LOG(level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + _LOG_print(__level, __domain, 0, self, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _LOG_err(errsv, level, domain, self, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + int __errsv = (errsv); \ + \ + /* The %m format specifier (GNU extension) would already allow you to specify the error + * message conveniently (and nm_log would get that right too). But we don't want to depend + * on that, so instead append the message at the end. + * Currently, users are expected not to use %m in the format string. */ \ + _LOG_print( \ + __level, \ + __domain, \ + __errsv, \ + self, \ + _NM_UTILS_MACRO_FIRST(__VA_ARGS__) ": %s (%d)" _NM_UTILS_MACRO_REST(__VA_ARGS__), \ + nm_strerror_native(__errsv), \ + __errsv); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void +delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data); +static gboolean delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink); +static void do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name); +static void do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type); +static void cache_on_change(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new); +static void cache_prune_all(NMPlatform *platform); +static gboolean event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks); +static struct nl_sock *_genl_sock(NMLinuxPlatform *platform); + +/*****************************************************************************/ + +static int +wait_for_nl_response_to_nmerr(WaitForNlResponseResult seq_result) +{ + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return (int) seq_result; + return -NME_PL_NETLINK; +} + +static const char * +wait_for_nl_response_to_string(WaitForNlResponseResult seq_result, + const char * errmsg, + char * buf, + gsize buf_size) +{ + char *buf0 = buf; + + switch (seq_result) { + case WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN: + nm_utils_strbuf_append_str(&buf, &buf_size, "unknown"); + break; + case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK: + nm_utils_strbuf_append_str(&buf, &buf_size, "success"); + break; + case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN: + nm_utils_strbuf_append_str(&buf, &buf_size, "failure"); + break; + default: + if (seq_result < 0) { + nm_utils_strbuf_append(&buf, + &buf_size, + "failure %d (%s%s%s)", + -((int) seq_result), + nm_strerror_native(-((int) seq_result)), + errmsg ? " - " : "", + errmsg ?: ""); + } else + nm_utils_strbuf_append(&buf, &buf_size, "internal failure %d", (int) seq_result); + break; + } + return buf0; +} + +/****************************************************************** + * Various utilities + ******************************************************************/ + +static int +_vlan_qos_mapping_cmp_from(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMVlanQosMapping *map_a = a; + const NMVlanQosMapping *map_b = b; + + if (map_a->from != map_b->from) + return map_a->from < map_b->from ? -1 : 1; + return 0; +} + +static int +_vlan_qos_mapping_cmp_from_ptr(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return _vlan_qos_mapping_cmp_from(*((const NMVlanQosMapping **) a), + *((const NMVlanQosMapping **) b), + NULL); +} + +/****************************************************************** + * NMLinkType functions + ******************************************************************/ + +typedef struct { + const char *type_string; + + /* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type + * should only be specified if the device type can be created without + * additional parameters, and if the device type can be determined from + * the rtnl_type. eg, tun/tap should not be specified since both + * tun and tap devices use "tun", and InfiniBand should not be + * specified because a PKey is required at creation. Drivers set this + * value from their 'struct rtnl_link_ops' structure. + */ + const char *rtnl_type; + + /* uevent DEVTYPE where applicable, from /sys/class/net/<ifname>/uevent; + * drivers set this value from their SET_NETDEV_DEV() call and the + * 'struct device_type' name member. + */ + const char *devtype; +} LinkDesc; + +static const LinkDesc link_descs[] = { + [NM_LINK_TYPE_NONE] = {"none", NULL, NULL}, + [NM_LINK_TYPE_UNKNOWN] = {"unknown", NULL, NULL}, + [NM_LINK_TYPE_ANY] = {"any", NULL, NULL}, + + [NM_LINK_TYPE_ETHERNET] = {"ethernet", NULL, NULL}, + [NM_LINK_TYPE_INFINIBAND] = {"infiniband", NULL, NULL}, + [NM_LINK_TYPE_OLPC_MESH] = {"olpc-mesh", NULL, NULL}, + [NM_LINK_TYPE_WIFI] = {"wifi", NULL, "wlan"}, + [NM_LINK_TYPE_WWAN_NET] = {"wwan", NULL, "wwan"}, + [NM_LINK_TYPE_WIMAX] = {"wimax", "wimax", "wimax"}, + [NM_LINK_TYPE_WPAN] = {"wpan", NULL, NULL}, + [NM_LINK_TYPE_6LOWPAN] = {"6lowpan", NULL, NULL}, + + [NM_LINK_TYPE_BNEP] = {"bluetooth", NULL, "bluetooth"}, + [NM_LINK_TYPE_DUMMY] = {"dummy", "dummy", NULL}, + [NM_LINK_TYPE_GRE] = {"gre", "gre", NULL}, + [NM_LINK_TYPE_GRETAP] = {"gretap", "gretap", NULL}, + [NM_LINK_TYPE_IFB] = {"ifb", "ifb", NULL}, + [NM_LINK_TYPE_IP6TNL] = {"ip6tnl", "ip6tnl", NULL}, + [NM_LINK_TYPE_IP6GRE] = {"ip6gre", "ip6gre", NULL}, + [NM_LINK_TYPE_IP6GRETAP] = {"ip6gretap", "ip6gretap", NULL}, + [NM_LINK_TYPE_IPIP] = {"ipip", "ipip", NULL}, + [NM_LINK_TYPE_LOOPBACK] = {"loopback", NULL, NULL}, + [NM_LINK_TYPE_MACSEC] = {"macsec", "macsec", NULL}, + [NM_LINK_TYPE_MACVLAN] = {"macvlan", "macvlan", NULL}, + [NM_LINK_TYPE_MACVTAP] = {"macvtap", "macvtap", NULL}, + [NM_LINK_TYPE_OPENVSWITCH] = {"openvswitch", "openvswitch", NULL}, + [NM_LINK_TYPE_PPP] = {"ppp", NULL, "ppp"}, + [NM_LINK_TYPE_SIT] = {"sit", "sit", NULL}, + [NM_LINK_TYPE_TUN] = {"tun", "tun", NULL}, + [NM_LINK_TYPE_VETH] = {"veth", "veth", NULL}, + [NM_LINK_TYPE_VLAN] = {"vlan", "vlan", "vlan"}, + [NM_LINK_TYPE_VRF] = {"vrf", "vrf", "vrf"}, + [NM_LINK_TYPE_VXLAN] = {"vxlan", "vxlan", "vxlan"}, + [NM_LINK_TYPE_WIREGUARD] = {"wireguard", "wireguard", "wireguard"}, + + [NM_LINK_TYPE_BRIDGE] = {"bridge", "bridge", "bridge"}, + [NM_LINK_TYPE_BOND] = {"bond", "bond", "bond"}, + [NM_LINK_TYPE_TEAM] = {"team", "team", NULL}, +}; + +static const LinkDesc * +_link_desc_from_link_type(NMLinkType link_type) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(link_type)); + nm_assert(link_type < G_N_ELEMENTS(link_descs)); + nm_assert(link_descs[link_type].type_string); + + return &link_descs[link_type]; +} + +static NMLinkType +_link_type_from_rtnl_type(const char *name) +{ + static const NMLinkType LIST[] = { + NM_LINK_TYPE_BOND, /* "bond" */ + NM_LINK_TYPE_BRIDGE, /* "bridge" */ + NM_LINK_TYPE_DUMMY, /* "dummy" */ + NM_LINK_TYPE_GRE, /* "gre" */ + NM_LINK_TYPE_GRETAP, /* "gretap" */ + NM_LINK_TYPE_IFB, /* "ifb" */ + NM_LINK_TYPE_IP6GRE, /* "ip6gre" */ + NM_LINK_TYPE_IP6GRETAP, /* "ip6gretap" */ + NM_LINK_TYPE_IP6TNL, /* "ip6tnl" */ + NM_LINK_TYPE_IPIP, /* "ipip" */ + NM_LINK_TYPE_MACSEC, /* "macsec" */ + NM_LINK_TYPE_MACVLAN, /* "macvlan" */ + NM_LINK_TYPE_MACVTAP, /* "macvtap" */ + NM_LINK_TYPE_OPENVSWITCH, /* "openvswitch" */ + NM_LINK_TYPE_SIT, /* "sit" */ + NM_LINK_TYPE_TEAM, /* "team" */ + NM_LINK_TYPE_TUN, /* "tun" */ + NM_LINK_TYPE_VETH, /* "veth" */ + NM_LINK_TYPE_VLAN, /* "vlan" */ + NM_LINK_TYPE_VRF, /* "vrf" */ + NM_LINK_TYPE_VXLAN, /* "vxlan" */ + NM_LINK_TYPE_WIMAX, /* "wimax" */ + NM_LINK_TYPE_WIREGUARD, /* "wireguard" */ + }; + + nm_assert(name); + + if (NM_MORE_ASSERT_ONCE(5)) { + int i, j, k; + + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { + nm_assert(_link_desc_from_link_type(LIST[i]) == &link_descs[LIST[i]]); + nm_assert(link_descs[LIST[i]].rtnl_type); + if (i > 0) + nm_assert(strcmp(link_descs[LIST[i - 1]].rtnl_type, link_descs[LIST[i]].rtnl_type) + < 0); + } + for (i = 0; i < G_N_ELEMENTS(link_descs); i++) { + if (!link_descs[i].rtnl_type) + continue; + for (j = 0, k = 0; j < G_N_ELEMENTS(LIST); j++) + k += (LIST[j] == i); + nm_assert(k == 1); + } + } + + { + int imin = 0; + int imax = (G_N_ELEMENTS(LIST) - 1); + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; + + for (;;) { + const int cmp = strcmp(link_descs[LIST[imid]].rtnl_type, name); + + if (G_UNLIKELY(cmp == 0)) + return LIST[imid]; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + + if (G_UNLIKELY(imin > imax)) + return NM_LINK_TYPE_NONE; + + imid = (imin + imax) / 2; + } + } +} + +static NMLinkType +_link_type_from_devtype(const char *name) +{ + static const NMLinkType LIST[] = { + NM_LINK_TYPE_BNEP, /* "bluetooth" */ + NM_LINK_TYPE_BOND, /* "bond" */ + NM_LINK_TYPE_BRIDGE, /* "bridge" */ + NM_LINK_TYPE_PPP, /* "ppp" */ + NM_LINK_TYPE_VLAN, /* "vlan" */ + NM_LINK_TYPE_VRF, /* "vrf" */ + NM_LINK_TYPE_VXLAN, /* "vxlan" */ + NM_LINK_TYPE_WIMAX, /* "wimax" */ + NM_LINK_TYPE_WIREGUARD, /* "wireguard" */ + NM_LINK_TYPE_WIFI, /* "wlan" */ + NM_LINK_TYPE_WWAN_NET, /* "wwan" */ + }; + + nm_assert(name); + + if (NM_MORE_ASSERT_ONCE(5)) { + int i, j, k; + + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { + nm_assert(_link_desc_from_link_type(LIST[i]) == &link_descs[LIST[i]]); + nm_assert(link_descs[LIST[i]].devtype); + if (i > 0) + nm_assert(strcmp(link_descs[LIST[i - 1]].devtype, link_descs[LIST[i]].devtype) < 0); + } + for (i = 0; i < G_N_ELEMENTS(link_descs); i++) { + if (!link_descs[i].devtype) + continue; + for (j = 0, k = 0; j < G_N_ELEMENTS(LIST); j++) + k += (LIST[j] == i); + nm_assert(k == 1); + } + } + + { + int imin = 0; + int imax = (G_N_ELEMENTS(LIST) - 1); + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; + + for (;;) { + const int cmp = strcmp(link_descs[LIST[imid]].devtype, name); + + if (G_UNLIKELY(cmp == 0)) + return LIST[imid]; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + + if (G_UNLIKELY(imin > imax)) + return NM_LINK_TYPE_NONE; + + imid = (imin + imax) / 2; + } + } +} + +static const char * +nm_link_type_to_rtnl_type_string(NMLinkType link_type) +{ + return _link_desc_from_link_type(link_type)->rtnl_type; +} + +const char * +nm_link_type_to_string(NMLinkType link_type) +{ + return _link_desc_from_link_type(link_type)->type_string; +} + +/****************************************************************** + * Utilities + ******************************************************************/ + +/* _timestamp_nl_to_ms: + * @timestamp_nl: a timestamp from ifa_cacheinfo. + * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current + * uptime and how often timestamp_nl wrapped. + * + * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds. + * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts + * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However, + * the uint32 counter wraps every 497 days of uptime, so we have to compensate + * for that. */ +static gint64 +_timestamp_nl_to_ms(guint32 timestamp_nl, gint64 monotonic_ms) +{ + const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100); + gint64 timestamp_nl_ms; + + /* convert timestamp from 1/100th of a second to msec. */ + timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100); + + /* timestamp wraps every 497 days. Try to compensate for that.*/ + if (timestamp_nl_ms > monotonic_ms) { + /* timestamp_nl_ms is in the future. Truncate it to *now* */ + timestamp_nl_ms = monotonic_ms; + } else if (monotonic_ms >= WRAP_INTERVAL) { + timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL; + if (timestamp_nl_ms > monotonic_ms) + timestamp_nl_ms -= WRAP_INTERVAL; + } + + return timestamp_nl_ms; +} + +static guint32 +_addrtime_timestamp_to_nm(guint32 timestamp, gint32 *out_now_nm) +{ + gint64 now_nl; + gint64 now_nm; + gint64 result; + + /* timestamp is unset. Default to 1. */ + if (!timestamp) { + NM_SET_OUT(out_now_nm, 0); + return 1; + } + + /* do all the calculations in milliseconds scale */ + + now_nm = nm_utils_get_monotonic_timestamp_msec(); + now_nl = nm_utils_clock_gettime_msec(CLOCK_MONOTONIC); + + nm_assert(now_nm >= 1000); + nm_assert(now_nl >= 0); + + result = now_nm - (now_nl - _timestamp_nl_to_ms(timestamp, now_nl)); + + NM_SET_OUT(out_now_nm, now_nm / 1000); + + /* converting the timestamp into nm_utils_get_monotonic_timestamp_msec() scale is + * a good guess but fails in the following situations: + * + * - If the address existed before start of the process, the timestamp in nm scale would + * be negative or zero. In this case we default to 1. + * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from + * nm_utils_get_monotonic_timestamp_msec() scale. + */ + if (result <= 1000) + return 1; + + if (result > now_nm) + return now_nm / 1000; + + return result / 1000; +} + +static guint32 +_addrtime_extend_lifetime(guint32 lifetime, guint32 seconds) +{ + guint64 v; + + if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT || seconds == 0) + return lifetime; + + v = (guint64) lifetime + (guint64) seconds; + return MIN(v, 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. + **/ +static void +_addrtime_get_lifetimes(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + guint32 *out_timestamp, + guint32 *out_lifetime, + guint32 *out_preferred) +{ + gint32 now; + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { + if (preferred > lifetime) + preferred = lifetime; + timestamp = _addrtime_timestamp_to_nm(timestamp, &now); + + if (now == 0) { + /* strange. failed to detect the last-update time and assumed that timestamp is 1. */ + nm_assert(timestamp == 1); + now = nm_utils_get_monotonic_timestamp_sec(); + } + if (timestamp < now) { + guint32 diff = now - timestamp; + + lifetime = _addrtime_extend_lifetime(lifetime, diff); + preferred = _addrtime_extend_lifetime(preferred, diff); + } else + nm_assert(timestamp == now); + } else + timestamp = 0; + *out_timestamp = timestamp; + *out_lifetime = lifetime; + *out_preferred = preferred; +} + +/*****************************************************************************/ + +static const NMPObject * +_lookup_cached_link(const NMPCache * cache, + int ifindex, + gboolean * completed_from_cache, + const NMPObject **link_cached) +{ + const NMPObject *obj; + + nm_assert(completed_from_cache && link_cached); + + if (!*completed_from_cache) { + obj = ifindex > 0 && cache ? nmp_cache_lookup_link(cache, ifindex) : NULL; + + *link_cached = obj; + *completed_from_cache = TRUE; + } + return *link_cached; +} + +/*****************************************************************************/ + +#define DEVTYPE_PREFIX "DEVTYPE=" + +static char * +_linktype_read_devtype(int dirfd) +{ + gs_free char *contents = NULL; + char * cont, *end; + + nm_assert(dirfd >= 0); + + if (!nm_utils_file_get_contents(dirfd, + "uevent", + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + NULL)) + return NULL; + for (cont = contents; cont; cont = end) { + end = strpbrk(cont, "\r\n"); + if (end) + *end++ = '\0'; + if (strncmp(cont, DEVTYPE_PREFIX, NM_STRLEN(DEVTYPE_PREFIX)) == 0) { + cont += NM_STRLEN(DEVTYPE_PREFIX); + memmove(contents, cont, strlen(cont) + 1); + return g_steal_pointer(&contents); + } + } + return NULL; +} + +static NMLinkType +_linktype_get_type(NMPlatform * platform, + const NMPCache * cache, + const char * kind, + int ifindex, + const char * ifname, + unsigned flags, + unsigned arptype, + gboolean * completed_from_cache, + const NMPObject **link_cached, + const char ** out_kind) +{ + NMLinkType link_type; + + NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform); + nm_assert(ifname); + nm_assert(_link_type_from_devtype("wlan") == NM_LINK_TYPE_WIFI); + nm_assert(_link_type_from_rtnl_type("bond") == NM_LINK_TYPE_BOND); + + if (completed_from_cache) { + const NMPObject *obj; + + obj = _lookup_cached_link(cache, ifindex, completed_from_cache, link_cached); + + /* If we detected the link type before, we stick to that + * decision unless the "kind" or "name" changed. If "name" changed, + * it means that their type may not have been determined correctly + * due to race conditions while accessing sysfs. + * + * This way, we save additional ethtool/sysctl lookups, but moreover, + * we keep the linktype stable and don't change it as long as the link + * exists. + * + * Note that kernel *can* reuse the ifindex (on integer overflow, and + * when moving interface to other netns). Thus here there is a tiny potential + * of messing stuff up. */ + if (obj && obj->_link.netlink.is_in_netlink + && !NM_IN_SET(obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE) + && nm_streq(ifname, obj->link.name) && (!kind || nm_streq0(kind, obj->link.kind))) { + nm_assert(obj->link.kind == g_intern_string(obj->link.kind)); + *out_kind = obj->link.kind; + return obj->link.type; + } + } + + /* we intern kind to not require us to keep the pointer alive. Essentially + * leaking it in a global cache. That should be safe enough, because the + * kind comes only from kernel messages, which depend on the number of + * available drivers. So, there is not the danger that we leak uncontrolled + * many kinds. */ + *out_kind = g_intern_string(kind); + + if (kind) { + link_type = _link_type_from_rtnl_type(kind); + if (link_type != NM_LINK_TYPE_NONE) + return link_type; + } + + if (arptype == ARPHRD_LOOPBACK) + return NM_LINK_TYPE_LOOPBACK; + else if (arptype == ARPHRD_INFINIBAND) + return NM_LINK_TYPE_INFINIBAND; + else if (arptype == ARPHRD_SIT) + return NM_LINK_TYPE_SIT; + else if (arptype == ARPHRD_TUNNEL6) + return NM_LINK_TYPE_IP6TNL; + else if (arptype == ARPHRD_PPP) + return NM_LINK_TYPE_PPP; + else if (arptype == ARPHRD_IEEE802154) + return NM_LINK_TYPE_WPAN; + else if (arptype == ARPHRD_6LOWPAN) + return NM_LINK_TYPE_6LOWPAN; + + { + NMPUtilsEthtoolDriverInfo driver_info; + + /* Fallback OVS detection for kernel <= 3.16 */ + if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (nm_streq(driver_info.driver, "openvswitch")) + return NM_LINK_TYPE_OPENVSWITCH; + + if (arptype == 256) { + /* Some s390 CTC-type devices report 256 for the encapsulation type + * for some reason, but we need to call them Ethernet. + */ + if (nm_streq(driver_info.driver, "ctcm")) + return NM_LINK_TYPE_ETHERNET; + } + } + } + + { + nm_auto_close int dirfd = -1; + gs_free char * devtype = NULL; + char ifname_verified[IFNAMSIZ]; + + dirfd = nmp_utils_sysctl_open_netdir(ifindex, ifname, ifname_verified); + if (dirfd >= 0) { + if (faccessat(dirfd, "anycast_mask", F_OK, 0) == 0) + return NM_LINK_TYPE_OLPC_MESH; + + devtype = _linktype_read_devtype(dirfd); + if (devtype) { + link_type = _link_type_from_devtype(devtype); + if (link_type != NM_LINK_TYPE_NONE) { + if (link_type == NM_LINK_TYPE_BNEP && arptype != ARPHRD_ETHER) { + /* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must + * use arptype to distinguish between them. + */ + } else + return link_type; + } + } + + /* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */ + if (nm_wifi_utils_is_wifi(dirfd, ifname_verified)) + return NM_LINK_TYPE_WIFI; + } + + if (arptype == ARPHRD_ETHER) { + /* Misc non-upstream WWAN drivers. rmnet is Qualcomm's proprietary + * modem interface, ccmni is MediaTek's. FIXME: these drivers should + * really set devtype=WWAN. + */ + if (g_str_has_prefix(ifname, "rmnet") || g_str_has_prefix(ifname, "rev_rmnet") + || g_str_has_prefix(ifname, "ccmni")) + return NM_LINK_TYPE_WWAN_NET; + + /* Standard wired ethernet interfaces don't report an rtnl_link_type, so + * only allow fallback to Ethernet if no type is given. This should + * prevent future virtual network drivers from being treated as Ethernet + * when they should be Generic instead. + */ + if (!kind && !devtype) + return NM_LINK_TYPE_ETHERNET; + + /* The USB gadget interfaces behave and look like ordinary ethernet devices + * aside from the DEVTYPE. */ + if (nm_streq0(devtype, "gadget")) + return NM_LINK_TYPE_ETHERNET; + + /* Distributed Switch Architecture switch chips */ + if (nm_streq0(devtype, "dsa")) + return NM_LINK_TYPE_ETHERNET; + } + } + + return NM_LINK_TYPE_UNKNOWN; +} + +/****************************************************************** + * libnl unility functions and wrappers + ******************************************************************/ + +#define NLMSG_TAIL(nmsg) ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/* copied from iproute2's addattr_l(). */ +static gboolean +_nl_addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) + return FALSE; + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return TRUE; +} + +/****************************************************************** + * NMPObject/netlink functions + ******************************************************************/ + +#define _check_addr_or_return_val(tb, attr, addr_len, ret_val) \ + ({ \ + const struct nlattr *__t = (tb)[(attr)]; \ + \ + if (__t) { \ + if (nla_len(__t) != (addr_len)) { \ + return ret_val; \ + } \ + } \ + !!__t; \ + }) + +#define _check_addr_or_return_null(tb, attr, addr_len) \ + _check_addr_or_return_val(tb, attr, addr_len, NULL) + +/*****************************************************************************/ + +/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */ +static gboolean +_parse_af_inet6(NMPlatform * platform, + struct nlattr * attr, + NMUtilsIPv6IfaceId *out_token, + gboolean * out_token_valid, + guint8 * out_addr_gen_mode_inv, + gboolean * out_addr_gen_mode_valid) +{ + static const struct nla_policy policy[] = { + [IFLA_INET6_FLAGS] = {.type = NLA_U32}, + [IFLA_INET6_CACHEINFO] = {.minlen = nm_offsetofend(struct ifla_cacheinfo, retrans_time)}, + [IFLA_INET6_CONF] = {.minlen = 4}, + [IFLA_INET6_STATS] = {.minlen = 8}, + [IFLA_INET6_ICMP6STATS] = {.minlen = 8}, + [IFLA_INET6_TOKEN] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_INET6_ADDR_GEN_MODE] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + struct in6_addr i6_token; + gboolean token_valid = FALSE; + gboolean addr_gen_mode_valid = FALSE; + guint8 i6_addr_gen_mode_inv = 0; + + if (nla_parse_nested_arr(tb, attr, policy) < 0) + return FALSE; + + if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4) + return FALSE; + if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8) + return FALSE; + if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8) + return FALSE; + + if (_check_addr_or_return_val(tb, IFLA_INET6_TOKEN, sizeof(struct in6_addr), FALSE)) { + nla_memcpy(&i6_token, tb[IFLA_INET6_TOKEN], sizeof(struct in6_addr)); + token_valid = TRUE; + } + + /* Hack to detect support addrgenmode of the kernel. We only parse + * netlink messages that we receive from kernel, hence this check + * is valid. */ + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + /* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL, + tb[IFLA_INET6_ADDR_GEN_MODE] ? 1 : -1); + } + + if (tb[IFLA_INET6_ADDR_GEN_MODE]) { + i6_addr_gen_mode_inv = _nm_platform_uint8_inv(nla_get_u8(tb[IFLA_INET6_ADDR_GEN_MODE])); + if (i6_addr_gen_mode_inv == 0) { + /* an inverse addrgenmode of zero is unexpected. We need to reserve zero + * to signal "unset". */ + return FALSE; + } + addr_gen_mode_valid = TRUE; + } + + if (token_valid) { + *out_token_valid = token_valid; + nm_utils_ipv6_interface_identifier_get_from_addr(out_token, &i6_token); + } + if (addr_gen_mode_valid) { + *out_addr_gen_mode_valid = addr_gen_mode_valid; + *out_addr_gen_mode_inv = i6_addr_gen_mode_inv; + } + return TRUE; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_bridge(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_BR_FORWARD_DELAY] = {.type = NLA_U32}, + [IFLA_BR_HELLO_TIME] = {.type = NLA_U32}, + [IFLA_BR_MAX_AGE] = {.type = NLA_U32}, + [IFLA_BR_AGEING_TIME] = {.type = NLA_U32}, + [IFLA_BR_STP_STATE] = {.type = NLA_U32}, + [IFLA_BR_PRIORITY] = {.type = NLA_U16}, + [IFLA_BR_VLAN_PROTOCOL] = {.type = NLA_U16}, + [IFLA_BR_VLAN_STATS_ENABLED] = {.type = NLA_U8}, + [IFLA_BR_GROUP_FWD_MASK] = {.type = NLA_U16}, + [IFLA_BR_GROUP_ADDR] = {.minlen = sizeof(NMEtherAddr)}, + [IFLA_BR_MCAST_SNOOPING] = {.type = NLA_U8}, + [IFLA_BR_MCAST_ROUTER] = {.type = NLA_U8}, + [IFLA_BR_MCAST_QUERY_USE_IFADDR] = {.type = NLA_U8}, + [IFLA_BR_MCAST_QUERIER] = {.type = NLA_U8}, + [IFLA_BR_MCAST_HASH_MAX] = {.type = NLA_U32}, + [IFLA_BR_MCAST_LAST_MEMBER_CNT] = {.type = NLA_U32}, + [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = {.type = NLA_U32}, + [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERIER_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERY_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = {.type = NLA_U64}, + [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = {.type = NLA_U64}, + }; + NMPlatformLnkBridge *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "bridge")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_BRIDGE, NULL); + + props = &obj->lnk_bridge; + *props = nm_platform_lnk_bridge_default; + + if (!_nm_platform_kernel_support_detected( + NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED)) { + /* IFLA_BR_VLAN_STATS_ENABLED was added in kernel 4.10 on April 30, 2016. + * See commit 6dada9b10a0818ba72c249526a742c8c41274a73. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED, + tb[IFLA_BR_VLAN_STATS_ENABLED] ? 1 : -1); + } + + if (tb[IFLA_BR_FORWARD_DELAY]) + props->forward_delay = nla_get_u32(tb[IFLA_BR_FORWARD_DELAY]); + if (tb[IFLA_BR_HELLO_TIME]) + props->hello_time = nla_get_u32(tb[IFLA_BR_HELLO_TIME]); + if (tb[IFLA_BR_MAX_AGE]) + props->max_age = nla_get_u32(tb[IFLA_BR_MAX_AGE]); + if (tb[IFLA_BR_AGEING_TIME]) + props->ageing_time = nla_get_u32(tb[IFLA_BR_AGEING_TIME]); + if (tb[IFLA_BR_STP_STATE]) + props->stp_state = !!nla_get_u32(tb[IFLA_BR_STP_STATE]); + if (tb[IFLA_BR_PRIORITY]) + props->priority = nla_get_u16(tb[IFLA_BR_PRIORITY]); + if (tb[IFLA_BR_VLAN_PROTOCOL]) + props->vlan_protocol = ntohs(nla_get_u16(tb[IFLA_BR_VLAN_PROTOCOL])); + if (tb[IFLA_BR_VLAN_STATS_ENABLED]) + props->vlan_stats_enabled = nla_get_u8(tb[IFLA_BR_VLAN_STATS_ENABLED]); + if (tb[IFLA_BR_GROUP_FWD_MASK]) + props->group_fwd_mask = nla_get_u16(tb[IFLA_BR_GROUP_FWD_MASK]); + if (tb[IFLA_BR_GROUP_ADDR]) + props->group_addr = *nla_data_as(NMEtherAddr, tb[IFLA_BR_GROUP_ADDR]); + if (tb[IFLA_BR_MCAST_SNOOPING]) + props->mcast_snooping = !!nla_get_u8(tb[IFLA_BR_MCAST_SNOOPING]); + if (tb[IFLA_BR_MCAST_ROUTER]) + props->mcast_router = nla_get_u8(tb[IFLA_BR_MCAST_ROUTER]); + if (tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]) + props->mcast_query_use_ifaddr = !!nla_get_u8(tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]); + if (tb[IFLA_BR_MCAST_QUERIER]) + props->mcast_querier = nla_get_u8(tb[IFLA_BR_MCAST_QUERIER]); + if (tb[IFLA_BR_MCAST_HASH_MAX]) + props->mcast_hash_max = nla_get_u32(tb[IFLA_BR_MCAST_HASH_MAX]); + if (tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]) + props->mcast_last_member_count = nla_get_u32(tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]); + if (tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]) + props->mcast_startup_query_count = nla_get_u32(tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]); + if (tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]) + props->mcast_last_member_interval = nla_get_u64(tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]); + if (tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]) + props->mcast_membership_interval = nla_get_u64(tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]); + if (tb[IFLA_BR_MCAST_QUERIER_INTVL]) + props->mcast_querier_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERIER_INTVL]); + if (tb[IFLA_BR_MCAST_QUERY_INTVL]) + props->mcast_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_INTVL]); + if (tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]) + props->mcast_query_response_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]); + if (tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]) + props->mcast_startup_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]); + + return obj; +} + +/***********************************************************************************/ + +static NMPObject * +_parse_lnk_gre(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_GRE_LINK] = {.type = NLA_U32}, + [IFLA_GRE_IFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_OFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_IKEY] = {.type = NLA_U32}, + [IFLA_GRE_OKEY] = {.type = NLA_U32}, + [IFLA_GRE_LOCAL] = {.type = NLA_U32}, + [IFLA_GRE_REMOTE] = {.type = NLA_U32}, + [IFLA_GRE_TTL] = {.type = NLA_U8}, + [IFLA_GRE_TOS] = {.type = NLA_U8}, + [IFLA_GRE_PMTUDISC] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkGre *props; + gboolean is_tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "gretap")) + is_tap = TRUE; + else if (nm_streq(kind, "gre")) + is_tap = FALSE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL); + props = &obj->lnk_gre; + + props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32(tb[IFLA_GRE_LINK]) : 0; + props->input_flags = tb[IFLA_GRE_IFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS])) : 0; + props->output_flags = tb[IFLA_GRE_OFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS])) : 0; + props->input_key = tb[IFLA_GRE_IKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_IKEY])) : 0; + props->output_key = tb[IFLA_GRE_OKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_OKEY])) : 0; + props->local = tb[IFLA_GRE_LOCAL] ? nla_get_u32(tb[IFLA_GRE_LOCAL]) : 0; + props->remote = tb[IFLA_GRE_REMOTE] ? nla_get_u32(tb[IFLA_GRE_REMOTE]) : 0; + props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8(tb[IFLA_GRE_TOS]) : 0; + props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8(tb[IFLA_GRE_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8(tb[IFLA_GRE_PMTUDISC]); + props->is_tap = is_tap; + + return obj; +} + +/*****************************************************************************/ + +/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers + * we're building against might not have those properties even though the + * running kernel might. + */ +#define IFLA_IPOIB_UNSPEC 0 +#define IFLA_IPOIB_PKEY 1 +#define IFLA_IPOIB_MODE 2 +#define IFLA_IPOIB_UMCAST 3 +#undef IFLA_IPOIB_MAX +#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST + +#define IPOIB_MODE_DATAGRAM 0 /* using unreliable datagram QPs */ +#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */ + +static NMPObject * +_parse_lnk_infiniband(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPOIB_PKEY] = {.type = NLA_U16}, + [IFLA_IPOIB_MODE] = {.type = NLA_U16}, + [IFLA_IPOIB_UMCAST] = {.type = NLA_U16}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPlatformLnkInfiniband *info; + NMPObject * obj; + const char * mode; + + if (!info_data || !nm_streq0(kind, "ipoib")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE]) + return NULL; + + switch (nla_get_u16(tb[IFLA_IPOIB_MODE])) { + case IPOIB_MODE_DATAGRAM: + mode = "datagram"; + break; + case IPOIB_MODE_CONNECTED: + mode = "connected"; + break; + default: + return NULL; + } + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL); + info = &obj->lnk_infiniband; + + info->p_key = nla_get_u16(tb[IFLA_IPOIB_PKEY]); + info->mode = mode; + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_ip6tnl(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_IPTUN_REMOTE] = {.minlen = sizeof(struct in6_addr)}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_ENCAP_LIMIT] = {.type = NLA_U8}, + [IFLA_IPTUN_FLOWINFO] = {.type = NLA_U32}, + [IFLA_IPTUN_PROTO] = {.type = NLA_U8}, + [IFLA_IPTUN_FLAGS] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIp6Tnl *props; + guint32 flowinfo; + + if (!info_data || !nm_streq0(kind, "ip6tnl")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_IP6TNL, NULL); + props = &obj->lnk_ip6tnl; + + if (tb[IFLA_IPTUN_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_IPTUN_LINK]); + if (tb[IFLA_IPTUN_LOCAL]) + props->local = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_LOCAL]); + if (tb[IFLA_IPTUN_REMOTE]) + props->remote = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_REMOTE]); + if (tb[IFLA_IPTUN_TTL]) + props->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]); + if (tb[IFLA_IPTUN_ENCAP_LIMIT]) + props->encap_limit = nla_get_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]); + if (tb[IFLA_IPTUN_FLOWINFO]) { + flowinfo = ntohl(nla_get_u32(tb[IFLA_IPTUN_FLOWINFO])); + props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK; + props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT; + } + if (tb[IFLA_IPTUN_PROTO]) + props->proto = nla_get_u8(tb[IFLA_IPTUN_PROTO]); + if (tb[IFLA_IPTUN_FLAGS]) + props->flags = nla_get_u32(tb[IFLA_IPTUN_FLAGS]); + + return obj; +} + +static NMPObject * +_parse_lnk_ip6gre(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_GRE_LINK] = {.type = NLA_U32}, + [IFLA_GRE_IFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_OFLAGS] = {.type = NLA_U16}, + [IFLA_GRE_IKEY] = {.type = NLA_U32}, + [IFLA_GRE_OKEY] = {.type = NLA_U32}, + [IFLA_GRE_LOCAL] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_GRE_REMOTE] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_GRE_TTL] = {.type = NLA_U8}, + [IFLA_GRE_ENCAP_LIMIT] = {.type = NLA_U8}, + [IFLA_GRE_FLOWINFO] = {.type = NLA_U32}, + [IFLA_GRE_FLAGS] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIp6Tnl *props; + guint32 flowinfo; + gboolean is_tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "ip6gre")) + is_tap = FALSE; + else if (nm_streq(kind, "ip6gretap")) + is_tap = TRUE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL); + props = &obj->lnk_ip6tnl; + props->is_gre = TRUE; + props->is_tap = is_tap; + + if (tb[IFLA_GRE_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_GRE_LINK]); + if (tb[IFLA_GRE_IFLAGS]) + props->input_flags = ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS])); + if (tb[IFLA_GRE_OFLAGS]) + props->output_flags = ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS])); + if (tb[IFLA_GRE_IKEY]) + props->input_key = ntohl(nla_get_u32(tb[IFLA_GRE_IKEY])); + if (tb[IFLA_GRE_OKEY]) + props->output_key = ntohl(nla_get_u32(tb[IFLA_GRE_OKEY])); + if (tb[IFLA_GRE_LOCAL]) + props->local = *nla_data_as(struct in6_addr, tb[IFLA_GRE_LOCAL]); + if (tb[IFLA_GRE_REMOTE]) + props->remote = *nla_data_as(struct in6_addr, tb[IFLA_GRE_REMOTE]); + if (tb[IFLA_GRE_TTL]) + props->ttl = nla_get_u8(tb[IFLA_GRE_TTL]); + if (tb[IFLA_GRE_ENCAP_LIMIT]) + props->encap_limit = nla_get_u8(tb[IFLA_GRE_ENCAP_LIMIT]); + if (tb[IFLA_GRE_FLOWINFO]) { + flowinfo = ntohl(nla_get_u32(tb[IFLA_GRE_FLOWINFO])); + props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK; + props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT; + } + if (tb[IFLA_GRE_FLAGS]) + props->flags = nla_get_u32(tb[IFLA_GRE_FLAGS]); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_ipip(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.type = NLA_U32}, + [IFLA_IPTUN_REMOTE] = {.type = NLA_U32}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_TOS] = {.type = NLA_U8}, + [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkIpIp *props; + + if (!info_data || !nm_streq0(kind, "ipip")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_IPIP, NULL); + props = &obj->lnk_ipip; + + props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0; + props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0; + props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0; + props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0; + props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_macvlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_MACVLAN_MODE] = {.type = NLA_U32}, + [IFLA_MACVLAN_FLAGS] = {.type = NLA_U16}, + }; + NMPlatformLnkMacvlan *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + gboolean tap; + + if (!info_data || !kind) + return NULL; + + if (nm_streq(kind, "macvlan")) + tap = FALSE; + else if (nm_streq(kind, "macvtap")) + tap = TRUE; + else + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_MACVLAN_MODE]) + return NULL; + + obj = nmp_object_new(tap ? NMP_OBJECT_TYPE_LNK_MACVTAP : NMP_OBJECT_TYPE_LNK_MACVLAN, NULL); + props = &obj->lnk_macvlan; + props->mode = nla_get_u32(tb[IFLA_MACVLAN_MODE]); + props->tap = tap; + + if (tb[IFLA_MACVLAN_FLAGS]) + props->no_promisc = + NM_FLAGS_HAS(nla_get_u16(tb[IFLA_MACVLAN_FLAGS]), MACVLAN_FLAG_NOPROMISC); + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_macsec(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_MACSEC_SCI] = {.type = NLA_U64}, + [IFLA_MACSEC_ICV_LEN] = {.type = NLA_U8}, + [IFLA_MACSEC_CIPHER_SUITE] = {.type = NLA_U64}, + [IFLA_MACSEC_WINDOW] = {.type = NLA_U32}, + [IFLA_MACSEC_ENCODING_SA] = {.type = NLA_U8}, + [IFLA_MACSEC_ENCRYPT] = {.type = NLA_U8}, + [IFLA_MACSEC_PROTECT] = {.type = NLA_U8}, + [IFLA_MACSEC_INC_SCI] = {.type = NLA_U8}, + [IFLA_MACSEC_ES] = {.type = NLA_U8}, + [IFLA_MACSEC_SCB] = {.type = NLA_U8}, + [IFLA_MACSEC_REPLAY_PROTECT] = {.type = NLA_U8}, + [IFLA_MACSEC_VALIDATION] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkMacsec *props; + + if (!info_data || !nm_streq0(kind, "macsec")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_MACSEC, NULL); + props = &obj->lnk_macsec; + + if (tb[IFLA_MACSEC_SCI]) { + props->sci = nla_get_be64(tb[IFLA_MACSEC_SCI]); + } + if (tb[IFLA_MACSEC_ICV_LEN]) { + props->icv_length = nla_get_u8(tb[IFLA_MACSEC_ICV_LEN]); + } + if (tb[IFLA_MACSEC_CIPHER_SUITE]) { + props->cipher_suite = nla_get_u64(tb[IFLA_MACSEC_CIPHER_SUITE]); + } + if (tb[IFLA_MACSEC_WINDOW]) { + props->window = nla_get_u32(tb[IFLA_MACSEC_WINDOW]); + } + if (tb[IFLA_MACSEC_ENCODING_SA]) { + props->encoding_sa = !!nla_get_u8(tb[IFLA_MACSEC_ENCODING_SA]); + } + if (tb[IFLA_MACSEC_ENCRYPT]) { + props->encrypt = !!nla_get_u8(tb[IFLA_MACSEC_ENCRYPT]); + } + if (tb[IFLA_MACSEC_PROTECT]) { + props->protect = !!nla_get_u8(tb[IFLA_MACSEC_PROTECT]); + } + if (tb[IFLA_MACSEC_INC_SCI]) { + props->include_sci = !!nla_get_u8(tb[IFLA_MACSEC_INC_SCI]); + } + if (tb[IFLA_MACSEC_ES]) { + props->es = !!nla_get_u8(tb[IFLA_MACSEC_ES]); + } + if (tb[IFLA_MACSEC_SCB]) { + props->scb = !!nla_get_u8(tb[IFLA_MACSEC_SCB]); + } + if (tb[IFLA_MACSEC_REPLAY_PROTECT]) { + props->replay_protect = !!nla_get_u8(tb[IFLA_MACSEC_REPLAY_PROTECT]); + } + if (tb[IFLA_MACSEC_VALIDATION]) { + props->validation = nla_get_u8(tb[IFLA_MACSEC_VALIDATION]); + } + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_sit(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_IPTUN_LINK] = {.type = NLA_U32}, + [IFLA_IPTUN_LOCAL] = {.type = NLA_U32}, + [IFLA_IPTUN_REMOTE] = {.type = NLA_U32}, + [IFLA_IPTUN_TTL] = {.type = NLA_U8}, + [IFLA_IPTUN_TOS] = {.type = NLA_U8}, + [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8}, + [IFLA_IPTUN_FLAGS] = {.type = NLA_U16}, + [IFLA_IPTUN_PROTO] = {.type = NLA_U8}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkSit *props; + + if (!info_data || !nm_streq0(kind, "sit")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_SIT, NULL); + props = &obj->lnk_sit; + + props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0; + props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0; + props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0; + props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0; + props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0; + props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]); + props->flags = tb[IFLA_IPTUN_FLAGS] ? nla_get_u16(tb[IFLA_IPTUN_FLAGS]) : 0; + props->proto = tb[IFLA_IPTUN_PROTO] ? nla_get_u8(tb[IFLA_IPTUN_PROTO]) : 0; + + return obj; +} + +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_tun(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_TUN_OWNER] = {.type = NLA_U32}, + [IFLA_TUN_GROUP] = {.type = NLA_U32}, + [IFLA_TUN_TYPE] = {.type = NLA_U8}, + [IFLA_TUN_PI] = {.type = NLA_U8}, + [IFLA_TUN_VNET_HDR] = {.type = NLA_U8}, + [IFLA_TUN_PERSIST] = {.type = NLA_U8}, + [IFLA_TUN_MULTI_QUEUE] = {.type = NLA_U8}, + [IFLA_TUN_NUM_QUEUES] = {.type = NLA_U32}, + [IFLA_TUN_NUM_DISABLED_QUEUES] = {.type = NLA_U32}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + NMPlatformLnkTun *props; + + if (!info_data || !nm_streq0(kind, "tun")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_TUN_TYPE]) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_TUN, NULL); + props = &obj->lnk_tun; + + props->type = nla_get_u8(tb[IFLA_TUN_TYPE]); + + props->pi = !!nla_get_u8_cond(tb, IFLA_TUN_PI, FALSE); + props->vnet_hdr = !!nla_get_u8_cond(tb, IFLA_TUN_VNET_HDR, FALSE); + props->multi_queue = !!nla_get_u8_cond(tb, IFLA_TUN_MULTI_QUEUE, FALSE); + props->persist = !!nla_get_u8_cond(tb, IFLA_TUN_PERSIST, FALSE); + + if (tb[IFLA_TUN_OWNER]) { + props->owner_valid = TRUE; + props->owner = nla_get_u32(tb[IFLA_TUN_OWNER]); + } + if (tb[IFLA_TUN_GROUP]) { + props->group_valid = TRUE; + props->group = nla_get_u32(tb[IFLA_TUN_GROUP]); + } + return obj; +} + +/*****************************************************************************/ + +static gboolean +_vlan_qos_mapping_from_nla(struct nlattr * nlattr, + const NMVlanQosMapping **out_map, + guint * out_n_map) +{ + struct nlattr * nla; + int remaining; + gs_unref_ptrarray GPtrArray *array = NULL; + + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == sizeof(struct ifla_vlan_qos_mapping)); + G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->to) + == sizeof(((struct ifla_vlan_qos_mapping *) 0)->to)); + G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->from) + == sizeof(((struct ifla_vlan_qos_mapping *) 0)->from)); + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) + == sizeof(((NMVlanQosMapping *) 0)->from) + + sizeof(((NMVlanQosMapping *) 0)->to)); + + nm_assert(out_map && !*out_map); + nm_assert(out_n_map && !*out_n_map); + + if (!nlattr) + return TRUE; + + array = g_ptr_array_new(); + nla_for_each_nested (nla, nlattr, remaining) { + if (nla_len(nla) < sizeof(NMVlanQosMapping)) + return FALSE; + g_ptr_array_add(array, nla_data(nla)); + } + + if (array->len > 0) { + NMVlanQosMapping *list; + guint i, j; + + /* The sorting is necessary, because for egress mapping, kernel + * doesn't sent the items strictly sorted by the from field. */ + g_ptr_array_sort_with_data(array, _vlan_qos_mapping_cmp_from_ptr, NULL); + + list = g_new(NMVlanQosMapping, array->len); + + for (i = 0, j = 0; i < array->len; i++) { + NMVlanQosMapping *map; + + map = array->pdata[i]; + + /* kernel doesn't really send us duplicates. Just be extra cautious + * because we want strong guarantees about the sort order and uniqueness + * of our mapping list (for simpler equality comparison). */ + if (j > 0 && list[j - 1].from == map->from) + list[j - 1] = *map; + else + list[j++] = *map; + } + + *out_n_map = j; + *out_map = list; + } + + return TRUE; +} + +/* Copied and heavily modified from libnl3's vlan_parse() */ +static NMPObject * +_parse_lnk_vlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VLAN_ID] = {.type = NLA_U16}, + [IFLA_VLAN_FLAGS] = {.minlen = nm_offsetofend(struct ifla_vlan_flags, flags)}, + [IFLA_VLAN_INGRESS_QOS] = {.type = NLA_NESTED}, + [IFLA_VLAN_EGRESS_QOS] = {.type = NLA_NESTED}, + [IFLA_VLAN_PROTOCOL] = {.type = NLA_U16}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject * obj_result; + + if (!info_data || !nm_streq0(kind, "vlan")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + if (!tb[IFLA_VLAN_ID]) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VLAN, NULL); + obj->lnk_vlan.id = nla_get_u16(tb[IFLA_VLAN_ID]); + + if (tb[IFLA_VLAN_FLAGS]) { + struct ifla_vlan_flags flags; + + nla_memcpy(&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags)); + + obj->lnk_vlan.flags = flags.flags; + } + + if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_INGRESS_QOS], + &obj->_lnk_vlan.ingress_qos_map, + &obj->_lnk_vlan.n_ingress_qos_map)) + return NULL; + + if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_EGRESS_QOS], + &obj->_lnk_vlan.egress_qos_map, + &obj->_lnk_vlan.n_egress_qos_map)) + return NULL; + + obj_result = obj; + obj = NULL; + return obj_result; +} + +/*****************************************************************************/ + +/* The installed kernel headers might not have VXLAN stuff at all, or + * they might have the original properties, but not PORT, GROUP6, or LOCAL6. + * So until we depend on kernel >= 3.11, we just ignore the actual enum + * in if_link.h and define the values ourselves. + */ +#define IFLA_VXLAN_UNSPEC 0 +#define IFLA_VXLAN_ID 1 +#define IFLA_VXLAN_GROUP 2 +#define IFLA_VXLAN_LINK 3 +#define IFLA_VXLAN_LOCAL 4 +#define IFLA_VXLAN_TTL 5 +#define IFLA_VXLAN_TOS 6 +#define IFLA_VXLAN_LEARNING 7 +#define IFLA_VXLAN_AGEING 8 +#define IFLA_VXLAN_LIMIT 9 +#define IFLA_VXLAN_PORT_RANGE 10 +#define IFLA_VXLAN_PROXY 11 +#define IFLA_VXLAN_RSC 12 +#define IFLA_VXLAN_L2MISS 13 +#define IFLA_VXLAN_L3MISS 14 +#define IFLA_VXLAN_PORT 15 +#define IFLA_VXLAN_GROUP6 16 +#define IFLA_VXLAN_LOCAL6 17 +#undef IFLA_VXLAN_MAX +#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6 + +#define IFLA_VRF_TABLE 1 + +/* older kernel header might not contain 'struct ifla_vxlan_port_range'. + * Redefine it. */ +struct nm_ifla_vxlan_port_range { + guint16 low; + guint16 high; +}; + +static NMPObject * +_parse_lnk_vxlan(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VXLAN_ID] = {.type = NLA_U32}, + [IFLA_VXLAN_GROUP] = {.type = NLA_U32}, + [IFLA_VXLAN_GROUP6] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_VXLAN_LINK] = {.type = NLA_U32}, + [IFLA_VXLAN_LOCAL] = {.type = NLA_U32}, + [IFLA_VXLAN_LOCAL6] = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)}, + [IFLA_VXLAN_TOS] = {.type = NLA_U8}, + [IFLA_VXLAN_TTL] = {.type = NLA_U8}, + [IFLA_VXLAN_LEARNING] = {.type = NLA_U8}, + [IFLA_VXLAN_AGEING] = {.type = NLA_U32}, + [IFLA_VXLAN_LIMIT] = {.type = NLA_U32}, + [IFLA_VXLAN_PORT_RANGE] = {.type = NLA_UNSPEC, + .minlen = sizeof(struct nm_ifla_vxlan_port_range)}, + [IFLA_VXLAN_PROXY] = {.type = NLA_U8}, + [IFLA_VXLAN_RSC] = {.type = NLA_U8}, + [IFLA_VXLAN_L2MISS] = {.type = NLA_U8}, + [IFLA_VXLAN_L3MISS] = {.type = NLA_U8}, + [IFLA_VXLAN_PORT] = {.type = NLA_U16}, + }; + NMPlatformLnkVxlan *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "vxlan")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VXLAN, NULL); + + props = &obj->lnk_vxlan; + + if (tb[IFLA_VXLAN_LINK]) + props->parent_ifindex = nla_get_u32(tb[IFLA_VXLAN_LINK]); + if (tb[IFLA_VXLAN_ID]) + props->id = nla_get_u32(tb[IFLA_VXLAN_ID]); + if (tb[IFLA_VXLAN_GROUP]) + props->group = nla_get_u32(tb[IFLA_VXLAN_GROUP]); + if (tb[IFLA_VXLAN_LOCAL]) + props->local = nla_get_u32(tb[IFLA_VXLAN_LOCAL]); + if (tb[IFLA_VXLAN_LOCAL6]) + props->local6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_LOCAL6]); + if (tb[IFLA_VXLAN_GROUP6]) + props->group6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_GROUP6]); + + if (tb[IFLA_VXLAN_AGEING]) + props->ageing = nla_get_u32(tb[IFLA_VXLAN_AGEING]); + if (tb[IFLA_VXLAN_LIMIT]) + props->limit = nla_get_u32(tb[IFLA_VXLAN_LIMIT]); + if (tb[IFLA_VXLAN_TOS]) + props->tos = nla_get_u8(tb[IFLA_VXLAN_TOS]); + if (tb[IFLA_VXLAN_TTL]) + props->ttl = nla_get_u8(tb[IFLA_VXLAN_TTL]); + + if (tb[IFLA_VXLAN_PORT]) + props->dst_port = ntohs(nla_get_u16(tb[IFLA_VXLAN_PORT])); + + if (tb[IFLA_VXLAN_PORT_RANGE]) { + struct nm_ifla_vxlan_port_range *range; + + range = nla_data_as(struct nm_ifla_vxlan_port_range, tb[IFLA_VXLAN_PORT_RANGE]); + props->src_port_min = ntohs(range->low); + props->src_port_max = ntohs(range->high); + } + + if (tb[IFLA_VXLAN_LEARNING]) + props->learning = !!nla_get_u8(tb[IFLA_VXLAN_LEARNING]); + if (tb[IFLA_VXLAN_PROXY]) + props->proxy = !!nla_get_u8(tb[IFLA_VXLAN_PROXY]); + if (tb[IFLA_VXLAN_RSC]) + props->rsc = !!nla_get_u8(tb[IFLA_VXLAN_RSC]); + if (tb[IFLA_VXLAN_L2MISS]) + props->l2miss = !!nla_get_u8(tb[IFLA_VXLAN_L2MISS]); + if (tb[IFLA_VXLAN_L3MISS]) + props->l3miss = !!nla_get_u8(tb[IFLA_VXLAN_L3MISS]); + + return obj; +} + +static NMPObject * +_parse_lnk_vrf(const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[] = { + [IFLA_VRF_TABLE] = {.type = NLA_U32}, + }; + NMPlatformLnkVrf *props; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj; + + if (!info_data || !nm_streq0(kind, "vrf")) + return NULL; + + if (nla_parse_nested_arr(tb, info_data, policy) < 0) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VRF, NULL); + + props = &obj->lnk_vrf; + + if (tb[IFLA_VRF_TABLE]) + props->table = nla_get_u32(tb[IFLA_VRF_TABLE]); + + return obj; +} + +/*****************************************************************************/ + +static gboolean +_wireguard_update_from_allowed_ips_nla(NMPWireGuardAllowedIP *allowed_ip, struct nlattr *nlattr) +{ + static const struct nla_policy policy[] = { + [WGALLOWEDIP_A_FAMILY] = {.type = NLA_U16}, + [WGALLOWEDIP_A_IPADDR] = {.minlen = sizeof(struct in_addr)}, + [WGALLOWEDIP_A_CIDR_MASK] = {.type = NLA_U8}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + int family; + int addr_len; + + if (nla_parse_nested_arr(tb, nlattr, policy) < 0) + return FALSE; + + if (!tb[WGALLOWEDIP_A_FAMILY]) + return FALSE; + + family = nla_get_u16(tb[WGALLOWEDIP_A_FAMILY]); + if (family == AF_INET) + addr_len = sizeof(in_addr_t); + else if (family == AF_INET6) + addr_len = sizeof(struct in6_addr); + else + return FALSE; + + _check_addr_or_return_val(tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE); + + *allowed_ip = (NMPWireGuardAllowedIP){ + .family = family, + }; + + nm_assert((int) allowed_ip->family == family); + + if (tb[WGALLOWEDIP_A_IPADDR]) + nla_memcpy(&allowed_ip->addr, tb[WGALLOWEDIP_A_IPADDR], addr_len); + if (tb[WGALLOWEDIP_A_CIDR_MASK]) + allowed_ip->mask = nla_get_u8(tb[WGALLOWEDIP_A_CIDR_MASK]); + + return TRUE; +} + +typedef struct { + CList lst; + NMPWireGuardPeer data; +} WireGuardPeerConstruct; + +static gboolean +_wireguard_update_from_peers_nla(CList *peers, GArray **p_allowed_ips, struct nlattr *peer_attr) +{ + static const struct nla_policy policy[] = { + [WGPEER_A_PUBLIC_KEY] = {.minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN}, + [WGPEER_A_PRESHARED_KEY] = {}, + [WGPEER_A_FLAGS] = {.type = NLA_U32}, + [WGPEER_A_ENDPOINT] = {}, + [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = {.type = NLA_U16}, + [WGPEER_A_LAST_HANDSHAKE_TIME] = {}, + [WGPEER_A_RX_BYTES] = {.type = NLA_U64}, + [WGPEER_A_TX_BYTES] = {.type = NLA_U64}, + [WGPEER_A_ALLOWEDIPS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + WireGuardPeerConstruct *peer_c; + + if (nla_parse_nested_arr(tb, peer_attr, policy) < 0) + return FALSE; + + if (!tb[WGPEER_A_PUBLIC_KEY]) + return FALSE; + + /* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */ + peer_c = c_list_last_entry(peers, WireGuardPeerConstruct, lst); + if (peer_c + && !memcmp(nla_data(tb[WGPEER_A_PUBLIC_KEY]), + peer_c->data.public_key, + NMP_WIREGUARD_PUBLIC_KEY_LEN)) { + G_STATIC_ASSERT_EXPR(NMP_WIREGUARD_PUBLIC_KEY_LEN == sizeof(peer_c->data.public_key)); + /* this message is a continuation of the previous peer. + * Only parse WGPEER_A_ALLOWEDIPS below. */ + } else { + /* otherwise, start a new peer */ + peer_c = g_slice_new0(WireGuardPeerConstruct); + c_list_link_tail(peers, &peer_c->lst); + + nla_memcpy(&peer_c->data.public_key, + tb[WGPEER_A_PUBLIC_KEY], + sizeof(peer_c->data.public_key)); + + if (tb[WGPEER_A_PRESHARED_KEY]) { + nla_memcpy(&peer_c->data.preshared_key, + tb[WGPEER_A_PRESHARED_KEY], + sizeof(peer_c->data.preshared_key)); + /* FIXME(netlink-bzero-secret) */ + nm_explicit_bzero(nla_data(tb[WGPEER_A_PRESHARED_KEY]), + nla_len(tb[WGPEER_A_PRESHARED_KEY])); + } + + nm_sock_addr_union_cpy_untrusted( + &peer_c->data.endpoint, + tb[WGPEER_A_ENDPOINT] ? nla_data(tb[WGPEER_A_ENDPOINT]) : NULL, + tb[WGPEER_A_ENDPOINT] ? nla_len(tb[WGPEER_A_ENDPOINT]) : 0); + + if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]) + peer_c->data.persistent_keepalive_interval = + nla_get_u16(tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]); + if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) { + if (nla_len(tb[WGPEER_A_LAST_HANDSHAKE_TIME]) + >= sizeof(peer_c->data.last_handshake_time)) + nla_memcpy(&peer_c->data.last_handshake_time, + tb[WGPEER_A_LAST_HANDSHAKE_TIME], + sizeof(peer_c->data.last_handshake_time)); + } + if (tb[WGPEER_A_RX_BYTES]) + peer_c->data.rx_bytes = nla_get_u64(tb[WGPEER_A_RX_BYTES]); + if (tb[WGPEER_A_TX_BYTES]) + peer_c->data.tx_bytes = nla_get_u64(tb[WGPEER_A_TX_BYTES]); + } + + if (tb[WGPEER_A_ALLOWEDIPS]) { + struct nlattr *attr; + int rem; + GArray * allowed_ips = *p_allowed_ips; + + nla_for_each_nested (attr, tb[WGPEER_A_ALLOWEDIPS], rem) { + if (!allowed_ips) { + allowed_ips = g_array_new(FALSE, FALSE, sizeof(NMPWireGuardAllowedIP)); + *p_allowed_ips = allowed_ips; + g_array_set_size(allowed_ips, 1); + } else + g_array_set_size(allowed_ips, allowed_ips->len + 1); + + if (!_wireguard_update_from_allowed_ips_nla( + &g_array_index(allowed_ips, NMPWireGuardAllowedIP, allowed_ips->len - 1), + attr)) { + /* we ignore the error of parsing one allowed-ip. */ + g_array_set_size(allowed_ips, allowed_ips->len - 1); + continue; + } + + if (!peer_c->data._construct_idx_end) + peer_c->data._construct_idx_start = allowed_ips->len - 1; + peer_c->data._construct_idx_end = allowed_ips->len; + } + } + + return TRUE; +} + +typedef struct { + const int ifindex; + NMPObject *obj; + CList peers; + GArray * allowed_ips; +} WireGuardParseData; + +static int +_wireguard_get_device_cb(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy policy[] = { + [WGDEVICE_A_IFINDEX] = {.type = NLA_U32}, + [WGDEVICE_A_IFNAME] = {.type = NLA_NUL_STRING, .maxlen = IFNAMSIZ}, + [WGDEVICE_A_PRIVATE_KEY] = {}, + [WGDEVICE_A_PUBLIC_KEY] = {}, + [WGDEVICE_A_FLAGS] = {.type = NLA_U32}, + [WGDEVICE_A_LISTEN_PORT] = {.type = NLA_U16}, + [WGDEVICE_A_FWMARK] = {.type = NLA_U32}, + [WGDEVICE_A_PEERS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + WireGuardParseData *parse_data = arg; + + if (genlmsg_parse_arr(nlmsg_hdr(msg), 0, tb, policy) < 0) + return NL_SKIP; + + if (tb[WGDEVICE_A_IFINDEX]) { + int ifindex; + + ifindex = (int) nla_get_u32(tb[WGDEVICE_A_IFINDEX]); + if (ifindex <= 0 || parse_data->ifindex != ifindex) + return NL_SKIP; + } else { + if (!parse_data->obj) + return NL_SKIP; + } + + if (parse_data->obj) { + /* we already have an object instance. This means the netlink message + * is a continuation, only providing more WGDEVICE_A_PEERS data below. */ + } else { + NMPObject * obj; + NMPlatformLnkWireGuard *props; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL); + props = &obj->lnk_wireguard; + + if (tb[WGDEVICE_A_PRIVATE_KEY]) { + nla_memcpy(props->private_key, tb[WGDEVICE_A_PRIVATE_KEY], sizeof(props->private_key)); + /* FIXME(netlink-bzero-secret): extend netlink library to wipe memory. For now, + * just hack it here (yes, this does not cover all places where the + * private key was copied). */ + nm_explicit_bzero(nla_data(tb[WGDEVICE_A_PRIVATE_KEY]), + nla_len(tb[WGDEVICE_A_PRIVATE_KEY])); + } + if (tb[WGDEVICE_A_PUBLIC_KEY]) + nla_memcpy(props->public_key, tb[WGDEVICE_A_PUBLIC_KEY], sizeof(props->public_key)); + if (tb[WGDEVICE_A_LISTEN_PORT]) + props->listen_port = nla_get_u16(tb[WGDEVICE_A_LISTEN_PORT]); + if (tb[WGDEVICE_A_FWMARK]) + props->fwmark = nla_get_u32(tb[WGDEVICE_A_FWMARK]); + + parse_data->obj = obj; + } + + if (tb[WGDEVICE_A_PEERS]) { + struct nlattr *attr; + int rem; + + nla_for_each_nested (attr, tb[WGDEVICE_A_PEERS], rem) { + if (!_wireguard_update_from_peers_nla(&parse_data->peers, + &parse_data->allowed_ips, + attr)) { + /* we ignore the error of parsing one peer. + * _wireguard_update_from_peers_nla() leaves the @peers array in the + * desired state. */ + } + } + } + + return NL_OK; +} + +static const NMPObject * +_wireguard_read_info(NMPlatform * platform /* used only as logging context */, + struct nl_sock *genl, + int wireguard_family_id, + int ifindex) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + NMPObject * obj = NULL; + WireGuardPeerConstruct * peer_c; + WireGuardPeerConstruct * peer_c_safe; + gs_unref_array GArray *allowed_ips = NULL; + WireGuardParseData parse_data = { + .ifindex = ifindex, + }; + guint i; + + nm_assert(genl); + nm_assert(wireguard_family_id >= 0); + nm_assert(ifindex > 0); + + _LOGT("wireguard: fetching information for ifindex %d (genl-id %d)...", + ifindex, + wireguard_family_id); + + msg = nlmsg_alloc(); + + if (!genlmsg_put(msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + wireguard_family_id, + 0, + NLM_F_DUMP, + WG_CMD_GET_DEVICE, + 1)) + return NULL; + + NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex); + + if (nl_send_auto(genl, msg) < 0) + return NULL; + + c_list_init(&parse_data.peers); + + /* we ignore errors, and return whatever we could successfully + * parse. */ + nl_recvmsgs(genl, + &((const struct nl_cb){ + .valid_cb = _wireguard_get_device_cb, + .valid_arg = (gpointer) &parse_data, + })); + + /* unpack: transfer ownership */ + obj = parse_data.obj; + allowed_ips = parse_data.allowed_ips; + + if (!obj) { + while ((peer_c = c_list_first_entry(&parse_data.peers, WireGuardPeerConstruct, lst))) { + c_list_unlink_stale(&peer_c->lst); + nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key)); + g_slice_free(WireGuardPeerConstruct, peer_c); + } + return NULL; + } + + /* we receive peers/allowed-ips possibly in separate netlink messages. Hence, while + * parsing the dump, we don't know upfront how many peers/allowed-ips we will receive. + * + * We solve that, by collecting all peers with a CList. It's done this way, + * because a GArray would require growing the array, but we want to bzero() + * the preshared-key of each peer while reallocating. The CList apprach avoids + * that. + * + * For allowed-ips, we instead track one GArray, which are all appended + * there. The realloc/resize of the GArray is fine there. However, + * while we build the GArray, we don't yet have the final pointers. + * Hence, while constructing, we track the indexes with peer->_construct_idx_* + * fields. These indexes must be converted to actual pointers blow. + * + * This is all done during parsing. In the final NMPObjectLnkWireGuard we + * don't want the CList anymore and repackage the NMPObject tightly. The + * reason is, that NMPObject instances are immutable and long-living. Spend + * a bit effort below during construction to obtain a most suitable representation + * in this regard. */ + obj->_lnk_wireguard.peers_len = c_list_length(&parse_data.peers); + obj->_lnk_wireguard.peers = obj->_lnk_wireguard.peers_len > 0 + ? g_new(NMPWireGuardPeer, obj->_lnk_wireguard.peers_len) + : NULL; + + /* duplicate allowed_ips instead of using the pointer. The GArray possibly has more + * space allocated then we need, and we want to get rid of this excess buffer. + * Note that NMPObject instance is possibly put into the cache and long-living. */ + obj->_lnk_wireguard._allowed_ips_buf_len = allowed_ips ? allowed_ips->len : 0u; + obj->_lnk_wireguard._allowed_ips_buf = + obj->_lnk_wireguard._allowed_ips_buf_len > 0 + ? (NMPWireGuardAllowedIP *) nm_memdup(allowed_ips->data, + sizeof(NMPWireGuardAllowedIP) * allowed_ips->len) + : NULL; + + i = 0; + c_list_for_each_entry_safe (peer_c, peer_c_safe, &parse_data.peers, lst) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &obj->_lnk_wireguard.peers[i++]; + + *peer = peer_c->data; + + c_list_unlink_stale(&peer_c->lst); + nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key)); + g_slice_free(WireGuardPeerConstruct, peer_c); + + if (peer->_construct_idx_end != 0) { + guint len; + + nm_assert(obj->_lnk_wireguard._allowed_ips_buf); + nm_assert(peer->_construct_idx_end > peer->_construct_idx_start); + nm_assert(peer->_construct_idx_start < obj->_lnk_wireguard._allowed_ips_buf_len); + nm_assert(peer->_construct_idx_end <= obj->_lnk_wireguard._allowed_ips_buf_len); + + len = peer->_construct_idx_end - peer->_construct_idx_start; + peer->allowed_ips = &obj->_lnk_wireguard._allowed_ips_buf[peer->_construct_idx_start]; + peer->allowed_ips_len = len; + } else { + nm_assert(!peer->_construct_idx_start); + nm_assert(!peer->_construct_idx_end); + peer->allowed_ips = NULL; + peer->allowed_ips_len = 0; + } + } + + return obj; + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static int +_wireguard_get_family_id(NMPlatform *platform, int ifindex_try) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int wireguard_family_id = -1; + + if (ifindex_try > 0) { + const NMPlatformLink *plink; + + if (nm_platform_link_get_lnk_wireguard(platform, ifindex_try, &plink)) + wireguard_family_id = NMP_OBJECT_UP_CAST(plink)->_link.wireguard_family_id; + } + if (wireguard_family_id < 0) + wireguard_family_id = genl_ctrl_resolve(priv->genl, "wireguard"); + return wireguard_family_id; +} + +static const NMPObject * +_wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifindex) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + nm_auto_nmpobj const NMPObject *lnk_new = NULL; + NMPCacheOpsType cache_op; + const NMPObject * plink = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + + nm_assert(wireguard_family_id >= 0); + nm_assert(ifindex > 0); + + nm_platform_process_events(platform); + + plink = nm_platform_link_get_obj(platform, ifindex, TRUE); + + if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD) { + nm_platform_link_refresh(platform, ifindex); + plink = nm_platform_link_get_obj(platform, ifindex, TRUE); + if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD) + return NULL; + if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } else { + lnk_new = _wireguard_read_info(platform, priv->genl, wireguard_family_id, ifindex); + if (!lnk_new) { + if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD) + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } else if (nmp_object_equal(plink->_link.netlink.lnk, lnk_new)) { + nmp_object_unref(lnk_new); + lnk_new = nmp_object_ref(plink->_link.netlink.lnk); + } + } + + if (plink->_link.wireguard_family_id == wireguard_family_id + && plink->_link.netlink.lnk == lnk_new) + return plink; + + /* we use nmp_cache_update_netlink() to re-inject the new object into the cache. + * For that, we need to clone it, and tweak it so that it's suitable. It's a bit + * of a hack, in particular that we need to clear driver and udev-device. */ + obj = nmp_object_clone(plink, FALSE); + obj->_link.wireguard_family_id = wireguard_family_id; + nmp_object_unref(obj->_link.netlink.lnk); + obj->_link.netlink.lnk = g_steal_pointer(&lnk_new); + obj->link.driver = NULL; + nm_clear_pointer(&obj->_link.udev.device, udev_device_unref); + + cache_op = + nmp_cache_update_netlink(nm_platform_get_cache(platform), obj, FALSE, &obj_old, &obj_new); + nm_assert(NM_IN_SET(cache_op, NMP_CACHE_OPS_UPDATED)); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + + nm_assert(!obj_new + || (NMP_OBJECT_GET_TYPE(obj_new) == NMP_OBJECT_TYPE_LINK + && obj_new->link.type == NM_LINK_TYPE_WIREGUARD + && (!obj_new->_link.netlink.lnk + || NMP_OBJECT_GET_TYPE(obj_new->_link.netlink.lnk) + == NMP_OBJECT_TYPE_LNK_WIREGUARD))); + return obj_new; +} + +static int +_wireguard_create_change_nlmsgs(NMPlatform * platform, + int ifindex, + int wireguard_family_id, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags, + GPtrArray ** out_msgs) +{ + gs_unref_ptrarray GPtrArray * msgs = NULL; + nm_auto_nlmsg struct nl_msg * msg = NULL; + const guint IDX_NIL = G_MAXUINT; + guint idx_peer_curr; + guint idx_allowed_ips_curr; + struct nlattr * nest_peers; + struct nlattr * nest_curr_peer; + struct nlattr * nest_allowed_ips; + struct nlattr * nest_curr_allowed_ip; + NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT; + +#define _nla_nest_end(msg, nest_start) \ + G_STMT_START \ + { \ + if (nla_nest_end((msg), (nest_start)) < 0) \ + g_return_val_if_reached(-NME_BUG); \ + } \ + G_STMT_END + + /* Adapted from LGPL-2.1+ code [1]. + * + * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */ + + idx_peer_curr = IDX_NIL; + idx_allowed_ips_curr = IDX_NIL; + + /* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS). + * The platform API should be extended to also support partial updates. In particular, configuring the same configuration + * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */ + +again: + + msg = nlmsg_alloc(); + if (!genlmsg_put(msg, + NL_AUTO_PORT, + NL_AUTO_SEQ, + wireguard_family_id, + 0, + NLM_F_REQUEST, + WG_CMD_SET_DEVICE, + 1)) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex); + + if (idx_peer_curr == IDX_NIL) { + guint32 flags; + + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY)) + NLA_PUT(msg, + WGDEVICE_A_PRIVATE_KEY, + sizeof(lnk_wireguard->private_key), + lnk_wireguard->private_key); + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT)) + NLA_PUT_U16(msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port); + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK)) + NLA_PUT_U32(msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark); + + flags = 0; + if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS)) + flags |= WGDEVICE_F_REPLACE_PEERS; + NLA_PUT_U32(msg, WGDEVICE_A_FLAGS, flags); + } + + if (peers_len == 0) + goto send; + + nest_curr_peer = NULL; + nest_allowed_ips = NULL; + nest_curr_allowed_ip = NULL; + + nest_peers = nla_nest_start(msg, WGDEVICE_A_PEERS); + if (!nest_peers) + g_return_val_if_reached(-NME_BUG); + + if (idx_peer_curr == IDX_NIL) + idx_peer_curr = 0; + for (; idx_peer_curr < peers_len; idx_peer_curr++) { + const NMPWireGuardPeer *p = &peers[idx_peer_curr]; + + if (peer_flags) { + p_flags = peer_flags[idx_peer_curr]; + if (!NM_FLAGS_ANY(p_flags, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)) { + /* no flags set. We take that as indication to skip configuring the peer + * entirely. */ + nm_assert(p_flags == NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE); + continue; + } + } + + nest_curr_peer = nla_nest_start(msg, 0); + if (!nest_curr_peer) + goto toobig_peers; + + if (nla_put(msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME)) { + /* all other p_flags are silently ignored. */ + if (nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME) < 0) + goto toobig_peers; + } else { + if (idx_allowed_ips_curr == IDX_NIL) { + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY) + && nla_put(msg, + WGPEER_A_PRESHARED_KEY, + sizeof(p->preshared_key), + p->preshared_key) + < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL) + && nla_put_uint16(msg, + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + p->persistent_keepalive_interval) + < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS) + && nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0) + goto toobig_peers; + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT)) { + if (NM_IN_SET(p->endpoint.sa.sa_family, AF_INET, AF_INET6)) { + if (nla_put(msg, + WGPEER_A_ENDPOINT, + p->endpoint.sa.sa_family == AF_INET ? sizeof(p->endpoint.in) + : sizeof(p->endpoint.in6), + &p->endpoint) + < 0) + goto toobig_peers; + } else { + /* I think there is no way to clear an endpoint, though there should be. */ + nm_assert(p->endpoint.sa.sa_family == AF_UNSPEC); + } + } + } + + if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS) + && p->allowed_ips_len > 0) { + if (idx_allowed_ips_curr == IDX_NIL) + idx_allowed_ips_curr = 0; + + nest_allowed_ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS); + if (!nest_allowed_ips) + goto toobig_allowedips; + + for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) { + const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr]; + + nest_curr_allowed_ip = nla_nest_start(msg, 0); + if (!nest_curr_allowed_ip) + goto toobig_allowedips; + + g_return_val_if_fail(NM_IN_SET(aip->family, AF_INET, AF_INET6), -NME_BUG); + + if (nla_put_uint16(msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0) + goto toobig_allowedips; + if (nla_put(msg, + WGALLOWEDIP_A_IPADDR, + nm_utils_addr_family_to_size(aip->family), + &aip->addr) + < 0) + goto toobig_allowedips; + if (nla_put_uint8(msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0) + goto toobig_allowedips; + + _nla_nest_end(msg, nest_curr_allowed_ip); + nest_curr_allowed_ip = NULL; + } + idx_allowed_ips_curr = IDX_NIL; + + _nla_nest_end(msg, nest_allowed_ips); + nest_allowed_ips = NULL; + } + } + + _nla_nest_end(msg, nest_curr_peer); + nest_curr_peer = NULL; + } + + _nla_nest_end(msg, nest_peers); + goto send; + +toobig_allowedips: + if (nest_curr_allowed_ip) + nla_nest_cancel(msg, nest_curr_allowed_ip); + if (nest_allowed_ips) + _nla_nest_end(msg, nest_allowed_ips); + _nla_nest_end(msg, nest_curr_peer); + _nla_nest_end(msg, nest_peers); + goto send; + +toobig_peers: + if (nest_curr_peer) + nla_nest_cancel(msg, nest_curr_peer); + _nla_nest_end(msg, nest_peers); + goto send; + +send: + if (!msgs) + msgs = g_ptr_array_new_with_free_func((GDestroyNotify) nlmsg_free); + g_ptr_array_add(msgs, g_steal_pointer(&msg)); + + if (idx_peer_curr != IDX_NIL && idx_peer_curr < peers_len) + goto again; + + NM_SET_OUT(out_msgs, g_steal_pointer(&msgs)); + return 0; + +nla_put_failure: + g_return_val_if_reached(-NME_BUG); + +#undef _nla_nest_end +} + +static int +link_wireguard_change(NMPlatform * platform, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gs_unref_ptrarray GPtrArray *msgs = NULL; + int wireguard_family_id; + guint i; + int r; + + wireguard_family_id = _wireguard_get_family_id(platform, ifindex); + if (wireguard_family_id < 0) + return -NME_PL_NO_FIRMWARE; + + r = _wireguard_create_change_nlmsgs(platform, + ifindex, + wireguard_family_id, + lnk_wireguard, + peers, + peer_flags, + peers_len, + change_flags, + &msgs); + if (r < 0) { + _LOGW("wireguard: set-device, cannot construct netlink message: %s", nm_strerror(r)); + return r; + } + + for (i = 0; i < msgs->len; i++) { + r = nl_send_auto(priv->genl, msgs->pdata[i]); + if (r < 0) { + _LOGW("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror(r)); + return r; + } + + do { + r = nl_recvmsgs(priv->genl, NULL); + } while (r == -EAGAIN); + if (r < 0) { + _LOGW("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror(r)); + return r; + } + + _LOGT("wireguard: set-device, message #%u sent and confirmed", i); + } + + _wireguard_refresh_link(platform, wireguard_family_id, ifindex); + + return 0; +} + +/*****************************************************************************/ + +static void +_nmp_link_address_set(NMPLinkAddress *dst, const struct nlattr *nla) +{ + *dst = (NMPLinkAddress){ + .len = 0, + }; + if (nla) { + int l = nla_len(nla); + + if (l > 0 && l <= _NM_UTILS_HWADDR_LEN_MAX) { + G_STATIC_ASSERT_EXPR(sizeof(dst->data) == _NM_UTILS_HWADDR_LEN_MAX); + memcpy(dst->data, nla_data(nla), l); + dst->len = l; + } + } +} + +/* Copied and heavily modified from libnl3's link_msg_parser(). */ +static NMPObject * +_new_from_nl_link(NMPlatform * platform, + const NMPCache * cache, + struct nlmsghdr *nlh, + gboolean id_only) +{ + static const struct nla_policy policy[] = { + [IFLA_IFNAME] = {.type = NLA_STRING, .maxlen = IFNAMSIZ}, + [IFLA_MTU] = {.type = NLA_U32}, + [IFLA_TXQLEN] = {.type = NLA_U32}, + [IFLA_LINK] = {.type = NLA_U32}, + [IFLA_WEIGHT] = {.type = NLA_U32}, + [IFLA_MASTER] = {.type = NLA_U32}, + [IFLA_OPERSTATE] = {.type = NLA_U8}, + [IFLA_LINKMODE] = {.type = NLA_U8}, + [IFLA_LINKINFO] = {.type = NLA_NESTED}, + [IFLA_QDISC] = {.type = NLA_STRING, .maxlen = IFQDISCSIZ}, + [IFLA_STATS] = {.minlen = nm_offsetofend(struct rtnl_link_stats, tx_compressed)}, + [IFLA_STATS64] = {.minlen = nm_offsetofend(struct rtnl_link_stats64, tx_compressed)}, + [IFLA_MAP] = {.minlen = nm_offsetofend(struct rtnl_link_ifmap, port)}, + [IFLA_IFALIAS] = {.type = NLA_STRING, .maxlen = IFALIASZ}, + [IFLA_NUM_VF] = {.type = NLA_U32}, + [IFLA_AF_SPEC] = {.type = NLA_NESTED}, + [IFLA_PROMISCUITY] = {.type = NLA_U32}, + [IFLA_NUM_TX_QUEUES] = {.type = NLA_U32}, + [IFLA_NUM_RX_QUEUES] = {.type = NLA_U32}, + [IFLA_GROUP] = {.type = NLA_U32}, + [IFLA_CARRIER] = {.type = NLA_U8}, + [IFLA_PHYS_PORT_ID] = {.type = NLA_UNSPEC}, + [IFLA_NET_NS_PID] = {.type = NLA_U32}, + [IFLA_NET_NS_FD] = {.type = NLA_U32}, + [IFLA_LINK_NETNSID] = {}, + }; + const struct ifinfomsg *ifi; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + struct nlattr * nl_info_data = NULL; + const char * nl_info_kind = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + gboolean completed_from_cache_val = FALSE; + gboolean * completed_from_cache = cache ? &completed_from_cache_val : NULL; + const NMPObject * link_cached = NULL; + const NMPObject * lnk_data = NULL; + gboolean address_complete_from_cache = TRUE; + gboolean broadcast_complete_from_cache = TRUE; + gboolean lnk_data_complete_from_cache = TRUE; + gboolean need_ext_data = FALSE; + gboolean af_inet6_token_valid = FALSE; + gboolean af_inet6_addr_gen_mode_valid = FALSE; + + if (!nlmsg_valid_hdr(nlh, sizeof(*ifi))) + return NULL; + + ifi = nlmsg_data(nlh); + + if (ifi->ifi_family != AF_UNSPEC) + return NULL; + if (ifi->ifi_index <= 0) + return NULL; + + obj = nmp_object_new_link(ifi->ifi_index); + + if (id_only) + return g_steal_pointer(&obj); + + if (nlmsg_parse_arr(nlh, sizeof(*ifi), tb, policy) < 0) + return NULL; + + if (!tb[IFLA_IFNAME]) + return NULL; + nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ); + if (!obj->link.name[0]) + return NULL; + + if (!tb[IFLA_MTU]) { + /* Kernel has two places that send RTM_GETLINK messages: + * net/core/rtnetlink.c and net/wireless/ext-core.c. + * Unfortunately ext-core.c sets only IFLA_WIRELESS and + * IFLA_IFNAME. This confuses code in this function, because + * it cannot get complete set of data for the interface and + * later incomplete object this function creates is used to + * overwrite existing data in NM's cache. + * Since ext-core.c doesn't set IFLA_MTU we can use it as a + * signal to ignore incoming message. + * To some extent this is a hack and correct approach is to + * merge objects per-field. + */ + return NULL; + } + obj->link.mtu = nla_get_u32(tb[IFLA_MTU]); + + if (tb[IFLA_LINKINFO]) { + static const struct nla_policy policy_link_info[] = { + [IFLA_INFO_KIND] = {.type = NLA_STRING}, + [IFLA_INFO_DATA] = {.type = NLA_NESTED}, + [IFLA_INFO_XSTATS] = {.type = NLA_NESTED}, + }; + struct nlattr *li[G_N_ELEMENTS(policy_link_info)]; + + if (nla_parse_nested_arr(li, tb[IFLA_LINKINFO], policy_link_info) < 0) + return NULL; + + if (li[IFLA_INFO_KIND]) + nl_info_kind = nla_get_string(li[IFLA_INFO_KIND]); + + nl_info_data = li[IFLA_INFO_DATA]; + } + + if (tb[IFLA_STATS64]) { + const char *stats = nla_data(tb[IFLA_STATS64]); + + obj->link.rx_packets = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_packets)]); + obj->link.rx_bytes = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_bytes)]); + obj->link.tx_packets = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_packets)]); + obj->link.tx_bytes = + unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_bytes)]); + } + + obj->link.n_ifi_flags = ifi->ifi_flags; + obj->link.connected = NM_FLAGS_HAS(obj->link.n_ifi_flags, IFF_LOWER_UP); + obj->link.arptype = ifi->ifi_type; + + obj->link.type = _linktype_get_type(platform, + cache, + nl_info_kind, + obj->link.ifindex, + obj->link.name, + obj->link.n_ifi_flags, + obj->link.arptype, + completed_from_cache, + &link_cached, + &obj->link.kind); + + if (tb[IFLA_MASTER]) + obj->link.master = nla_get_u32(tb[IFLA_MASTER]); + + if (tb[IFLA_LINK]) { + if (!tb[IFLA_LINK_NETNSID]) + obj->link.parent = nla_get_u32(tb[IFLA_LINK]); + else + obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS; + } + + if (tb[IFLA_ADDRESS]) { + _nmp_link_address_set(&obj->link.l_address, tb[IFLA_ADDRESS]); + address_complete_from_cache = FALSE; + } + + if (tb[IFLA_BROADCAST]) { + _nmp_link_address_set(&obj->link.l_broadcast, tb[IFLA_BROADCAST]); + broadcast_complete_from_cache = FALSE; + } + + if (tb[IFLA_AF_SPEC]) { + struct nlattr *af_attr; + int remaining; + + nla_for_each_nested (af_attr, tb[IFLA_AF_SPEC], remaining) { + switch (nla_type(af_attr)) { + case AF_INET6: + _parse_af_inet6(platform, + af_attr, + &obj->link.inet6_token, + &af_inet6_token_valid, + &obj->link.inet6_addr_gen_mode_inv, + &af_inet6_addr_gen_mode_valid); + break; + } + } + } + + switch (obj->link.type) { + case NM_LINK_TYPE_BRIDGE: + lnk_data = _parse_lnk_bridge(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + lnk_data = _parse_lnk_gre(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_INFINIBAND: + lnk_data = _parse_lnk_infiniband(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IP6TNL: + lnk_data = _parse_lnk_ip6tnl(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + lnk_data = _parse_lnk_ip6gre(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_IPIP: + lnk_data = _parse_lnk_ipip(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_MACSEC: + lnk_data = _parse_lnk_macsec(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_MACVLAN: + case NM_LINK_TYPE_MACVTAP: + lnk_data = _parse_lnk_macvlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_SIT: + lnk_data = _parse_lnk_sit(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_TUN: + lnk_data = _parse_lnk_tun(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VLAN: + lnk_data = _parse_lnk_vlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VRF: + lnk_data = _parse_lnk_vrf(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VXLAN: + lnk_data = _parse_lnk_vxlan(nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_WIFI: + case NM_LINK_TYPE_OLPC_MESH: + case NM_LINK_TYPE_WPAN: + need_ext_data = TRUE; + lnk_data_complete_from_cache = FALSE; + break; + case NM_LINK_TYPE_WIREGUARD: + lnk_data_complete_from_cache = TRUE; + break; + default: + lnk_data_complete_from_cache = FALSE; + break; + } + + if (completed_from_cache + && (lnk_data_complete_from_cache || need_ext_data || address_complete_from_cache + || broadcast_complete_from_cache || !af_inet6_token_valid + || !af_inet6_addr_gen_mode_valid || !tb[IFLA_STATS64])) { + _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached); + if (link_cached && link_cached->_link.netlink.is_in_netlink) { + if (lnk_data_complete_from_cache && link_cached->link.type == obj->link.type + && link_cached->_link.netlink.lnk + && (!lnk_data || nmp_object_equal(lnk_data, link_cached->_link.netlink.lnk))) { + /* We always try to look into the cache and reuse the object there. + * We do that, because we consider the lnk object as immutable and don't + * modify it after creating. Hence we can share it and reuse. + * + * Also, sometimes the info-data is missing for updates. In this case + * we want to keep the previously received lnk_data. */ + nmp_object_unref(lnk_data); + lnk_data = nmp_object_ref(link_cached->_link.netlink.lnk); + } + + if (need_ext_data && link_cached->link.type == obj->link.type + && link_cached->_link.ext_data) { + /* Prefer reuse of existing ext_data object */ + obj->_link.ext_data = g_object_ref(link_cached->_link.ext_data); + } + + if (address_complete_from_cache) + obj->link.l_address = link_cached->link.l_address; + if (broadcast_complete_from_cache) + obj->link.l_broadcast = link_cached->link.l_broadcast; + if (!af_inet6_token_valid) + obj->link.inet6_token = link_cached->link.inet6_token; + if (!af_inet6_addr_gen_mode_valid) + obj->link.inet6_addr_gen_mode_inv = link_cached->link.inet6_addr_gen_mode_inv; + if (!tb[IFLA_STATS64]) { + obj->link.rx_packets = link_cached->link.rx_packets; + obj->link.rx_bytes = link_cached->link.rx_bytes; + obj->link.tx_packets = link_cached->link.tx_packets; + obj->link.tx_bytes = link_cached->link.tx_bytes; + } + } + } + + obj->_link.netlink.lnk = lnk_data; + + if (need_ext_data && obj->_link.ext_data == NULL) { + switch (obj->link.type) { + case NM_LINK_TYPE_WIFI: + case NM_LINK_TYPE_OLPC_MESH: + obj->_link.ext_data = + (GObject *) nm_wifi_utils_new(ifi->ifi_index, + _genl_sock(NM_LINUX_PLATFORM(platform)), + TRUE); + break; + case NM_LINK_TYPE_WPAN: + obj->_link.ext_data = + (GObject *) nm_wpan_utils_new(ifi->ifi_index, + _genl_sock(NM_LINUX_PLATFORM(platform)), + TRUE); + break; + default: + g_assert_not_reached(); + } + } + + if (obj->link.type == NM_LINK_TYPE_WIREGUARD) { + const NMPObject *lnk_data_new = NULL; + struct nl_sock * genl = NM_LINUX_PLATFORM_GET_PRIVATE(platform)->genl; + + /* The WireGuard kernel module does not yet send link update + * notifications, so we don't actually update the cache. For + * now, always refetch link data here. */ + + _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached); + if (link_cached && link_cached->_link.netlink.is_in_netlink + && link_cached->link.type == NM_LINK_TYPE_WIREGUARD) + obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id; + else + obj->_link.wireguard_family_id = -1; + + if (obj->_link.wireguard_family_id < 0) + obj->_link.wireguard_family_id = genl_ctrl_resolve(genl, "wireguard"); + + if (obj->_link.wireguard_family_id >= 0) { + lnk_data_new = _wireguard_read_info(platform, + genl, + obj->_link.wireguard_family_id, + obj->link.ifindex); + } + + if (lnk_data_new && obj->_link.netlink.lnk + && nmp_object_equal(obj->_link.netlink.lnk, lnk_data_new)) + nmp_object_unref(lnk_data_new); + else { + nmp_object_unref(obj->_link.netlink.lnk); + obj->_link.netlink.lnk = lnk_data_new; + } + } + + obj->_link.netlink.is_in_netlink = TRUE; + return g_steal_pointer(&obj); +} + +/* Copied and heavily modified from libnl3's addr_msg_parser(). */ +static NMPObject * +_new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [IFA_LABEL] = {.type = NLA_STRING, .maxlen = IFNAMSIZ}, + [IFA_CACHEINFO] = {.minlen = nm_offsetofend(struct ifa_cacheinfo, tstamp)}, + [IFA_FLAGS] = {}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct ifaddrmsg *ifa; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_len; + guint32 lifetime, preferred, timestamp; + + if (!nlmsg_valid_hdr(nlh, sizeof(*ifa))) + return NULL; + + ifa = nlmsg_data(nlh); + + if (!NM_IN_SET(ifa->ifa_family, AF_INET, AF_INET6)) + return NULL; + + is_v4 = ifa->ifa_family == AF_INET; + + if (nlmsg_parse_arr(nlh, sizeof(*ifa), tb, policy) < 0) + return NULL; + + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128)) + return NULL; + + /*****************************************************************/ + + obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); + + obj->ip_address.ifindex = ifa->ifa_index; + obj->ip_address.plen = ifa->ifa_prefixlen; + + _check_addr_or_return_null(tb, IFA_ADDRESS, addr_len); + _check_addr_or_return_null(tb, IFA_LOCAL, addr_len); + if (is_v4) { + /* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they + * are effectively 0.0.0.0 (all-zero). */ + if (tb[IFA_LOCAL]) + memcpy(&obj->ip4_address.address, nla_data(tb[IFA_LOCAL]), addr_len); + if (tb[IFA_ADDRESS]) + memcpy(&obj->ip4_address.peer_address, nla_data(tb[IFA_ADDRESS]), addr_len); + + _check_addr_or_return_null(tb, IFA_BROADCAST, addr_len); + obj->ip4_address.broadcast_address = + tb[IFA_BROADCAST] ? nla_get_u32(tb[IFA_BROADCAST]) : 0u; + obj->ip4_address.use_ip4_broadcast_address = TRUE; + } else { + /* For IPv6, IFA_ADDRESS is always present. + * + * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address + * is :: (all-zero). + * + * If unexpectedly IFA_ADDRESS is missing, make the best of it -- but it _should_ + * actually be there. */ + if (tb[IFA_ADDRESS] || tb[IFA_LOCAL]) { + if (tb[IFA_LOCAL]) { + memcpy(&obj->ip6_address.address, nla_data(tb[IFA_LOCAL]), addr_len); + if (tb[IFA_ADDRESS]) + memcpy(&obj->ip6_address.peer_address, nla_data(tb[IFA_ADDRESS]), addr_len); + else + obj->ip6_address.peer_address = obj->ip6_address.address; + } else + memcpy(&obj->ip6_address.address, nla_data(tb[IFA_ADDRESS]), addr_len); + } + } + + obj->ip_address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; + + obj->ip_address.n_ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : ifa->ifa_flags; + + if (is_v4) { + if (tb[IFA_LABEL]) { + char label[IFNAMSIZ]; + + nla_strlcpy(label, tb[IFA_LABEL], IFNAMSIZ); + + /* Check for ':'; we're only interested in labels used as interface aliases */ + if (strchr(label, ':')) + g_strlcpy(obj->ip4_address.label, label, sizeof(obj->ip4_address.label)); + } + } + + lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + preferred = NM_PLATFORM_LIFETIME_PERMANENT; + timestamp = 0; + /* IPv6 only */ + if (tb[IFA_CACHEINFO]) { + const struct ifa_cacheinfo *ca; + + ca = nla_data_as(struct ifa_cacheinfo, tb[IFA_CACHEINFO]); + lifetime = ca->ifa_valid; + preferred = ca->ifa_prefered; + timestamp = ca->tstamp; + } + _addrtime_get_lifetimes(timestamp, + lifetime, + preferred, + &obj->ip_address.timestamp, + &obj->ip_address.lifetime, + &obj->ip_address.preferred); + + return g_steal_pointer(&obj); +} + +/* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */ +static NMPObject * +_new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [RTA_TABLE] = {.type = NLA_U32}, + [RTA_IIF] = {.type = NLA_U32}, + [RTA_OIF] = {.type = NLA_U32}, + [RTA_PRIORITY] = {.type = NLA_U32}, + [RTA_PREF] = {.type = NLA_U8}, + [RTA_FLOW] = {.type = NLA_U32}, + [RTA_CACHEINFO] = {.minlen = nm_offsetofend(struct rta_cacheinfo, rta_tsage)}, + [RTA_METRICS] = {.type = NLA_NESTED}, + [RTA_MULTIPATH] = {.type = NLA_NESTED}, + }; + const struct rtmsg *rtm; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_len; + struct { + gboolean is_present; + int ifindex; + NMIPAddr gateway; + } nh = { + .is_present = FALSE, + }; + guint32 mss; + guint32 window = 0; + guint32 cwnd = 0; + guint32 initcwnd = 0; + guint32 initrwnd = 0; + guint32 mtu = 0; + guint32 lock = 0; + + if (!nlmsg_valid_hdr(nlh, sizeof(*rtm))) + return NULL; + + rtm = nlmsg_data(nlh); + + /***************************************************************** + * only handle ~supported~ routes. + *****************************************************************/ + + if (!NM_IN_SET(rtm->rtm_family, AF_INET, AF_INET6)) + return NULL; + + if (!NM_IN_SET(rtm->rtm_type, RTN_UNICAST, RTN_LOCAL)) + return NULL; + + if (nlmsg_parse_arr(nlh, sizeof(struct rtmsg), tb, policy) < 0) + return NULL; + + /*****************************************************************/ + + is_v4 = rtm->rtm_family == AF_INET; + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (rtm->rtm_dst_len > (is_v4 ? 32 : 128)) + return NULL; + + /***************************************************************** + * parse nexthops. Only handle routes with one nh. + *****************************************************************/ + + if (tb[RTA_MULTIPATH]) { + size_t tlen = nla_len(tb[RTA_MULTIPATH]); + struct rtnexthop *rtnh; + + if (tlen < sizeof(*rtnh)) + goto rta_multipath_done; + + rtnh = nla_data_as(struct rtnexthop, tb[RTA_MULTIPATH]); + + if (tlen < rtnh->rtnh_len) + goto rta_multipath_done; + + while (TRUE) { + if (nh.is_present) { + /* we don't support multipath routes. */ + return NULL; + } + + nh.is_present = TRUE; + nh.ifindex = rtnh->rtnh_ifindex; + + if (rtnh->rtnh_len > sizeof(*rtnh)) { + struct nlattr *ntb[G_N_ELEMENTS(policy)]; + + if (nla_parse_arr(ntb, + (struct nlattr *) RTNH_DATA(rtnh), + rtnh->rtnh_len - sizeof(*rtnh), + policy) + < 0) + return NULL; + + if (_check_addr_or_return_null(ntb, RTA_GATEWAY, addr_len)) + memcpy(&nh.gateway, nla_data(ntb[RTA_GATEWAY]), addr_len); + } + + if (tlen < RTNH_ALIGN(rtnh->rtnh_len) + sizeof(*rtnh)) + goto rta_multipath_done; + + tlen -= RTNH_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } +rta_multipath_done:; + } + + if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW]) { + int ifindex = 0; + NMIPAddr gateway = {}; + + if (tb[RTA_OIF]) + ifindex = nla_get_u32(tb[RTA_OIF]); + if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len)) + memcpy(&gateway, nla_data(tb[RTA_GATEWAY]), addr_len); + + if (!nh.is_present) { + /* If no nexthops have been provided via RTA_MULTIPATH + * we add it as regular nexthop to maintain backwards + * compatibility */ + nh.ifindex = ifindex; + nh.gateway = gateway; + } else { + /* Kernel supports new style nexthop configuration, + * verify that it is a duplicate and ignore old-style nexthop. */ + if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0) + return NULL; + } + } else if (!nh.is_present) + return NULL; + + /*****************************************************************/ + + mss = 0; + if (tb[RTA_METRICS]) { + static const struct nla_policy rtax_policy[] = { + [RTAX_LOCK] = {.type = NLA_U32}, + [RTAX_ADVMSS] = {.type = NLA_U32}, + [RTAX_WINDOW] = {.type = NLA_U32}, + [RTAX_CWND] = {.type = NLA_U32}, + [RTAX_INITCWND] = {.type = NLA_U32}, + [RTAX_INITRWND] = {.type = NLA_U32}, + [RTAX_MTU] = {.type = NLA_U32}, + }; + struct nlattr *mtb[G_N_ELEMENTS(rtax_policy)]; + + if (nla_parse_nested_arr(mtb, tb[RTA_METRICS], rtax_policy) < 0) + return NULL; + + if (mtb[RTAX_LOCK]) + lock = nla_get_u32(mtb[RTAX_LOCK]); + if (mtb[RTAX_ADVMSS]) + mss = nla_get_u32(mtb[RTAX_ADVMSS]); + if (mtb[RTAX_WINDOW]) + window = nla_get_u32(mtb[RTAX_WINDOW]); + if (mtb[RTAX_CWND]) + cwnd = nla_get_u32(mtb[RTAX_CWND]); + if (mtb[RTAX_INITCWND]) + initcwnd = nla_get_u32(mtb[RTAX_INITCWND]); + if (mtb[RTAX_INITRWND]) + initrwnd = nla_get_u32(mtb[RTAX_INITRWND]); + if (mtb[RTAX_MTU]) + mtu = nla_get_u32(mtb[RTAX_MTU]); + } + + /*****************************************************************/ + + obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + + obj->ip_route.is_external = TRUE; + obj->ip_route.type_coerced = nm_platform_route_type_coerce(rtm->rtm_type); + obj->ip_route.table_coerced = nm_platform_route_table_coerce( + tb[RTA_TABLE] ? nla_get_u32(tb[RTA_TABLE]) : (guint32) rtm->rtm_table); + + obj->ip_route.ifindex = nh.ifindex; + + if (_check_addr_or_return_null(tb, RTA_DST, addr_len)) + memcpy(obj->ip_route.network_ptr, nla_data(tb[RTA_DST]), addr_len); + + obj->ip_route.plen = rtm->rtm_dst_len; + + if (tb[RTA_PRIORITY]) + obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]); + + if (is_v4) + obj->ip4_route.gateway = nh.gateway.addr4; + else + obj->ip6_route.gateway = nh.gateway.addr6; + + if (is_v4) + obj->ip4_route.scope_inv = nm_platform_route_scope_inv(rtm->rtm_scope); + + if (_check_addr_or_return_null(tb, RTA_PREFSRC, addr_len)) { + if (is_v4) + memcpy(&obj->ip4_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); + else + memcpy(&obj->ip6_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len); + } + + if (is_v4) + obj->ip4_route.tos = rtm->rtm_tos; + else { + if (tb[RTA_SRC]) { + _check_addr_or_return_null(tb, RTA_SRC, addr_len); + memcpy(&obj->ip6_route.src, nla_data(tb[RTA_SRC]), addr_len); + } + obj->ip6_route.src_plen = rtm->rtm_src_len; + } + + obj->ip_route.mss = mss; + obj->ip_route.window = window; + obj->ip_route.cwnd = cwnd; + obj->ip_route.initcwnd = initcwnd; + obj->ip_route.initrwnd = initrwnd; + obj->ip_route.mtu = mtu; + obj->ip_route.lock_window = NM_FLAGS_HAS(lock, 1 << RTAX_WINDOW); + obj->ip_route.lock_cwnd = NM_FLAGS_HAS(lock, 1 << RTAX_CWND); + obj->ip_route.lock_initcwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITCWND); + obj->ip_route.lock_initrwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITRWND); + obj->ip_route.lock_mtu = NM_FLAGS_HAS(lock, 1 << RTAX_MTU); + + if (!is_v4) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)) { + /* Detect support for RTA_PREF by inspecting the netlink message. + * RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF, + tb[RTA_PREF] ? 1 : -1); + } + + if (tb[RTA_PREF]) + obj->ip6_route.rt_pref = nla_get_u8(tb[RTA_PREF]); + } + + obj->ip_route.r_rtm_flags = rtm->rtm_flags; + obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot(rtm->rtm_protocol); + + return g_steal_pointer(&obj); +} + +static NMPObject * +_new_from_nl_routing_rule(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [FRA_UNSPEC] = {}, + [FRA_DST] = {/* struct in_addr, struct in6_addr */}, + [FRA_SRC] = {/* struct in_addr, struct in6_addr */}, + [FRA_IIFNAME] = + { + .type = NLA_STRING, + .maxlen = IFNAMSIZ, + }, + [FRA_GOTO] = + { + .type = NLA_U32, + }, + [FRA_UNUSED2] = {}, + [FRA_PRIORITY] = + { + .type = NLA_U32, + }, + [FRA_UNUSED3] = {}, + [FRA_UNUSED4] = {}, + [FRA_UNUSED5] = {}, + [FRA_FWMARK] = + { + .type = NLA_U32, + }, + [FRA_FLOW] = + { + .type = NLA_U32, + }, + [FRA_TUN_ID] = + { + .type = NLA_U64, + }, + [FRA_SUPPRESS_IFGROUP] = + { + .type = NLA_U32, + }, + [FRA_SUPPRESS_PREFIXLEN] = + { + .type = NLA_U32, + }, + [FRA_TABLE] = + { + .type = NLA_U32, + }, + [FRA_FWMASK] = + { + .type = NLA_U32, + }, + [FRA_OIFNAME] = + { + .type = NLA_STRING, + .maxlen = IFNAMSIZ, + }, + [FRA_PAD] = + { + .type = NLA_U32, + }, + [FRA_L3MDEV] = + { + .type = NLA_U8, + }, + [FRA_UID_RANGE] = + { + .minlen = sizeof(NMFibRuleUidRange), + .maxlen = sizeof(NMFibRuleUidRange), + }, + [FRA_PROTOCOL] = + { + .type = NLA_U8, + }, + [FRA_IP_PROTO] = + { + .type = NLA_U8, + }, + [FRA_SPORT_RANGE] = + { + .minlen = sizeof(NMFibRulePortRange), + .maxlen = sizeof(NMFibRulePortRange), + }, + [FRA_DPORT_RANGE] = + { + .minlen = sizeof(NMFibRulePortRange), + .maxlen = sizeof(NMFibRulePortRange), + }, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct fib_rule_hdr *frh; + NMPlatformRoutingRule * props; + nm_auto_nmpobj NMPObject *obj = NULL; + int addr_family; + guint8 addr_size; + + if (nlmsg_parse_arr(nlh, sizeof(*frh), tb, policy) < 0) + return NULL; + + frh = nlmsg_data(nlh); + + addr_family = frh->family; + + if (!NM_IN_SET(addr_family, AF_INET, AF_INET6)) { + /* we don't care about other address families. */ + return NULL; + } + + addr_size = nm_utils_addr_family_to_size(addr_family); + + obj = nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, NULL); + props = &obj->routing_rule; + + props->addr_family = addr_family; + props->action = frh->action; + props->flags = frh->flags; + props->tos = frh->tos; + + props->table = tb[FRA_TABLE] ? nla_get_u32(tb[FRA_TABLE]) : frh->table; + + if (tb[FRA_SUPPRESS_PREFIXLEN]) + props->suppress_prefixlen_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]); + + if (tb[FRA_SUPPRESS_IFGROUP]) + props->suppress_ifgroup_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]); + + if (tb[FRA_IIFNAME]) + nla_strlcpy(props->iifname, tb[FRA_IIFNAME], sizeof(props->iifname)); + + if (tb[FRA_OIFNAME]) + nla_strlcpy(props->oifname, tb[FRA_OIFNAME], sizeof(props->oifname)); + + if (tb[FRA_PRIORITY]) + props->priority = nla_get_u32(tb[FRA_PRIORITY]); + + if (tb[FRA_FWMARK]) + props->fwmark = nla_get_u32(tb[FRA_FWMARK]); + + if (tb[FRA_FWMASK]) + props->fwmask = nla_get_u32(tb[FRA_FWMASK]); + + if (tb[FRA_GOTO]) + props->goto_target = nla_get_u32(tb[FRA_GOTO]); + + props->src_len = frh->src_len; + if (props->src_len > addr_size * 8) + return NULL; + if (!tb[FRA_SRC]) { + if (props->src_len > 0) + return NULL; + } else if (!nm_ip_addr_set_from_untrusted(addr_family, + &props->src, + nla_data(tb[FRA_SRC]), + nla_len(tb[FRA_SRC]), + NULL)) + return NULL; + + props->dst_len = frh->dst_len; + if (props->dst_len > addr_size * 8) + return NULL; + if (!tb[FRA_DST]) { + if (props->dst_len > 0) + return NULL; + } else if (!nm_ip_addr_set_from_untrusted(addr_family, + &props->dst, + nla_data(tb[FRA_DST]), + nla_len(tb[FRA_DST]), + NULL)) + return NULL; + + if (tb[FRA_FLOW]) + props->flow = nla_get_u32(tb[FRA_FLOW]); + + if (tb[FRA_TUN_ID]) + props->tun_id = nla_get_be64(tb[FRA_TUN_ID]); + + if (tb[FRA_L3MDEV]) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) { + /* support for FRA_L3MDEV was added in 96c63fa7393d0a346acfe5a91e0c7d4c7782641b, + * kernel 4.8, 3 October 2017. + * + * We can only detect support if the attribute is present. A missing attribute + * is not conclusive. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, 1); + } + + /* actually, kernel only allows this attribute to be missing or + * "1". Still, encode it as full uint8. + * + * Note that FRA_L3MDEV and FRA_TABLE are mutally exclusive. */ + props->l3mdev = nla_get_u8(tb[FRA_L3MDEV]); + } + + if (tb[FRA_PROTOCOL]) + props->protocol = nla_get_u8(tb[FRA_PROTOCOL]); + else + nm_assert(props->protocol == RTPROT_UNSPEC); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) { + /* FRA_PROTOCOL was added in kernel 4.17, dated 3 June, 2018. + * See commit 1b71af6053af1bd2f849e9fda4f71c1e3f145dcf. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL, + tb[FRA_PROTOCOL] ? 1 : -1); + } + + if (tb[FRA_IP_PROTO]) + props->ip_proto = nla_get_u8(tb[FRA_IP_PROTO]); + + G_STATIC_ASSERT_EXPR(sizeof(NMFibRulePortRange) == 4); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, start) == 0); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, end) == 2); + + nla_memcpy_checked_size(&props->sport_range, tb[FRA_SPORT_RANGE], sizeof(props->sport_range)); + nla_memcpy_checked_size(&props->dport_range, tb[FRA_DPORT_RANGE], sizeof(props->dport_range)); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + /* support for FRA_IP_PROTO, FRA_SPORT_RANGE, and FRA_DPORT_RANGE was added together + * by bfff4862653bb96001ab57c1edd6d03f48e5f035, kernel 4.17, 4 June 2018. + * + * Unfortunately, a missing attribute does not tell us anything about support. + * We can only tell for sure when we have support, but not when we don't have. */ + if (tb[FRA_IP_PROTO] || tb[FRA_SPORT_RANGE] || tb[FRA_DPORT_RANGE]) + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, 1); + } + + G_STATIC_ASSERT_EXPR(sizeof(NMFibRuleUidRange) == 8); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, start) == 0); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, end) == 4); + + if (tb[FRA_UID_RANGE]) { + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + /* support for FRA_UID_RANGE was added in 622ec2c9d52405973c9f1ca5116eb1c393adfc7d, + * kernel 4.10, 19 February 2017. + * + * We can only detect support if the attribute is present. A missing attribute + * is not conclusive. */ + _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, 1); + } + + nla_memcpy_checked_size(&props->uid_range, tb[FRA_UID_RANGE], sizeof(props->uid_range)); + props->uid_range_has = TRUE; + } + + return g_steal_pointer(&obj); +} + +static guint32 +psched_tick_to_time(NMPlatform *platform, guint32 tick) +{ + static gboolean initialized; + static double tick_in_usec = 1; + + if (!initialized) { + gs_free char *params = NULL; + double clock_factor = 1; + guint32 clock_res; + guint32 t2us; + guint32 us2t; + + initialized = TRUE; + params = nm_platform_sysctl_get(platform, NMP_SYSCTL_PATHID_ABSOLUTE("/proc/net/psched")); + if (!params || sscanf(params, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) { + _LOGW("packet scheduler parameters not available"); + } else { + /* See tc_core_init() in iproute2 */ + if (clock_res == 1000000000) + t2us = us2t; + + clock_factor = (double) clock_res / PSCHED_TIME_UNITS_PER_SEC; + tick_in_usec = (double) t2us / us2t * clock_factor; + } + } + + return tick / tick_in_usec; +} + +static NMPObject * +_new_from_nl_qdisc(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [TCA_KIND] = {.type = NLA_STRING}, + [TCA_OPTIONS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + const struct tcmsg *tcm; + nm_auto_nmpobj NMPObject *obj = NULL; + + if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0) + return NULL; + + if (!tb[TCA_KIND]) + return NULL; + + tcm = nlmsg_data(nlh); + + obj = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL); + + obj->qdisc.kind = g_intern_string(nla_get_string(tb[TCA_KIND])); + obj->qdisc.ifindex = tcm->tcm_ifindex; + obj->qdisc.addr_family = tcm->tcm_family; + obj->qdisc.handle = tcm->tcm_handle; + obj->qdisc.parent = tcm->tcm_parent; + obj->qdisc.info = tcm->tcm_info; + + if (nm_streq0(obj->qdisc.kind, "fq_codel")) { + obj->qdisc.fq_codel.memory_limit = NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET; + obj->qdisc.fq_codel.ce_threshold = NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED; + } + + if (tb[TCA_OPTIONS]) { + struct nlattr *options_attr; + int remaining; + + if (nm_streq0(obj->qdisc.kind, "sfq")) { + struct tc_sfq_qopt_v1 opt; + + if (tb[TCA_OPTIONS]->nla_len >= nla_attr_size(sizeof(opt))) { + memcpy(&opt, nla_data(tb[TCA_OPTIONS]), sizeof(opt)); + obj->qdisc.sfq.quantum = opt.v0.quantum; + obj->qdisc.sfq.perturb_period = opt.v0.perturb_period; + obj->qdisc.sfq.limit = opt.v0.limit; + obj->qdisc.sfq.divisor = opt.v0.divisor; + obj->qdisc.sfq.flows = opt.v0.flows; + obj->qdisc.sfq.depth = opt.depth; + } + } else if (nm_streq0(obj->qdisc.kind, "tbf")) { + static const struct nla_policy tbf_policy[] = { + [TCA_TBF_PARMS] = {.minlen = sizeof(struct tc_tbf_qopt)}, + [TCA_TBF_RATE64] = {.type = NLA_U64}, + }; + struct nlattr * tbf_tb[G_N_ELEMENTS(tbf_policy)]; + struct tc_tbf_qopt opt; + + if (nla_parse_nested_arr(tbf_tb, tb[TCA_OPTIONS], tbf_policy) < 0) + return NULL; + if (!tbf_tb[TCA_TBF_PARMS]) + return NULL; + + nla_memcpy_checked_size(&opt, tbf_tb[TCA_TBF_PARMS], sizeof(opt)); + obj->qdisc.tbf.rate = opt.rate.rate; + if (tbf_tb[TCA_TBF_RATE64]) + obj->qdisc.tbf.rate = nla_get_u64(tbf_tb[TCA_TBF_RATE64]); + obj->qdisc.tbf.burst = + ((double) obj->qdisc.tbf.rate * psched_tick_to_time(platform, opt.buffer)) + / PSCHED_TIME_UNITS_PER_SEC; + obj->qdisc.tbf.limit = opt.limit; + } else { + nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) { + if (nla_len(options_attr) < sizeof(uint32_t)) + continue; + + if (nm_streq0(obj->qdisc.kind, "fq_codel")) { + switch (nla_type(options_attr)) { + case TCA_FQ_CODEL_LIMIT: + obj->qdisc.fq_codel.limit = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_FLOWS: + obj->qdisc.fq_codel.flows = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_TARGET: + obj->qdisc.fq_codel.target = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_INTERVAL: + obj->qdisc.fq_codel.interval = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_QUANTUM: + obj->qdisc.fq_codel.quantum = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_CE_THRESHOLD: + obj->qdisc.fq_codel.ce_threshold = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_MEMORY_LIMIT: + obj->qdisc.fq_codel.memory_limit = nla_get_u32(options_attr); + break; + case TCA_FQ_CODEL_ECN: + obj->qdisc.fq_codel.ecn = !!nla_get_u32(options_attr); + break; + } + } + } + } + } + + return g_steal_pointer(&obj); +} + +static NMPObject * +_new_from_nl_tfilter(struct nlmsghdr *nlh, gboolean id_only) +{ + static const struct nla_policy policy[] = { + [TCA_KIND] = {.type = NLA_STRING}, + }; + struct nlattr * tb[G_N_ELEMENTS(policy)]; + NMPObject * obj = NULL; + const struct tcmsg *tcm; + + if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0) + return NULL; + + if (!tb[TCA_KIND]) + return NULL; + + tcm = nlmsg_data(nlh); + + obj = nmp_object_new(NMP_OBJECT_TYPE_TFILTER, NULL); + + obj->tfilter.kind = g_intern_string(nla_get_string(tb[TCA_KIND])); + obj->tfilter.ifindex = tcm->tcm_ifindex; + obj->tfilter.addr_family = tcm->tcm_family; + obj->tfilter.handle = tcm->tcm_handle; + obj->tfilter.parent = tcm->tcm_parent; + obj->tfilter.info = tcm->tcm_info; + + return obj; +} + +/** + * nmp_object_new_from_nl: + * @platform: (allow-none): for creating certain objects, the constructor wants to check + * sysfs. For this the platform instance is needed. If missing, the object might not + * be correctly detected. + * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information. + * If a cache is given, the object is completed with information from the cache. + * @nlh: the netlink message header + * @id_only: whether only to create an empty object with only the ID fields set. + * + * Returns: %NULL or a newly created NMPObject instance. + **/ +static NMPObject * +nmp_object_new_from_nl(NMPlatform * platform, + const NMPCache *cache, + struct nl_msg * msg, + gboolean id_only) +{ + struct nlmsghdr *msghdr; + + if (nlmsg_get_proto(msg) != NETLINK_ROUTE) + return NULL; + + msghdr = nlmsg_hdr(msg); + + switch (msghdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + case RTM_GETLINK: + case RTM_SETLINK: + return _new_from_nl_link(platform, cache, msghdr, id_only); + case RTM_NEWADDR: + case RTM_DELADDR: + case RTM_GETADDR: + return _new_from_nl_addr(msghdr, id_only); + case RTM_NEWROUTE: + case RTM_DELROUTE: + case RTM_GETROUTE: + return _new_from_nl_route(msghdr, id_only); + case RTM_NEWRULE: + case RTM_DELRULE: + case RTM_GETRULE: + return _new_from_nl_routing_rule(msghdr, id_only); + case RTM_NEWQDISC: + case RTM_DELQDISC: + case RTM_GETQDISC: + return _new_from_nl_qdisc(platform, msghdr, id_only); + case RTM_NEWTFILTER: + case RTM_DELTFILTER: + case RTM_GETTFILTER: + return _new_from_nl_tfilter(msghdr, id_only); + default: + return NULL; + } +} + +/*****************************************************************************/ + +static gboolean +_nl_msg_new_link_set_afspec(struct nl_msg *msg, int addr_gen_mode, NMUtilsIPv6IfaceId *iid) +{ + struct nlattr *af_spec; + struct nlattr *af_attr; + + nm_assert(msg); + + if (!(af_spec = nla_nest_start(msg, IFLA_AF_SPEC))) + goto nla_put_failure; + + if (addr_gen_mode >= 0 || iid) { + if (!(af_attr = nla_nest_start(msg, AF_INET6))) + goto nla_put_failure; + + if (addr_gen_mode >= 0) + NLA_PUT_U8(msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode); + + if (iid) { + struct in6_addr i6_token = {.s6_addr = { + 0, + }}; + + nm_utils_ipv6_addr_set_interface_identifier(&i6_token, *iid); + NLA_PUT(msg, IFLA_INET6_TOKEN, sizeof(struct in6_addr), &i6_token); + } + + nla_nest_end(msg, af_attr); + } + + nla_nest_end(msg, af_spec); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +_nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpointer extra_data) +{ + struct nlattr *info; + struct nlattr *data = NULL; + const char * kind; + + nm_assert(msg); + + kind = nm_link_type_to_rtnl_type_string(link_type); + if (!kind) + goto nla_put_failure; + + if (!(info = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, IFLA_INFO_KIND, kind); + + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + { + const NMPlatformLnkBridge *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_BR_FORWARD_DELAY, props->forward_delay); + NLA_PUT_U32(msg, IFLA_BR_HELLO_TIME, props->hello_time); + NLA_PUT_U32(msg, IFLA_BR_MAX_AGE, props->max_age); + NLA_PUT_U32(msg, IFLA_BR_AGEING_TIME, props->ageing_time); + NLA_PUT_U32(msg, IFLA_BR_STP_STATE, !!props->stp_state); + NLA_PUT_U16(msg, IFLA_BR_PRIORITY, props->priority); + NLA_PUT_U16(msg, IFLA_BR_VLAN_PROTOCOL, htons(props->vlan_protocol)); + if (props->vlan_stats_enabled) + NLA_PUT_U8(msg, IFLA_BR_VLAN_STATS_ENABLED, !!props->vlan_stats_enabled); + NLA_PUT_U16(msg, IFLA_BR_GROUP_FWD_MASK, props->group_fwd_mask); + NLA_PUT(msg, IFLA_BR_GROUP_ADDR, sizeof(props->group_addr), &props->group_addr); + NLA_PUT_U8(msg, IFLA_BR_MCAST_SNOOPING, !!props->mcast_snooping); + NLA_PUT_U8(msg, IFLA_BR_MCAST_ROUTER, props->mcast_router); + NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERY_USE_IFADDR, !!props->mcast_query_use_ifaddr); + NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERIER, !!props->mcast_querier); + NLA_PUT_U32(msg, IFLA_BR_MCAST_HASH_MAX, props->mcast_hash_max); + NLA_PUT_U32(msg, IFLA_BR_MCAST_LAST_MEMBER_CNT, props->mcast_last_member_count); + NLA_PUT_U32(msg, IFLA_BR_MCAST_STARTUP_QUERY_CNT, props->mcast_startup_query_count); + NLA_PUT_U64(msg, IFLA_BR_MCAST_LAST_MEMBER_INTVL, props->mcast_last_member_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_MEMBERSHIP_INTVL, props->mcast_membership_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERIER_INTVL, props->mcast_querier_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_INTVL, props->mcast_query_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, props->mcast_query_response_interval); + NLA_PUT_U64(msg, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, props->mcast_startup_query_interval); + break; + } + case NM_LINK_TYPE_VLAN: + { + const NMPlatformLnkVlan *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U16(msg, IFLA_VLAN_ID, props->id); + + { + struct ifla_vlan_flags flags = { + .flags = props->flags & _NM_VLAN_FLAGS_ALL, + .mask = _NM_VLAN_FLAGS_ALL, + }; + + NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags); + } + break; + } + case NM_LINK_TYPE_VRF: + { + const NMPlatformLnkVrf *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_VRF_TABLE, props->table); + break; + } + case NM_LINK_TYPE_VXLAN: + { + const NMPlatformLnkVxlan *props = extra_data; + + nm_assert(extra_data); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_VXLAN_ID, props->id); + + if (props->group) + NLA_PUT(msg, IFLA_VXLAN_GROUP, sizeof(props->group), &props->group); + else if (!IN6_IS_ADDR_UNSPECIFIED(&props->group6)) + NLA_PUT(msg, IFLA_VXLAN_GROUP6, sizeof(props->group6), &props->group6); + + if (props->local) + NLA_PUT(msg, IFLA_VXLAN_LOCAL, sizeof(props->local), &props->local); + else if (!IN6_IS_ADDR_UNSPECIFIED(&props->local6)) + NLA_PUT(msg, IFLA_VXLAN_LOCAL6, sizeof(props->local6), &props->local6); + + if (props->parent_ifindex >= 0) + NLA_PUT_U32(msg, IFLA_VXLAN_LINK, props->parent_ifindex); + + if (props->src_port_min || props->src_port_max) { + struct nm_ifla_vxlan_port_range port_range = { + .low = htons(props->src_port_min), + .high = htons(props->src_port_max), + }; + + NLA_PUT(msg, IFLA_VXLAN_PORT_RANGE, sizeof(port_range), &port_range); + } + + NLA_PUT_U16(msg, IFLA_VXLAN_PORT, htons(props->dst_port)); + NLA_PUT_U8(msg, IFLA_VXLAN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_VXLAN_TTL, props->ttl); + NLA_PUT_U32(msg, IFLA_VXLAN_AGEING, props->ageing); + NLA_PUT_U32(msg, IFLA_VXLAN_LIMIT, props->limit); + NLA_PUT_U8(msg, IFLA_VXLAN_LEARNING, !!props->learning); + NLA_PUT_U8(msg, IFLA_VXLAN_PROXY, !!props->proxy); + NLA_PUT_U8(msg, IFLA_VXLAN_RSC, !!props->rsc); + NLA_PUT_U8(msg, IFLA_VXLAN_L2MISS, !!props->l2miss); + NLA_PUT_U8(msg, IFLA_VXLAN_L3MISS, !!props->l3miss); + break; + } + case NM_LINK_TYPE_VETH: + { + const char * veth_peer = extra_data; + const struct ifinfomsg ifi = {}; + struct nlattr * info_peer; + + nm_assert(veth_peer); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + if (!(info_peer = nla_nest_start(msg, 1 /*VETH_INFO_PEER*/))) + goto nla_put_failure; + if (nlmsg_append_struct(msg, &ifi) < 0) + goto nla_put_failure; + NLA_PUT_STRING(msg, IFLA_IFNAME, veth_peer); + nla_nest_end(msg, info_peer); + break; + } + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + { + const NMPlatformLnkGre *props = extra_data; + + nm_assert(props); + nm_assert(props->is_tap == (link_type == NM_LINK_TYPE_GRETAP)); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_GRE_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_GRE_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_GRE_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_GRE_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_GRE_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery); + NLA_PUT_U32(msg, IFLA_GRE_IKEY, htonl(props->input_key)); + NLA_PUT_U32(msg, IFLA_GRE_OKEY, htonl(props->output_key)); + NLA_PUT_U16(msg, IFLA_GRE_IFLAGS, htons(props->input_flags)); + NLA_PUT_U16(msg, IFLA_GRE_OFLAGS, htons(props->output_flags)); + break; + } + case NM_LINK_TYPE_SIT: + { + const NMPlatformLnkSit *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); + break; + } + case NM_LINK_TYPE_IP6TNL: + { + const NMPlatformLnkIp6Tnl *props = extra_data; + guint32 flowinfo; + + nm_assert(props); + nm_assert(!props->is_gre); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_IPTUN_LOCAL, sizeof(props->local), &props->local); + if (!IN6_IS_ADDR_UNSPECIFIED(&props->remote)) + NLA_PUT(msg, IFLA_IPTUN_REMOTE, sizeof(props->remote), &props->remote); + + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit); + + flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK; + flowinfo |= (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT) & IP6_FLOWINFO_TCLASS_MASK; + NLA_PUT_U32(msg, IFLA_IPTUN_FLOWINFO, htonl(flowinfo)); + NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, props->proto); + NLA_PUT_U32(msg, IFLA_IPTUN_FLAGS, props->flags); + break; + } + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + { + const NMPlatformLnkIp6Tnl *props = extra_data; + guint32 flowinfo; + + nm_assert(props); + nm_assert(props->is_gre); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_GRE_LINK, props->parent_ifindex); + + NLA_PUT_U32(msg, IFLA_GRE_IKEY, htonl(props->input_key)); + NLA_PUT_U32(msg, IFLA_GRE_OKEY, htonl(props->output_key)); + NLA_PUT_U16(msg, IFLA_GRE_IFLAGS, htons(props->input_flags)); + NLA_PUT_U16(msg, IFLA_GRE_OFLAGS, htons(props->output_flags)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_GRE_LOCAL, sizeof(props->local), &props->local); + if (!IN6_IS_ADDR_UNSPECIFIED(&props->local)) + NLA_PUT(msg, IFLA_GRE_REMOTE, sizeof(props->remote), &props->remote); + + NLA_PUT_U8(msg, IFLA_GRE_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit); + + flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK; + flowinfo |= (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT) & IP6_FLOWINFO_TCLASS_MASK; + NLA_PUT_U32(msg, IFLA_GRE_FLOWINFO, htonl(flowinfo)); + NLA_PUT_U32(msg, IFLA_GRE_FLAGS, props->flags); + break; + } + case NM_LINK_TYPE_IPIP: + { + const NMPlatformLnkIpIp *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->parent_ifindex) + NLA_PUT_U32(msg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8(msg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8(msg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); + break; + } + case NM_LINK_TYPE_MACSEC: + { + const NMPlatformLnkMacsec *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (props->icv_length) + NLA_PUT_U8(msg, IFLA_MACSEC_ICV_LEN, 16); + if (props->cipher_suite) + NLA_PUT_U64(msg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite); + if (props->replay_protect) + NLA_PUT_U32(msg, IFLA_MACSEC_WINDOW, props->window); + + NLA_PUT_U64(msg, IFLA_MACSEC_SCI, htobe64(props->sci)); + NLA_PUT_U8(msg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa); + NLA_PUT_U8(msg, IFLA_MACSEC_ENCRYPT, props->encrypt); + NLA_PUT_U8(msg, IFLA_MACSEC_PROTECT, props->protect); + NLA_PUT_U8(msg, IFLA_MACSEC_INC_SCI, props->include_sci); + NLA_PUT_U8(msg, IFLA_MACSEC_ES, props->es); + NLA_PUT_U8(msg, IFLA_MACSEC_SCB, props->scb); + NLA_PUT_U8(msg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect); + NLA_PUT_U8(msg, IFLA_MACSEC_VALIDATION, props->validation); + break; + }; + case NM_LINK_TYPE_MACVTAP: + case NM_LINK_TYPE_MACVLAN: + { + const NMPlatformLnkMacvlan *props = extra_data; + + nm_assert(props); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + NLA_PUT_U32(msg, IFLA_MACVLAN_MODE, props->mode); + NLA_PUT_U16(msg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0); + break; + } + default: + nm_assert(!extra_data); + break; + } + + if (data) + nla_nest_end(msg, data); + + nla_nest_end(msg, info); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +_nl_msg_new_link_set_linkinfo_vlan(struct nl_msg * msg, + int vlan_id, + guint32 flags_mask, + guint32 flags_set, + const NMVlanQosMapping *ingress_qos, + int ingress_qos_len, + const NMVlanQosMapping *egress_qos, + int egress_qos_len) +{ + struct nlattr *info; + struct nlattr *data; + guint i; + gboolean has_any_vlan_properties = FALSE; + + G_STATIC_ASSERT(_NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR); + G_STATIC_ASSERT(_NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP); + G_STATIC_ASSERT(_NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING); + G_STATIC_ASSERT(_NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP); + +#define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0) + + nm_assert(msg); + + /* We must not create an empty IFLA_LINKINFO section. Otherwise, kernel + * rejects the request as invalid. */ + if (flags_mask != 0 || vlan_id >= 0) + has_any_vlan_properties = TRUE; + if (!has_any_vlan_properties && ingress_qos && ingress_qos_len > 0) { + for (i = 0; i < ingress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) { + has_any_vlan_properties = TRUE; + break; + } + } + } + if (!has_any_vlan_properties && egress_qos && egress_qos_len > 0) { + for (i = 0; i < egress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) { + has_any_vlan_properties = TRUE; + break; + } + } + } + if (!has_any_vlan_properties) + return TRUE; + + if (!(info = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, IFLA_INFO_KIND, "vlan"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (vlan_id >= 0) + NLA_PUT_U16(msg, IFLA_VLAN_ID, vlan_id); + + if (flags_mask != 0) { + struct ifla_vlan_flags flags = { + .flags = flags_mask & flags_set, + .mask = flags_mask, + }; + + NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags); + } + + if (ingress_qos && ingress_qos_len > 0) { + struct nlattr *qos = NULL; + + for (i = 0; i < ingress_qos_len; i++) { + /* Silently ignore invalid mappings. Kernel would truncate + * them and modify the wrong mapping. */ + if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) { + if (!qos) { + if (!(qos = nla_nest_start(msg, IFLA_VLAN_INGRESS_QOS))) + goto nla_put_failure; + } + NLA_PUT(msg, i, sizeof(ingress_qos[i]), &ingress_qos[i]); + } + } + + if (qos) + nla_nest_end(msg, qos); + } + + if (egress_qos && egress_qos_len > 0) { + struct nlattr *qos = NULL; + + for (i = 0; i < egress_qos_len; i++) { + if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) { + if (!qos) { + if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS))) + goto nla_put_failure; + } + NLA_PUT(msg, i, sizeof(egress_qos[i]), &egress_qos[i]); + } + } + + if (qos) + nla_nest_end(msg, qos); + } + + nla_nest_end(msg, data); + nla_nest_end(msg, info); + + return TRUE; +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static struct nl_msg * +_nl_msg_new_link_full(int nlmsg_type, + int nlmsg_flags, + int ifindex, + const char *ifname, + guint8 family, + unsigned flags_mask, + unsigned flags_set) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const struct ifinfomsg ifi = { + .ifi_family = family, + .ifi_change = flags_mask, + .ifi_flags = flags_set, + .ifi_index = ifindex, + }; + + nm_assert(NM_IN_SET(nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK, RTM_SETLINK)); + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + if (nlmsg_append_struct(msg, &ifi) < 0) + goto nla_put_failure; + + if (ifname) + NLA_PUT_STRING(msg, IFLA_IFNAME, ifname); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_link(int nlmsg_type, int nlmsg_flags, int ifindex, const char *ifname) +{ + return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0); +} + +/* Copied and modified from libnl3's build_addr_msg(). */ +static struct nl_msg * +_nl_msg_new_address(int nlmsg_type, + int nlmsg_flags, + int family, + int ifindex, + gconstpointer address, + guint8 plen, + gconstpointer peer_address, + guint32 flags, + int scope, + guint32 lifetime, + guint32 preferred, + in_addr_t ip4_broadcast_address, + const char * label) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct ifaddrmsg am = { + .ifa_family = family, + .ifa_index = ifindex, + .ifa_prefixlen = plen, + .ifa_flags = flags, + }; + gsize addr_len; + + nm_assert(NM_IN_SET(family, AF_INET, AF_INET6)); + nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWADDR, RTM_DELADDR)); + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + if (scope == -1) { + /* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */ + if (family == AF_INET && address && *((char *) address) == 127) + scope = RT_SCOPE_HOST; + else + scope = RT_SCOPE_UNIVERSE; + } + am.ifa_scope = scope, + + addr_len = family == AF_INET ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + if (nlmsg_append_struct(msg, &am) < 0) + goto nla_put_failure; + + if (address) + NLA_PUT(msg, IFA_LOCAL, addr_len, address); + + if (peer_address) + NLA_PUT(msg, IFA_ADDRESS, addr_len, peer_address); + else if (address) + NLA_PUT(msg, IFA_ADDRESS, addr_len, address); + + if (label && label[0]) + NLA_PUT_STRING(msg, IFA_LABEL, label); + + if (ip4_broadcast_address != 0) + NLA_PUT(msg, IFA_BROADCAST, sizeof(in_addr_t), &ip4_broadcast_address); + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { + struct ifa_cacheinfo ca = { + .ifa_valid = lifetime, + .ifa_prefered = preferred, + }; + + NLA_PUT(msg, IFA_CACHEINFO, sizeof(ca), &ca); + } + + if (flags & ~((guint32) 0xFF)) { + /* only set the IFA_FLAGS attribute, if they actually contain additional + * flags that are not already set to am.ifa_flags. + * + * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL + * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which + * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59. */ + NLA_PUT_U32(msg, IFA_FLAGS, flags); + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static guint32 +ip_route_get_lock_flag(const NMPlatformIPRoute *route) +{ + return (((guint32) route->lock_window) << RTAX_WINDOW) + | (((guint32) route->lock_cwnd) << RTAX_CWND) + | (((guint32) route->lock_initcwnd) << RTAX_INITCWND) + | (((guint32) route->lock_initrwnd) << RTAX_INITRWND) + | (((guint32) route->lock_mtu) << RTAX_MTU); +} + +/* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */ +static struct nl_msg * +_nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const NMPClass * klass = NMP_OBJECT_GET_CLASS(obj); + gboolean is_v4 = klass->addr_family == AF_INET; + const guint32 lock = ip_route_get_lock_flag(NMP_OBJECT_CAST_IP_ROUTE(obj)); + const guint32 table = + nm_platform_route_table_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->table_coerced, TRUE); + const struct rtmsg rtmsg = { + .rtm_family = klass->addr_family, + .rtm_tos = is_v4 ? obj->ip4_route.tos : 0, + .rtm_table = table <= 0xFF ? table : RT_TABLE_UNSPEC, + .rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot(obj->ip_route.rt_source), + .rtm_scope = + is_v4 ? nm_platform_route_scope_inv(obj->ip4_route.scope_inv) : RT_SCOPE_NOWHERE, + .rtm_type = nm_platform_route_type_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced), + .rtm_flags = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)), + .rtm_dst_len = obj->ip_route.plen, + .rtm_src_len = is_v4 ? 0 : NMP_OBJECT_CAST_IP6_ROUTE(obj)->src_plen, + }; + + gsize addr_len; + + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE)); + + msg = nlmsg_alloc_simple(nlmsg_type, (int) nlmsgflags); + + if (nlmsg_append_struct(msg, &rtmsg) < 0) + goto nla_put_failure; + + addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr); + + NLA_PUT(msg, + RTA_DST, + addr_len, + is_v4 ? (gconstpointer) &obj->ip4_route.network + : (gconstpointer) &obj->ip6_route.network); + + if (!is_v4) { + if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->src)) + NLA_PUT(msg, RTA_SRC, addr_len, &obj->ip6_route.src); + } + + NLA_PUT_U32(msg, + RTA_PRIORITY, + is_v4 ? nm_platform_ip4_route_get_effective_metric(&obj->ip4_route) + : nm_platform_ip6_route_get_effective_metric(&obj->ip6_route)); + + if (table > 0xFF) + NLA_PUT_U32(msg, RTA_TABLE, table); + + if (is_v4) { + if (NMP_OBJECT_CAST_IP4_ROUTE(obj)->pref_src) + NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src); + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->pref_src)) + NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src); + } + + if (obj->ip_route.mss || obj->ip_route.window || obj->ip_route.cwnd || obj->ip_route.initcwnd + || obj->ip_route.initrwnd || obj->ip_route.mtu || lock) { + struct nlattr *metrics; + + metrics = nla_nest_start(msg, RTA_METRICS); + if (!metrics) + goto nla_put_failure; + + if (obj->ip_route.mss) + NLA_PUT_U32(msg, RTAX_ADVMSS, obj->ip_route.mss); + if (obj->ip_route.window) + NLA_PUT_U32(msg, RTAX_WINDOW, obj->ip_route.window); + if (obj->ip_route.cwnd) + NLA_PUT_U32(msg, RTAX_CWND, obj->ip_route.cwnd); + if (obj->ip_route.initcwnd) + NLA_PUT_U32(msg, RTAX_INITCWND, obj->ip_route.initcwnd); + if (obj->ip_route.initrwnd) + NLA_PUT_U32(msg, RTAX_INITRWND, obj->ip_route.initrwnd); + if (obj->ip_route.mtu) + NLA_PUT_U32(msg, RTAX_MTU, obj->ip_route.mtu); + if (lock) + NLA_PUT_U32(msg, RTAX_LOCK, lock); + + nla_nest_end(msg, metrics); + } + + /* We currently don't have need for multi-hop routes... */ + if (is_v4) { + NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) + NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway); + } + NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex); + + if (!is_v4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM) + NLA_PUT_U8(msg, RTA_PREF, obj->ip6_route.rt_pref); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_routing_rule(int nlmsg_type, int nlmsg_flags, const NMPlatformRoutingRule *routing_rule) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const guint8 addr_size = nm_utils_addr_family_to_size(routing_rule->addr_family); + guint32 table; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags); + + table = routing_rule->table; + + if (NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6) + && routing_rule->action == FR_ACT_TO_TBL && routing_rule->l3mdev == 0 + && table == RT_TABLE_UNSPEC) { + /* for IPv6, this setting is invalid and rejected by kernel. That's fine. + * + * for IPv4, kernel will automatically assign an unused table. That's not + * fine, because we don't know what we will get. + * + * The caller must not allow that to happen. */ + nm_assert_not_reached(); + } + + { + const struct fib_rule_hdr frh = { + .family = routing_rule->addr_family, + .src_len = routing_rule->src_len, + .dst_len = routing_rule->dst_len, + .tos = routing_rule->tos, + .table = table < 0x100u ? (guint8) table : (guint8) RT_TABLE_UNSPEC, + .action = routing_rule->action, + + /* we only allow setting the "not" flag. */ + .flags = routing_rule->flags & ((guint32) FIB_RULE_INVERT), + }; + + if (nlmsg_append_struct(msg, &frh) < 0) + goto nla_put_failure; + } + + if (table > G_MAXINT8) + NLA_PUT_U32(msg, FRA_TABLE, table); + + if (routing_rule->suppress_prefixlen_inverse != 0) + NLA_PUT_U32(msg, FRA_SUPPRESS_PREFIXLEN, ~routing_rule->suppress_prefixlen_inverse); + + if (routing_rule->suppress_ifgroup_inverse != 0) + NLA_PUT_U32(msg, FRA_SUPPRESS_IFGROUP, ~routing_rule->suppress_ifgroup_inverse); + + if (routing_rule->iifname[0] != '\0') + NLA_PUT_STRING(msg, FRA_IIFNAME, routing_rule->iifname); + + if (routing_rule->oifname[0] != '\0') + NLA_PUT_STRING(msg, FRA_OIFNAME, routing_rule->oifname); + + /* we always set the priority and don't support letting kernel pick one. */ + NLA_PUT_U32(msg, FRA_PRIORITY, routing_rule->priority); + + if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) { + NLA_PUT_U32(msg, FRA_FWMARK, routing_rule->fwmark); + NLA_PUT_U32(msg, FRA_FWMASK, routing_rule->fwmask); + } + + if (routing_rule->src_len > 0) + NLA_PUT(msg, FRA_SRC, addr_size, &routing_rule->src); + + if (routing_rule->dst_len > 0) + NLA_PUT(msg, FRA_DST, addr_size, &routing_rule->dst); + + if (routing_rule->flow != 0) { + /* only relevant for IPv4. */ + NLA_PUT_U32(msg, FRA_FLOW, routing_rule->flow); + } + + if (routing_rule->tun_id != 0) + NLA_PUT_U64(msg, FRA_TUN_ID, htobe64(routing_rule->tun_id)); + + if (routing_rule->l3mdev) + NLA_PUT_U8(msg, FRA_L3MDEV, routing_rule->l3mdev); + + if (routing_rule->protocol != RTPROT_UNSPEC) + NLA_PUT_U8(msg, FRA_PROTOCOL, routing_rule->protocol); + + if (routing_rule->ip_proto != 0) + NLA_PUT_U8(msg, FRA_IP_PROTO, routing_rule->ip_proto); + + if (routing_rule->sport_range.start || routing_rule->sport_range.end) + NLA_PUT(msg, + FRA_SPORT_RANGE, + sizeof(routing_rule->sport_range), + &routing_rule->sport_range); + + if (routing_rule->dport_range.start || routing_rule->dport_range.end) + NLA_PUT(msg, + FRA_DPORT_RANGE, + sizeof(routing_rule->dport_range), + &routing_rule->dport_range); + + if (routing_rule->uid_range_has) + NLA_PUT(msg, FRA_UID_RANGE, sizeof(routing_rule->uid_range), &routing_rule->uid_range); + + switch (routing_rule->action) { + case FR_ACT_GOTO: + NLA_PUT_U32(msg, FRA_GOTO, routing_rule->goto_target); + break; + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_qdisc(int nlmsg_type, int nlmsg_flags, const NMPlatformQdisc *qdisc) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * tc_options; + const struct tcmsg tcm = { + .tcm_family = qdisc->addr_family, + .tcm_ifindex = qdisc->ifindex, + .tcm_handle = qdisc->handle, + .tcm_parent = qdisc->parent, + .tcm_info = qdisc->info, + }; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO); + + if (nlmsg_append_struct(msg, &tcm) < 0) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_KIND, qdisc->kind); + + if (nm_streq(qdisc->kind, "sfq")) { + struct tc_sfq_qopt_v1 opt = {}; + + opt.v0.quantum = qdisc->sfq.quantum; + opt.v0.limit = qdisc->sfq.limit; + opt.v0.perturb_period = qdisc->sfq.perturb_period; + opt.v0.flows = qdisc->sfq.flows; + opt.v0.divisor = qdisc->sfq.divisor; + opt.depth = qdisc->sfq.depth; + + NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt); + } else if (nm_streq(qdisc->kind, "tbf")) { + struct tc_tbf_qopt opt = {}; + + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + opt.rate.rate = (qdisc->tbf.rate >= (1ULL << 32)) ? ~0U : (guint32) qdisc->tbf.rate; + if (qdisc->tbf.limit) + opt.limit = qdisc->tbf.limit; + else if (qdisc->tbf.latency) { + opt.limit = qdisc->tbf.rate * (double) qdisc->tbf.latency / PSCHED_TIME_UNITS_PER_SEC + + qdisc->tbf.burst; + } + + NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opt), &opt); + if (qdisc->tbf.rate >= (1ULL << 32)) + NLA_PUT_U64(msg, TCA_TBF_RATE64, qdisc->tbf.rate); + NLA_PUT_U32(msg, TCA_TBF_BURST, qdisc->tbf.burst); + + nla_nest_end(msg, tc_options); + } else if (nm_streq(qdisc->kind, "prio")) { + struct tc_prio_qopt opt = {3, {1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}; + + NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt); + } else { + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + if (nm_streq(qdisc->kind, "fq_codel")) { + if (qdisc->fq_codel.limit) + NLA_PUT_U32(msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit); + if (qdisc->fq_codel.flows) + NLA_PUT_U32(msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows); + if (qdisc->fq_codel.target) + NLA_PUT_U32(msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target); + if (qdisc->fq_codel.interval) + NLA_PUT_U32(msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval); + if (qdisc->fq_codel.quantum) + NLA_PUT_U32(msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum); + if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) + NLA_PUT_U32(msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold); + if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET) + NLA_PUT_U32(msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit); + if (qdisc->fq_codel.ecn) + NLA_PUT_U32(msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn); + } + + nla_nest_end(msg, tc_options); + } + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +_nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tfilter) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * tc_options; + struct nlattr * act_tab; + const struct tcmsg tcm = { + .tcm_family = tfilter->addr_family, + .tcm_ifindex = tfilter->ifindex, + .tcm_handle = tfilter->handle, + .tcm_parent = tfilter->parent, + .tcm_info = tfilter->info, + }; + + msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO); + + if (nlmsg_append_struct(msg, &tcm) < 0) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_KIND, tfilter->kind); + + if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS))) + goto nla_put_failure; + + if (!(act_tab = nla_nest_start(msg, TCA_OPTIONS))) // 3 TCA_ACT_KIND TCA_ACT_KIND + goto nla_put_failure; + + if (tfilter->action.kind) { + const NMPlatformAction *action = &tfilter->action; + struct nlattr * prio; + struct nlattr * act_options; + + if (!(prio = nla_nest_start(msg, 1 /* priority */))) + goto nla_put_failure; + + NLA_PUT_STRING(msg, TCA_ACT_KIND, action->kind); + + if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + const NMPlatformActionSimple *simple = &action->simple; + struct tc_defact sel = { + 0, + }; + + if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS))) + goto nla_put_failure; + + NLA_PUT(msg, TCA_DEF_PARMS, sizeof(sel), &sel); + NLA_PUT(msg, TCA_DEF_DATA, sizeof(simple->sdata), simple->sdata); + + nla_nest_end(msg, act_options); + + } else if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + const NMPlatformActionMirred *mirred = &action->mirred; + struct tc_mirred sel = { + 0, + }; + + if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS))) + goto nla_put_failure; + + if (mirred->egress && mirred->redirect) + sel.eaction = TCA_EGRESS_REDIR; + else if (mirred->egress && mirred->mirror) + sel.eaction = TCA_EGRESS_MIRROR; + else if (mirred->ingress && mirred->redirect) + sel.eaction = TCA_INGRESS_REDIR; + else if (mirred->ingress && mirred->mirror) + sel.eaction = TCA_INGRESS_MIRROR; + sel.ifindex = mirred->ifindex; + + NLA_PUT(msg, TCA_MIRRED_PARMS, sizeof(sel), &sel); + + nla_nest_end(msg, act_options); + } + + nla_nest_end(msg, prio); + } + + nla_nest_end(msg, tc_options); + + nla_nest_end(msg, act_tab); + + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +/*****************************************************************************/ + +static struct nl_sock * +_genl_sock(NMLinuxPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + return priv->genl; +} + +#define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \ + G_STMT_START \ + { \ + const char *const _pathid = (pathid); \ + const int _dirfd = (dirfd); \ + const char *const _path = (path); \ + \ + nm_assert(_path &&_path[0]); \ + g_assert(!strstr(_path, "/../")); \ + if (_dirfd < 0) { \ + nm_assert(!_pathid); \ + nm_assert(_path[0] == '/'); \ + nm_assert(g_str_has_prefix(_path, "/proc/sys/") || g_str_has_prefix(_path, "/sys/") \ + || g_str_has_prefix(_path, "/proc/net")); \ + } else { \ + nm_assert(_pathid &&_pathid[0] && _pathid[0] != '/'); \ + nm_assert(_path[0] != '/'); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +/* core sysctl-set functions can be called from a non-main thread. + * Hence, we require locking from nm-logging. Indicate that by + * setting NM_THREAD_SAFE_ON_MAIN_THREAD to zero. */ +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 0 + +static void +_log_dbg_sysctl_set_impl(NMPlatform *platform, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + GError * error = NULL; + gs_free char *contents = NULL; + gs_free char *value_escaped = g_strescape(value, NULL); + + if (!nm_utils_file_get_contents(dirfd, + path, + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + &error)) { + _LOGD("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", + pathid ?: path, + value_escaped, + error->message); + g_clear_error(&error); + return; + } + + g_strstrip(contents); + if (nm_streq(contents, value)) + _LOGD("sysctl: setting '%s' to '%s' (current value is identical)", + pathid ?: path, + value_escaped); + else { + gs_free char *contents_escaped = g_strescape(contents, NULL); + + _LOGD("sysctl: setting '%s' to '%s' (current value is '%s')", + pathid ?: path, + value_escaped, + contents_escaped); + } +} + +#define _log_dbg_sysctl_set(platform, pathid, dirfd, path, value) \ + G_STMT_START \ + { \ + if (_LOGD_ENABLED()) { \ + _log_dbg_sysctl_set_impl(platform, pathid, dirfd, path, value); \ + } \ + } \ + G_STMT_END + +static gboolean +sysctl_set_internal(NMPlatform *platform, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + int fd, tries; + gssize nwrote; + gssize len; + char * actual; + gs_free char *actual_free = NULL; + int errsv; + + if (dirfd < 0) { + pathid = path; + + fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC); + if (fd == -1) { + errsv = errno; + if (errsv == ENOENT) { + _LOGD("sysctl: failed to open '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } else { + _LOGE("sysctl: failed to open '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } + errno = errsv; + return FALSE; + } + } else { + fd = openat(dirfd, path, O_WRONLY | O_TRUNC | O_CLOEXEC); + if (fd == -1) { + errsv = errno; + if (errsv == ENOENT) { + _LOGD("sysctl: failed to openat '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } else { + _LOGE("sysctl: failed to openat '%s': (%d) %s", + pathid, + errsv, + nm_strerror_native(errsv)); + } + errno = errsv; + return FALSE; + } + } + + _log_dbg_sysctl_set(platform, pathid, dirfd, path, value); + + /* Most sysfs and sysctl options don't care about a trailing LF, while some + * (like infiniband) do. So always add the LF. Also, neither sysfs nor + * sysctl support partial writes so the LF must be added to the string we're + * about to write. + */ + len = strlen(value) + 1; + nm_assert(len > 0); + if (len > 512) + actual = actual_free = g_malloc(len + 1); + else + actual = g_alloca(len + 1); + memcpy(actual, value, len - 1); + actual[len - 1] = '\n'; + actual[len] = '\0'; + + /* Try to write the entire value three times if a partial write occurs */ + errsv = 0; + for (tries = 0, nwrote = 0; tries < 3 && nwrote < len - 1; tries++) { + nwrote = write(fd, actual, len); + if (nwrote == -1) { + errsv = errno; + if (errsv == EINTR) { + _LOGD("sysctl: interrupted, will try again"); + continue; + } + break; + } + } + if (nwrote == -1) { + NMLogLevel level = LOGL_ERR; + + if (errsv == EEXIST) { + level = LOGL_DEBUG; + } else if (errsv == EINVAL + && nm_utils_sysctl_ip_conf_is_path(AF_INET6, path, NULL, "mtu")) { + /* setting the MTU can fail under regular conditions. Suppress + * logging a warning. */ + level = LOGL_DEBUG; + } + + _NMLOG(level, + "sysctl: failed to set '%s' to '%s': (%d) %s", + path, + value, + errsv, + nm_strerror_native(errsv)); + } else if (nwrote < len - 1) { + _LOGE("sysctl: failed to set '%s' to '%s' after three attempts", path, value); + } + + if (nwrote < len - 1) { + if (nm_close(fd) != 0) { + if (errsv != 0) + errno = errsv; + } else if (errsv != 0) + errno = errsv; + else + errno = EIO; + return FALSE; + } + if (nm_close(fd) != 0) { + /* errno is already properly set. */ + return FALSE; + } + + /* success. errno is undefined (no need to set). */ + return TRUE; +} + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + +/*****************************************************************************/ + +static gboolean +sysctl_set(NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(value, FALSE); + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd < 0 && !nm_platform_netns_push(platform, &netns)) { + errno = ENETDOWN; + return FALSE; + } + + return sysctl_set_internal(platform, pathid, dirfd, path, value); +} + +typedef struct { + NMPlatform * platform; + char * pathid; + int dirfd; + char * path; + char ** values; + GCancellable * cancellable; + NMPlatformAsyncCallback callback; + gpointer callback_data; +} SysctlAsyncInfo; + +static void +sysctl_async_info_free(SysctlAsyncInfo *info) +{ + g_object_unref(info->platform); + g_free(info->pathid); + if (info->dirfd >= 0) + nm_close(info->dirfd); + g_free(info->path); + g_strfreev(info->values); + g_object_unref(info->cancellable); + g_slice_free(SysctlAsyncInfo, info); +} + +static void +sysctl_async_cb(GObject *object, GAsyncResult *res, gpointer user_data) +{ + NMPlatform * platform; + GTask * task = G_TASK(res); + SysctlAsyncInfo *info; + gs_free_error GError *error = NULL; + gs_free char * values_str = NULL; + + info = g_task_get_task_data(task); + + if (g_task_propagate_boolean(task, &error)) { + platform = info->platform; + _LOGD("sysctl: successfully set-async '%s' to values '%s'", + info->pathid ?: info->path, + (values_str = g_strjoinv(", ", info->values))); + } + + if (info->callback) + info->callback(error, info->callback_data); +} + +static void +sysctl_async_thread_fn(GTask * task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + SysctlAsyncInfo * info = task_data; + GError * error = NULL; + char ** value; + + if (g_task_return_error_if_cancelled(task)) + return; + + if (info->dirfd < 0 && !nm_platform_netns_push(info->platform, &netns)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failed changing namespace"); + g_task_return_error(task, error); + return; + } + + for (value = info->values; *value; value++) { + if (!sysctl_set_internal(info->platform, info->pathid, info->dirfd, info->path, *value)) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failed setting '%s' to value '%s': %s", + info->pathid ?: info->path, + *value, + nm_strerror_native(errno)); + g_task_return_error(task, error); + return; + } + if (g_task_return_error_if_cancelled(task)) + return; + } + g_task_return_boolean(task, TRUE); +} + +static void +sysctl_set_async_return_idle(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_free_error GError *cancelled_error = NULL; + gs_free_error GError * error = NULL; + NMPlatformAsyncCallback callback; + gpointer callback_data; + + nm_utils_user_data_unpack(user_data, &platform, &callback, &callback_data, &error); + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + callback(cancelled_error ?: error, callback_data); +} + +static void +sysctl_set_async(NMPlatform * platform, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + SysctlAsyncInfo *info; + GTask * task; + int dirfd_dup, errsv; + gpointer packed; + GError * error = NULL; + + g_return_if_fail(platform); + g_return_if_fail(path); + g_return_if_fail(values && values[0]); + g_return_if_fail(cancellable); + g_return_if_fail(!data || callback); + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd >= 0) { + dirfd_dup = fcntl(dirfd, F_DUPFD_CLOEXEC, 0); + if (dirfd_dup < 0) { + if (!callback) + return; + errsv = errno; + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "sysctl: failure duplicating directory fd: %s", + nm_strerror_native(errsv)); + packed = nm_utils_user_data_pack(g_object_ref(platform), callback, data, error); + nm_utils_invoke_on_idle(cancellable, sysctl_set_async_return_idle, packed); + return; + } + } else + dirfd_dup = -1; + + info = g_slice_new0(SysctlAsyncInfo); + info->platform = g_object_ref(platform); + info->pathid = g_strdup(pathid); + info->dirfd = dirfd_dup; + info->path = g_strdup(path); + info->values = g_strdupv((char **) values); + info->callback = callback; + info->callback_data = data; + info->cancellable = g_object_ref(cancellable); + + task = g_task_new(platform, cancellable, sysctl_async_cb, NULL); + g_task_set_task_data(task, info, (GDestroyNotify) sysctl_async_info_free); + g_task_set_return_on_cancel(task, FALSE); + g_task_run_in_thread(task, sysctl_async_thread_fn); + g_object_unref(task); +} + +static CList sysctl_clear_cache_lst_head = C_LIST_INIT(sysctl_clear_cache_lst_head); +static GMutex sysctl_clear_cache_lock; + +void +_nm_logging_clear_platform_logging_cache(void) +{ + NM_G_MUTEX_LOCKED(&sysctl_clear_cache_lock); + + while (TRUE) { + NMLinuxPlatformPrivate *priv; + + priv = c_list_first_entry(&sysctl_clear_cache_lst_head, + NMLinuxPlatformPrivate, + sysctl_clear_cache_lst); + if (!priv) + return; + + nm_assert(NM_IS_LINUX_PLATFORM(NM_LINUX_PLATFORM_FROM_PRIVATE(priv))); + + c_list_unlink(&priv->sysctl_clear_cache_lst); + + nm_clear_pointer(&priv->sysctl_get_prev_values, g_hash_table_destroy); + } +} + +typedef struct { + const char *path; + CList lst; + char * value; + char path_data[]; +} SysctlCacheEntry; + +static void +sysctl_cache_entry_free(SysctlCacheEntry *entry) +{ + c_list_unlink_stale(&entry->lst); + g_free(entry->value); + g_free(entry); +} + +static void +_log_dbg_sysctl_get_impl(NMPlatform *platform, const char *pathid, const char *contents) +{ + /* Note that we only have on global mutex for all NMPlatform instances. But in general + * we hardly run with concurrent threads and there are few NMPlatform instances. So + * this is acceptable. + * + * Note that there are only three functions that touch + * - sysctl_clear_cache_lst_head + * - priv->sysctl_get_prev_values + * - priv->sysctl_list + * - priv->sysctl_clear_cache_lst + * these are: + * 1) _nm_logging_clear_platform_logging_cache() + * 2) _log_dbg_sysctl_get_impl() + * 3) finalize() + * + * Note that 2) keeps the lock while also log! Logging itself may take a lock + * and it may even call back into our code again (like g_log() handlers + * and _nm_logging_clear_platform_logging_cache() which is called by logging). + * + * But in practice this is safe because logging code releases its lock before + * calling _nm_logging_clear_platform_logging_cache(). + **/ + NM_G_MUTEX_LOCKED(&sysctl_clear_cache_lock); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + SysctlCacheEntry * entry = NULL; + + if (!priv->sysctl_get_prev_values) { + c_list_link_tail(&sysctl_clear_cache_lst_head, &priv->sysctl_clear_cache_lst); + c_list_init(&priv->sysctl_list); + priv->sysctl_get_prev_values = + g_hash_table_new_full(nm_pstr_hash, + nm_pstr_equal, + (GDestroyNotify) sysctl_cache_entry_free, + NULL); + } else + entry = g_hash_table_lookup(priv->sysctl_get_prev_values, &pathid); + + if (entry) { + if (!nm_streq(entry->value, contents)) { + gs_free char *contents_escaped = g_strescape(contents, NULL); + gs_free char *prev_value_escaped = g_strescape(entry->value, NULL); + + _LOGD("sysctl: reading '%s': '%s' (changed from '%s' on last read)", + pathid, + contents_escaped, + prev_value_escaped); + g_free(entry->value); + entry->value = g_strdup(contents); + } + + nm_c_list_move_front(&priv->sysctl_list, &entry->lst); + } else { + gs_free char * contents_escaped = g_strescape(contents, NULL); + SysctlCacheEntry *old; + size_t len; + + len = strlen(pathid); + entry = g_malloc(sizeof(SysctlCacheEntry) + len + 1); + entry->value = g_strdup(contents); + entry->path = entry->path_data; + memcpy(entry->path_data, pathid, len + 1); + + /* Remove oldest entry when the cache becomes too big */ + if (g_hash_table_size(priv->sysctl_get_prev_values) > 1000u) { + old = c_list_last_entry(&priv->sysctl_list, SysctlCacheEntry, lst); + g_hash_table_remove(priv->sysctl_get_prev_values, old); + } + + _LOGD("sysctl: reading '%s': '%s'", pathid, contents_escaped); + + g_hash_table_add(priv->sysctl_get_prev_values, entry); + c_list_link_front(&priv->sysctl_list, &entry->lst); + } +} + +#define _log_dbg_sysctl_get(platform, pathid, contents) \ + G_STMT_START \ + { \ + if (_LOGD_ENABLED()) \ + _log_dbg_sysctl_get_impl(platform, pathid, contents); \ + } \ + G_STMT_END + +static char * +sysctl_get(NMPlatform *platform, const char *pathid, int dirfd, const char *path) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + GError * error = NULL; + gs_free char * contents = NULL; + + ASSERT_SYSCTL_ARGS(pathid, dirfd, path); + + if (dirfd < 0) { + if (!nm_platform_netns_push(platform, &netns)) { + errno = EBUSY; + return NULL; + } + pathid = path; + } + + if (!nm_utils_file_get_contents(dirfd, + path, + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + NULL, + NULL, + &error)) { + NMLogLevel log_level = LOGL_ERR; + int errsv = EBUSY; + + if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + errsv = ENOENT; + log_level = LOGL_DEBUG; + } else if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NODEV) + || g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) { + /* We assume FAILED means EOPNOTSUP and don't log a error message. */ + log_level = LOGL_DEBUG; + } + + _NMLOG(log_level, "error reading %s: %s", pathid, error->message); + g_clear_error(&error); + errno = errsv; + return NULL; + } + + g_strstrip(contents); + + _log_dbg_sysctl_get(platform, pathid, contents); + + /* errno is left undefined (as we don't return NULL). */ + return g_steal_pointer(&contents); +} + +/*****************************************************************************/ + +static void +process_events(NMPlatform *platform) +{ + delayed_action_handle_all(platform, TRUE); +} + +/*****************************************************************************/ + +static const RefreshAllInfo * +refresh_all_type_get_info(RefreshAllType refresh_all_type) +{ + static const RefreshAllInfo infos[] = { +#define R(_refresh_all_type, _obj_type, _addr_family) \ + [_refresh_all_type] = { \ + .obj_type = _obj_type, \ + .addr_family = _addr_family, \ + } + R(REFRESH_ALL_TYPE_LINKS, NMP_OBJECT_TYPE_LINK, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP4_ROUTES, NMP_OBJECT_TYPE_IP4_ROUTE, AF_UNSPEC), + R(REFRESH_ALL_TYPE_IP6_ROUTES, NMP_OBJECT_TYPE_IP6_ROUTE, AF_UNSPEC), + R(REFRESH_ALL_TYPE_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET), + R(REFRESH_ALL_TYPE_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6), + R(REFRESH_ALL_TYPE_QDISCS, NMP_OBJECT_TYPE_QDISC, AF_UNSPEC), + R(REFRESH_ALL_TYPE_TFILTERS, NMP_OBJECT_TYPE_TFILTER, AF_UNSPEC), +#undef R + }; + + nm_assert(_NM_INT_NOT_NEGATIVE(refresh_all_type)); + nm_assert(refresh_all_type < G_N_ELEMENTS(infos)); + nm_assert(nmp_class_from_type(infos[refresh_all_type].obj_type)); + + return &infos[refresh_all_type]; +} + +static NM_UTILS_LOOKUP_DEFINE( + delayed_action_type_to_refresh_all_type, + DelayedActionType, + RefreshAllType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, REFRESH_ALL_TYPE_LINKS), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, + REFRESH_ALL_TYPE_IP4_ADDRESSES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, + REFRESH_ALL_TYPE_IP6_ADDRESSES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, REFRESH_ALL_TYPE_IP4_ROUTES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, REFRESH_ALL_TYPE_IP6_ROUTES), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, + REFRESH_ALL_TYPE_ROUTING_RULES_IP4), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + REFRESH_ALL_TYPE_ROUTING_RULES_IP6), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, REFRESH_ALL_TYPE_QDISCS), + NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, REFRESH_ALL_TYPE_TFILTERS), + NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); + +static DelayedActionType +delayed_action_type_from_refresh_all_type(RefreshAllType refresh_all_type) +{ + DelayedActionType t; + + nm_assert(refresh_all_type_get_info(refresh_all_type)); + + t = (((DelayedActionType) 1) << refresh_all_type); + + nm_assert(refresh_all_type == delayed_action_type_to_refresh_all_type(t)); + + return t; +} + +static RefreshAllType +refresh_all_type_from_needle_object(const NMPObject *obj_needle) +{ + switch (NMP_OBJECT_GET_TYPE(obj_needle)) { + case NMP_OBJECT_TYPE_LINK: + return REFRESH_ALL_TYPE_LINKS; + case NMP_OBJECT_TYPE_IP4_ADDRESS: + return REFRESH_ALL_TYPE_IP4_ADDRESSES; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return REFRESH_ALL_TYPE_IP6_ADDRESSES; + case NMP_OBJECT_TYPE_IP4_ROUTE: + return REFRESH_ALL_TYPE_IP4_ROUTES; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return REFRESH_ALL_TYPE_IP6_ROUTES; + case NMP_OBJECT_TYPE_QDISC: + return REFRESH_ALL_TYPE_QDISCS; + case NMP_OBJECT_TYPE_TFILTER: + return REFRESH_ALL_TYPE_TFILTERS; + case NMP_OBJECT_TYPE_ROUTING_RULE: + switch (NMP_OBJECT_CAST_ROUTING_RULE(obj_needle)->addr_family) { + case AF_INET: + return REFRESH_ALL_TYPE_ROUTING_RULES_IP4; + case AF_INET6: + return REFRESH_ALL_TYPE_ROUTING_RULES_IP6; + } + nm_assert_not_reached(); + return 0; + default: + nm_assert_not_reached(); + return 0; + } +} + +static const NMPLookup * +refresh_all_type_init_lookup(RefreshAllType refresh_all_type, NMPLookup *lookup) +{ + const RefreshAllInfo *refresh_all_info; + + nm_assert(lookup); + + refresh_all_info = refresh_all_type_get_info(refresh_all_type); + + nm_assert(refresh_all_info); + + if (NM_IN_SET(refresh_all_info->obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)) { + return nmp_lookup_init_object_by_addr_family(lookup, + refresh_all_info->obj_type, + refresh_all_info->addr_family); + } + + /* not yet implemented. */ + nm_assert(refresh_all_info->addr_family == AF_UNSPEC); + + return nmp_lookup_init_obj_type(lookup, refresh_all_info->obj_type); +} + +static DelayedActionType +delayed_action_refresh_from_needle_object(const NMPObject *obj_needle) +{ + return delayed_action_type_from_refresh_all_type( + refresh_all_type_from_needle_object(obj_needle)); +} + +static NM_UTILS_LOOKUP_STR_DEFINE( + delayed_action_to_string, + DelayedActionType, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, "refresh-all-links"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, + "refresh-all-ip4-addresses"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, + "refresh-all-ip6-addresses"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, "refresh-all-ip4-routes"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, "refresh-all-ip6-routes"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, + "refresh-all-routing-rules-ip4"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, + "refresh-all-routing-rules-ip6"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, "refresh-all-qdiscs"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, "refresh-all-tfilters"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_LINK, "refresh-link"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_MASTER_CONNECTED, "master-connected"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_NETLINK, "read-netlink"), + NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, "wait-for-nl-response"), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_NONE), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL), + NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL), + NM_UTILS_LOOKUP_ITEM_IGNORE(__DELAYED_ACTION_TYPE_MAX), ); + +static const char * +delayed_action_to_string_full(DelayedActionType action_type, + gpointer user_data, + char * buf, + gsize buf_size) +{ + char * buf0 = buf; + const DelayedActionWaitForNlResponseData *data; + + nm_utils_strbuf_append_str(&buf, &buf_size, delayed_action_to_string(action_type)); + switch (action_type) { + case DELAYED_ACTION_TYPE_MASTER_CONNECTED: + nm_utils_strbuf_append(&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT(user_data)); + break; + case DELAYED_ACTION_TYPE_REFRESH_LINK: + nm_utils_strbuf_append(&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT(user_data)); + break; + case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE: + data = user_data; + + if (data) { + gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_nsec(); + char b[255]; + + nm_utils_strbuf_append( + &buf, + &buf_size, + " (seq %u, timeout in %s%" G_GINT64_FORMAT ".%09" G_GINT64_FORMAT + ", response-type %d%s%s)", + data->seq_number, + timeout < 0 ? "-" : "", + (timeout < 0 ? -timeout : timeout) / NM_UTILS_NSEC_PER_SEC, + (timeout < 0 ? -timeout : timeout) % NM_UTILS_NSEC_PER_SEC, + (int) data->response_type, + data->seq_result ? ", " : "", + data->seq_result + ? wait_for_nl_response_to_string(data->seq_result, NULL, b, sizeof(b)) + : ""); + } else + nm_utils_strbuf_append_str(&buf, &buf_size, " (any)"); + break; + default: + nm_assert(!user_data); + break; + } + return buf0; +} + +#define _LOGt_delayed_action(action_type, user_data, operation) \ + G_STMT_START \ + { \ + char _buf[255]; \ + \ + _LOGt("delayed-action: %s %s", \ + "" operation, \ + delayed_action_to_string_full(action_type, user_data, _buf, sizeof(_buf))); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static gboolean +delayed_action_refresh_all_in_progress(NMPlatform *platform, DelayedActionType action_type) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + RefreshAllType refresh_all_type; + + nm_assert(nm_utils_is_power_of_two(action_type)); + nm_assert(NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_ALL)); + nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL)); + + if (NM_FLAGS_ANY(priv->delayed_action.flags, action_type)) + return TRUE; + + refresh_all_type = delayed_action_type_to_refresh_all_type(action_type); + return (priv->delayed_action.refresh_all_in_progress[refresh_all_type] > 0); +} + +static void +delayed_action_wait_for_nl_response_complete(NMPlatform * platform, + guint idx, + WaitForNlResponseResult seq_result) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + + nm_assert(NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + nm_assert(idx < priv->delayed_action.list_wait_for_nl_response->len); + nm_assert(seq_result); + + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + idx); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete"); + + if (priv->delayed_action.list_wait_for_nl_response->len <= 1) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE; + if (data->out_seq_result) + *data->out_seq_result = seq_result; + switch (data->response_type) { + case DELAYED_ACTION_RESPONSE_TYPE_VOID: + break; + case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS: + if (data->response.out_refresh_all_in_progress) { + nm_assert(*data->response.out_refresh_all_in_progress > 0); + *data->response.out_refresh_all_in_progress -= 1; + data->response.out_refresh_all_in_progress = NULL; + } + break; + case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET: + if (data->response.out_route_get) { + nm_assert(!*data->response.out_route_get); + data->response.out_route_get = NULL; + } + break; + } + + g_array_remove_index_fast(priv->delayed_action.list_wait_for_nl_response, idx); +} + +static void +delayed_action_wait_for_nl_response_complete_check(NMPlatform * platform, + WaitForNlResponseResult force_result, + guint32 * out_next_seq_number, + gint64 * out_next_timeout_abs_ns, + gint64 * p_now_ns) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + guint i; + guint32 next_seq_number = 0; + gint64 next_timeout_abs_ns = 0; + int now_ns = 0; + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len;) { + const DelayedActionWaitForNlResponseData *data = + &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->seq_result) + delayed_action_wait_for_nl_response_complete(platform, i, data->seq_result); + else if (p_now_ns + && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_nsec())) + >= data->timeout_abs_ns)) { + /* the caller can optionally check for timeout by providing a p_now_ns argument. */ + delayed_action_wait_for_nl_response_complete( + platform, + i, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT); + } else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) + delayed_action_wait_for_nl_response_complete(platform, i, force_result); + else { + if (next_seq_number == 0 || next_timeout_abs_ns > data->timeout_abs_ns) { + next_seq_number = data->seq_number; + next_timeout_abs_ns = data->timeout_abs_ns; + } + i++; + } + } + + if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) { + nm_assert( + !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + nm_assert(priv->delayed_action.list_wait_for_nl_response->len == 0); + } + + NM_SET_OUT(out_next_seq_number, next_seq_number); + NM_SET_OUT(out_next_timeout_abs_ns, next_timeout_abs_ns); + NM_SET_OUT(p_now_ns, now_ns); +} + +static void +delayed_action_wait_for_nl_response_complete_all(NMPlatform * platform, + WaitForNlResponseResult fallback_result) +{ + delayed_action_wait_for_nl_response_complete_check(platform, fallback_result, NULL, NULL, NULL); +} + +/*****************************************************************************/ + +static void +delayed_action_handle_MASTER_CONNECTED(NMPlatform *platform, int master_ifindex) +{ + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCacheOpsType cache_op; + + cache_op = nmp_cache_update_link_master_connected(nm_platform_get_cache(platform), + master_ifindex, + &obj_old, + &obj_new); + if (cache_op == NMP_CACHE_OPS_UNCHANGED) + return; + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); +} + +static void +delayed_action_handle_REFRESH_LINK(NMPlatform *platform, int ifindex) +{ + do_request_link_no_delayed_actions(platform, ifindex, NULL); +} + +static void +delayed_action_handle_REFRESH_ALL(NMPlatform *platform, DelayedActionType flags) +{ + do_request_all_no_delayed_actions(platform, flags); +} + +static void +delayed_action_handle_READ_NETLINK(NMPlatform *platform) +{ + event_handler_read_netlink(platform, FALSE); +} + +static void +delayed_action_handle_WAIT_FOR_NL_RESPONSE(NMPlatform *platform) +{ + event_handler_read_netlink(platform, TRUE); +} + +static gboolean +delayed_action_handle_one(NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gpointer user_data; + + if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE) + return FALSE; + + /* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions. + * This type of action is entirely cache-internal and is here to resolve a + * cache inconsistency. It should be fixed right away. */ + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) { + nm_assert(priv->delayed_action.list_master_connected->len > 0); + + user_data = priv->delayed_action.list_master_connected->pdata[0]; + g_ptr_array_remove_index_fast(priv->delayed_action.list_master_connected, 0); + if (priv->delayed_action.list_master_connected->len == 0) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED; + nm_assert(nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_master_connected->pdata, + priv->delayed_action.list_master_connected->len, + user_data) + < 0); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle"); + delayed_action_handle_MASTER_CONNECTED(platform, GPOINTER_TO_INT(user_data)); + return TRUE; + } + nm_assert(priv->delayed_action.list_master_connected->len == 0); + + /* Next we prefer read-netlink, because the buffer size is limited and we want to process events + * from netlink early. */ + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) { + _LOGt_delayed_action(DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle"); + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK; + delayed_action_handle_READ_NETLINK(platform); + return TRUE; + } + + if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) { + DelayedActionType flags, iflags; + + flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL; + + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL; + + if (_LOGt_ENABLED()) { + FOR_EACH_DELAYED_ACTION(iflags, flags) + _LOGt_delayed_action(iflags, NULL, "handle"); + } + + delayed_action_handle_REFRESH_ALL(platform, flags); + return TRUE; + } + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) { + nm_assert(priv->delayed_action.list_refresh_link->len > 0); + + user_data = priv->delayed_action.list_refresh_link->pdata[0]; + g_ptr_array_remove_index_fast(priv->delayed_action.list_refresh_link, 0); + if (priv->delayed_action.list_refresh_link->len == 0) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK; + nm_assert(nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_refresh_link->pdata, + priv->delayed_action.list_refresh_link->len, + user_data) + < 0); + + _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle"); + + delayed_action_handle_REFRESH_LINK(platform, GPOINTER_TO_INT(user_data)); + + return TRUE; + } + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle"); + delayed_action_handle_WAIT_FOR_NL_RESPONSE(platform); + return TRUE; + } + + return FALSE; +} + +static gboolean +delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gboolean any = FALSE; + + g_return_val_if_fail(priv->delayed_action.is_handling == 0, FALSE); + + priv->delayed_action.is_handling++; + if (read_netlink) + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL); + while (delayed_action_handle_one(platform)) + any = TRUE; + priv->delayed_action.is_handling--; + + cache_prune_all(platform); + + return any; +} + +static void +delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionType iflags; + + nm_assert(action_type != DELAYED_ACTION_TYPE_NONE); + + switch (action_type) { + case DELAYED_ACTION_TYPE_REFRESH_LINK: + if (nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_refresh_link->pdata, + priv->delayed_action.list_refresh_link->len, + user_data) + < 0) + g_ptr_array_add(priv->delayed_action.list_refresh_link, user_data); + break; + case DELAYED_ACTION_TYPE_MASTER_CONNECTED: + if (nm_utils_ptrarray_find_first( + (gconstpointer *) priv->delayed_action.list_master_connected->pdata, + priv->delayed_action.list_master_connected->len, + user_data) + < 0) + g_ptr_array_add(priv->delayed_action.list_master_connected, user_data); + break; + case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE: + g_array_append_vals(priv->delayed_action.list_wait_for_nl_response, user_data, 1); + break; + default: + nm_assert(!user_data); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)); + nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + break; + } + + priv->delayed_action.flags |= action_type; + + if (_LOGt_ENABLED()) { + FOR_EACH_DELAYED_ACTION(iflags, action_type) + _LOGt_delayed_action(iflags, user_data, "schedule"); + } +} + +static void +delayed_action_schedule_WAIT_FOR_NL_RESPONSE(NMPlatform * platform, + guint32 seq_number, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + DelayedActionWaitForNlResponseData data = { + .seq_number = seq_number, + .timeout_abs_ns = + nm_utils_get_monotonic_timestamp_nsec() + (200 * (NM_UTILS_NSEC_PER_SEC / 1000)), + .out_seq_result = out_seq_result, + .out_errmsg = out_errmsg, + .response_type = response_type, + .response.out_data = response_out_data, + }; + + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, &data); +} + +/*****************************************************************************/ + +static void +cache_prune_one_type(NMPlatform *platform, const NMPLookup *lookup) +{ + NMDedupMultiIter iter; + const NMPObject *obj; + NMPCacheOpsType cache_op; + NMPCache * cache = nm_platform_get_cache(platform); + + nm_dedup_multi_iter_init(&iter, nmp_cache_lookup(cache, lookup)); + while (nm_dedup_multi_iter_next(&iter)) { + const NMDedupMultiEntry *main_entry; + + /* we only track the dirty flag for the OBJECT-TYPE index. That means, + * for other lookup types we need to check the dirty flag of the main-entry. */ + main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup); + if (!main_entry->dirty) + continue; + + obj = main_entry->obj; + + _LOGt("cache-prune: prune %s", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + + { + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + cache_op = nmp_cache_remove(cache, obj, TRUE, TRUE, &obj_old); + nm_assert(cache_op == NMP_CACHE_OPS_REMOVED); + cache_on_change(platform, cache_op, obj_old, NULL); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, NULL); + } + } +} + +static void +cache_prune_all(NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + RefreshAllType refresh_all_type; + + for (refresh_all_type = _REFRESH_ALL_TYPE_FIRST; refresh_all_type < _REFRESH_ALL_TYPE_NUM; + refresh_all_type++) { + NMPLookup lookup; + + if (priv->pruning[refresh_all_type] == 0) + continue; + priv->pruning[refresh_all_type] -= 1; + if (priv->pruning[refresh_all_type] > 0) + continue; + refresh_all_type_init_lookup(refresh_all_type, &lookup); + cache_prune_one_type(platform, &lookup); + } +} + +static void +cache_on_change(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ + const NMPClass *klass; + char str_buf[sizeof(_nm_utils_to_string_buffer)]; + char str_buf2[sizeof(_nm_utils_to_string_buffer)]; + NMPCache * cache = nm_platform_get_cache(platform); + + ASSERT_nmp_cache_ops(cache, cache_op, obj_old, obj_new); + nm_assert(cache_op != NMP_CACHE_OPS_UNCHANGED); + + klass = obj_old ? NMP_OBJECT_GET_CLASS(obj_old) : NMP_OBJECT_GET_CLASS(obj_new); + + _LOGt( + "update-cache-%s: %s: %s%s%s", + klass->obj_type_name, + (cache_op == NMP_CACHE_OPS_UPDATED ? "UPDATE" + : (cache_op == NMP_CACHE_OPS_REMOVED ? "REMOVE" + : (cache_op == NMP_CACHE_OPS_ADDED) ? "ADD" + : "???")), + (cache_op != NMP_CACHE_OPS_ADDED + ? nmp_object_to_string(obj_old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2)) + : nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2))), + (cache_op == NMP_CACHE_OPS_UPDATED) ? " -> " : "", + (cache_op == NMP_CACHE_OPS_UPDATED + ? nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof(str_buf)) + : "")); + + switch (klass->obj_type) { + case NMP_OBJECT_TYPE_LINK: + { + /* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */ + if (obj_old + && nmp_cache_link_connected_needs_toggle_by_ifindex(cache, + obj_old->link.master, + obj_new, + obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_old->link.master)); + if (obj_new && (!obj_old || obj_old->link.master != obj_new->link.master) + && nmp_cache_link_connected_needs_toggle_by_ifindex(cache, + obj_new->link.master, + obj_new, + obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_new->link.master)); + } + { + /* check whether we are about to change a master link that needs toggling connected state. */ + if (obj_new /* <-- nonsensical, make coverity happy */ + && nmp_cache_link_connected_needs_toggle(cache, obj_new, obj_new, obj_old)) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_MASTER_CONNECTED, + GINT_TO_POINTER(obj_new->link.ifindex)); + } + { + int ifindex = 0; + + /* if we remove a link (from netlink), we must refresh the addresses, routes, qdiscs and tfilters */ + if (cache_op == NMP_CACHE_OPS_REMOVED + && obj_old /* <-- nonsensical, make coverity happy */) + ifindex = obj_old->link.ifindex; + else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && !obj_new->_link.netlink.is_in_netlink + && obj_new->_link.netlink.is_in_netlink + != obj_old->_link.netlink.is_in_netlink) + ifindex = obj_new->link.ifindex; + + if (ifindex > 0) { + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + } + } + { + int ifindex = -1; + + /* removal of a link could be caused by moving the link to another netns. + * In this case, we potentially have to update other links that have this link as parent. + * Currently, kernel misses to sent us a notification in this case + * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */ + + if (cache_op == NMP_CACHE_OPS_REMOVED + && obj_old /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink) + ifindex = obj_old->link.ifindex; + else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink + && !obj_new->_link.netlink.is_in_netlink) + ifindex = obj_new->link.ifindex; + + if (ifindex > 0) { + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink *l; + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + nmp_cache_iter_for_each_link (&iter, nmp_cache_lookup(cache, &lookup), &l) { + if (l->parent == ifindex) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(l->ifindex)); + } + } + } + { + /* if a link goes down, we must refresh routes */ + if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old + && obj_new /* <-- nonsensical, make coverity happy */ + && obj_old->_link.netlink.is_in_netlink && obj_new->_link.netlink.is_in_netlink + && ((NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_UP) + && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_UP)) + || (NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP) + && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP)))) { + /* FIXME: I suspect that IFF_LOWER_UP must not be considered, and I + * think kernel does send RTM_DELROUTE events for IPv6 routes, so + * we might not need to refresh IPv6 routes. */ + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, + NULL); + } + } + if (NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED) + && (obj_new && obj_new->_link.netlink.is_in_netlink) + && (!obj_old || !obj_old->_link.netlink.is_in_netlink)) { + gboolean re_request_link = FALSE; + const NMPlatformLnkTun *lnk_tun; + + if (!obj_new->_link.netlink.lnk + && NM_IN_SET(obj_new->link.type, + NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP, + NM_LINK_TYPE_INFINIBAND, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_TUN, + NM_LINK_TYPE_VLAN, + NM_LINK_TYPE_VXLAN)) { + /* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that + * kernel didn't send this notification, thus when we first learn about a link + * that lacks an lnk_data we re-request it again. + * + * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_TUN && obj_new->_link.netlink.lnk + && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun) && !lnk_tun->persist + && lnk_tun->pi && !lnk_tun->vnet_hdr && !lnk_tun->multi_queue + && !lnk_tun->owner_valid && !lnk_tun->group_valid) { + /* kernel has/had a know issue that the first notification for TUN device would + * be sent with invalid parameters. The message looks like that kind, so refetch + * it. */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_VETH && obj_new->link.parent == 0) { + /* the initial notification when adding a veth pair can lack the parent/IFLA_LINK + * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827). + * Request it again. */ + re_request_link = TRUE; + } else if (obj_new->link.type == NM_LINK_TYPE_ETHERNET + && obj_new->link.l_address.len == 0) { + /* Due to a kernel bug, we sometimes receive spurious NEWLINK + * messages after a wifi interface has disappeared. Since the + * link is not present anymore we can't determine its type and + * thus it will show up as a Ethernet one, with no address + * specified. Request the link again to check if it really + * exists. https://bugzilla.redhat.com/show_bug.cgi?id=1302037 + */ + re_request_link = TRUE; + } + if (re_request_link) { + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(obj_new->link.ifindex)); + } + } + { + /* on enslave/release, we also refresh the master. */ + int ifindex1 = 0, ifindex2 = 0; + gboolean changed_master, changed_connected; + + changed_master = + (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0 + ? obj_new->link.master + : 0) + != (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0 + ? obj_old->link.master + : 0); + changed_connected = (obj_new && obj_new->_link.netlink.is_in_netlink + ? NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP) + : 2) + != (obj_old && obj_old->_link.netlink.is_in_netlink + ? NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP) + : 2); + + if (changed_master || changed_connected) { + ifindex1 = + (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0) + ? obj_old->link.master + : 0; + ifindex2 = + (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0) + ? obj_new->link.master + : 0; + + if (ifindex1 > 0) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(ifindex1)); + if (ifindex2 > 0 && ifindex1 != ifindex2) + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER(ifindex2)); + } + } + break; + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + { + /* Address deletion is sometimes accompanied by route deletion. We need to + * check all routes belonging to the same interface. */ + if (cache_op == NMP_CACHE_OPS_REMOVED) { + delayed_action_schedule(platform, + (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS) + ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, + NULL); + } + } break; + default: + break; + } +} + +/*****************************************************************************/ + +static guint32 +_nlh_seq_next_get(NMLinuxPlatformPrivate *priv) +{ + /* generate a new sequence number, but never return zero. + * Wrapping numbers are not a problem, because we don't rely + * on strictly increasing sequence numbers. */ + return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next); +} + +/** + * _nl_send_nlmsghdr: + * @platform: + * @nlhdr: + * @out_seq_result: + * @response_type: + * @response_out_data: + * + * Returns: 0 on success or a negative errno. + */ +static int +_nl_send_nlmsghdr(NMPlatform * platform, + struct nlmsghdr * nlhdr, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + guint32 seq; + int errsv; + + nm_assert(nlhdr); + + seq = _nlh_seq_next_get(priv); + nlhdr->nlmsg_seq = seq; + + { + struct sockaddr_nl nladdr = { + .nl_family = AF_NETLINK, + }; + struct iovec iov = {.iov_base = nlhdr, .iov_len = nlhdr->nlmsg_len}; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int try_count; + + if (!nlhdr->nlmsg_pid) + nlhdr->nlmsg_pid = nl_socket_get_local_port(priv->nlh); + nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK); + + try_count = 0; +again: + errsv = sendmsg(nl_socket_get_fd(priv->nlh), &msg, 0); + if (errsv < 0) { + errsv = errno; + if (errsv == EINTR && try_count++ < 100) + goto again; + _LOGD("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", + nm_strerror_native(errsv), + errsv); + return -nm_errno_from_native(errsv); + } + } + + delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform, + seq, + out_seq_result, + out_errmsg, + response_type, + response_out_data); + return 0; +} + +/** + * _nl_send_nlmsg: + * @platform: + * @nlmsg: + * @out_seq_result: + * @response_type: + * @response_out_data: + * + * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno). + */ +static int +_nl_send_nlmsg(NMPlatform * platform, + struct nl_msg * nlmsg, + WaitForNlResponseResult * out_seq_result, + char ** out_errmsg, + DelayedActionWaitForNlResponseType response_type, + gpointer response_out_data) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + struct nlmsghdr * nlhdr; + guint32 seq; + int nle; + + nlhdr = nlmsg_hdr(nlmsg); + seq = _nlh_seq_next_get(priv); + nlhdr->nlmsg_seq = seq; + + nle = nl_send_auto(priv->nlh, nlmsg); + if (nle < 0) { + _LOGD("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle); + return nle; + } + + delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform, + seq, + out_seq_result, + out_errmsg, + response_type, + response_out_data); + return 0; +} + +static void +do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int nle; + + if (name && !name[0]) + name = NULL; + + g_return_if_fail(ifindex > 0 || name); + + _LOGD("do-request-link: %d %s", ifindex, name ?: ""); + + if (ifindex > 0) { + const NMDedupMultiEntry *entry; + + entry = nmp_cache_lookup_entry_link(nm_platform_get_cache(platform), ifindex); + if (entry) { + priv->pruning[REFRESH_ALL_TYPE_LINKS] += 1; + nm_dedup_multi_entry_set_dirty(entry, TRUE); + } + } + + event_handler_read_netlink(platform, FALSE); + + nlmsg = _nl_msg_new_link(RTM_GETLINK, 0, ifindex, name); + if (nlmsg) { + nle = _nl_send_nlmsg(platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + if (nle < 0) { + _LOGE("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)", + ifindex, + name ?: "", + nm_strerror(nle), + -nle); + return; + } + } +} + +static void +do_request_link(NMPlatform *platform, int ifindex, const char *name) +{ + do_request_link_no_delayed_actions(platform, ifindex, name); + delayed_action_handle_all(platform, FALSE); +} + +static struct nl_msg * +_nl_msg_new_dump(NMPObjectType obj_type, int preferred_addr_family) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const NMPClass * klass; + + klass = nmp_class_from_type(obj_type); + + nm_assert(klass); + nm_assert(klass->rtm_gettype > 0); + + nlmsg = nlmsg_alloc_simple(klass->rtm_gettype, NLM_F_DUMP); + + if (klass->addr_family != AF_UNSPEC) { + /* if the class specifies a particular address family, then it is preferred. */ + nm_assert(NM_IN_SET(preferred_addr_family, AF_UNSPEC, klass->addr_family)); + preferred_addr_family = klass->addr_family; + } + + switch (klass->obj_type) { + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + { + const struct tcmsg tcmsg = { + .tcm_family = preferred_addr_family, + }; + + if (nlmsg_append_struct(nlmsg, &tcmsg) < 0) + g_return_val_if_reached(NULL); + } break; + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_ROUTING_RULE: + { + const struct rtgenmsg gmsg = { + .rtgen_family = preferred_addr_family, + }; + + if (nlmsg_append_struct(nlmsg, &gmsg) < 0) + g_return_val_if_reached(NULL); + } break; + default: + g_return_val_if_reached(NULL); + } + + return g_steal_pointer(&nlmsg); +} + +static void +do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionType action_type_prune; + DelayedActionType iflags; + + nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL)); + action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL; + + action_type_prune = action_type; + + /* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra + * cache lookup for every entry. + * + * Avoid that, by special casing routing-rules here. */ + if (NM_FLAGS_ALL(action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL)) { + NMPLookup lookup; + + priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP4] += 1; + priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP6] += 1; + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup); + action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL; + } + + FOR_EACH_DELAYED_ACTION(iflags, action_type_prune) + { + RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags); + NMPLookup lookup; + + priv->pruning[refresh_all_type] += 1; + refresh_all_type_init_lookup(refresh_all_type, &lookup); + nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup); + } + + FOR_EACH_DELAYED_ACTION(iflags, action_type) + { + RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags); + const RefreshAllInfo *refresh_all_info = refresh_all_type_get_info(refresh_all_type); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int * out_refresh_all_in_progress; + + out_refresh_all_in_progress = + &priv->delayed_action.refresh_all_in_progress[refresh_all_type]; + nm_assert(*out_refresh_all_in_progress >= 0); + *out_refresh_all_in_progress += 1; + + /* clear any delayed action that request a refresh of this object type. */ + priv->delayed_action.flags &= ~iflags; + _LOGt_delayed_action(iflags, NULL, "handle (do-request-all)"); + + if (refresh_all_type == REFRESH_ALL_TYPE_LINKS) { + nm_assert( + (priv->delayed_action.list_refresh_link->len > 0) + == NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)); + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) { + _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK, + NULL, + "clear (do-request-all)"); + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK; + g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0); + } + } + + event_handler_read_netlink(platform, FALSE); + + nlmsg = _nl_msg_new_dump(refresh_all_info->obj_type, refresh_all_info->addr_family); + if (!nlmsg) + goto next_after_fail; + + if (_nl_send_nlmsg(platform, + nlmsg, + NULL, + NULL, + DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, + out_refresh_all_in_progress) + < 0) + goto next_after_fail; + + continue; + +next_after_fail: + nm_assert(*out_refresh_all_in_progress > 0); + *out_refresh_all_in_progress -= 1; + } +} + +static void +do_request_one_type_by_needle_object(NMPlatform *platform, const NMPObject *obj_needle) +{ + do_request_all_no_delayed_actions(platform, + delayed_action_refresh_from_needle_object(obj_needle)); + delayed_action_handle_all(platform, FALSE); +} + +static void +event_seq_check_refresh_all(NMPlatform *platform, guint32 seq_number) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + guint i; + + if (NM_IN_SET(seq_number, 0, priv->nlh_seq_last_seen)) + return; + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS + && data->response.out_refresh_all_in_progress + && data->seq_number == priv->nlh_seq_last_seen) { + *data->response.out_refresh_all_in_progress -= 1; + data->response.out_refresh_all_in_progress = NULL; + break; + } + } + } + + priv->nlh_seq_last_seen = seq_number; +} + +static void +event_seq_check(NMPlatform * platform, + guint32 seq_number, + WaitForNlResponseResult seq_result, + const char * msg) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + DelayedActionWaitForNlResponseData *data; + guint i; + + if (seq_number == 0) + return; + + if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + data = &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->seq_number == seq_number) { + /* We potentially receive many parts partial responses for the same sequence number. + * Thus, we only remember the result, and collect it later. */ + if (data->seq_result < 0) { + /* we already saw an error for this sequence number. + * Preserve it. */ + } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN + || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) + data->seq_result = seq_result; + if (data->out_errmsg && !*data->out_errmsg) + *data->out_errmsg = g_strdup(msg); + return; + } + } + } + +#if NM_MORE_LOGGING + if (seq_number != priv->nlh_seq_last_handled) + _LOGt("netlink: recvmsg: unwaited sequence number %u", seq_number); + priv->nlh_seq_last_handled = seq_number; +#endif +} + +static void +event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events) +{ + NMLinuxPlatformPrivate *priv; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPCacheOpsType cache_op; + struct nlmsghdr * msghdr; + char buf_nlmsghdr[400]; + gboolean is_del = FALSE; + gboolean is_dump = FALSE; + NMPCache * cache = nm_platform_get_cache(platform); + + msghdr = nlmsg_hdr(msg); + + if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) + && msghdr->nlmsg_type == RTM_NEWADDR) { + /* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6, + * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */ + if (nlmsg_valid_hdr(msghdr, sizeof(struct ifaddrmsg)) + && NM_IN_SET(((struct ifaddrmsg *) nlmsg_data(msghdr))->ifa_family, + AF_INET, + AF_INET6)) { + /* see if the nl_msg contains the IFA_FLAGS attribute. If it does, + * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR + * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14, + * dated 30 March, 2014. + * + * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy + * way to detect kernel support. */ + _nm_platform_kernel_support_init( + NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS, + !!nlmsg_find_attr(msghdr, sizeof(struct ifaddrmsg), IFA_FLAGS) ? 1 : -1); + } + } + + if (!handle_events) + return; + + if (NM_IN_SET(msghdr->nlmsg_type, + RTM_DELLINK, + RTM_DELADDR, + RTM_DELROUTE, + RTM_DELRULE, + RTM_DELQDISC, + RTM_DELTFILTER)) { + /* The event notifies about a deleted object. We don't need to initialize all + * fields of the object. */ + is_del = TRUE; + } + + obj = nmp_object_new_from_nl(platform, cache, msg, is_del); + if (!obj) { + _LOGT("event-notification: %s: ignore", + nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr))); + return; + } + + if (!is_del + && NM_IN_SET(msghdr->nlmsg_type, + RTM_NEWADDR, + RTM_NEWLINK, + RTM_NEWROUTE, + RTM_NEWRULE, + RTM_NEWQDISC, + RTM_NEWTFILTER)) { + is_dump = + delayed_action_refresh_all_in_progress(platform, + delayed_action_refresh_from_needle_object(obj)); + } + + _LOGT("event-notification: %s%s: %s", + nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)), + is_dump ? ", in-dump" : "", + nmp_object_to_string(obj, + is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, + NULL, + 0)); + + { + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + + switch (msghdr->nlmsg_type) { + case RTM_GETLINK: + case RTM_NEWADDR: + case RTM_NEWLINK: + case RTM_NEWQDISC: + case RTM_NEWRULE: + case RTM_NEWTFILTER: + cache_op = nmp_cache_update_netlink(cache, obj, is_dump, &obj_old, &obj_new); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + break; + + case RTM_NEWROUTE: + { + nm_auto_nmpobj const NMPObject *obj_replace = NULL; + gboolean resync_required = FALSE; + gboolean only_dirty = FALSE; + gboolean is_ipv6; + + /* IPv4 routes that are a response to RTM_GETROUTE must have + * the cloned flag while IPv6 routes don't have to. */ + is_ipv6 = NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP6_ROUTE; + if (is_ipv6 || NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED)) { + nm_assert(is_ipv6 || !nmp_object_is_alive(obj)); + priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + if (NM_FLAGS_HAS(priv->delayed_action.flags, + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + guint i; + + nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0); + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) { + DelayedActionWaitForNlResponseData *data = + &g_array_index(priv->delayed_action.list_wait_for_nl_response, + DelayedActionWaitForNlResponseData, + i); + + if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET + && data->response.out_route_get) { + nm_assert(!*data->response.out_route_get); + if (data->seq_number == nlmsg_hdr(msg)->nlmsg_seq) { + *data->response.out_route_get = nmp_object_clone(obj, FALSE); + data->response.out_route_get = NULL; + break; + } + } + } + } + } + + cache_op = nmp_cache_update_netlink_route(cache, + obj, + is_dump, + msghdr->nlmsg_flags, + &obj_old, + &obj_new, + &obj_replace, + &resync_required); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + if (obj_replace) { + const NMDedupMultiEntry *entry_replace; + + /* we found an object that is to be replaced by the RTM_NEWROUTE message. + * While we invoke the signal, the platform cache might change and invalidate + * the findings. Mitigate that (for the most part), by marking the entry as + * dirty and only delete @obj_replace if it is still dirty afterwards. + * + * Yes, there is a tiny tiny chance for still getting it wrong. But in practice, + * the signal handlers do not cause to call the platform again, so the cache + * is not really changing. -- if they would, it would anyway be dangerous to overflow + * the stack and it's not ensured that the processing of netlink messages is + * reentrant (maybe it is). + */ + entry_replace = nmp_cache_lookup_entry(cache, obj_replace); + nm_assert(entry_replace && entry_replace->obj == obj_replace); + nm_dedup_multi_entry_set_dirty(entry_replace, TRUE); + only_dirty = TRUE; + } + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + + if (obj_replace) { + /* the RTM_NEWROUTE message indicates that another route was replaced. + * Remove it now. */ + cache_op = nmp_cache_remove(cache, obj_replace, TRUE, only_dirty, NULL); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_assert(cache_op == NMP_CACHE_OPS_REMOVED); + cache_on_change(platform, cache_op, obj_replace, NULL); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_replace, NULL); + } + } + + if (resync_required) { + /* we'd like to avoid such resyncs as they are expensive and we should only rely on the + * netlink events. This needs investigation. */ + _LOGT("schedule resync of routes after RTM_NEWROUTE"); + delayed_action_schedule(platform, + delayed_action_refresh_from_needle_object(obj), + NULL); + } + break; + } + + case RTM_DELADDR: + case RTM_DELLINK: + case RTM_DELQDISC: + case RTM_DELROUTE: + case RTM_DELRULE: + case RTM_DELTFILTER: + cache_op = nmp_cache_remove_netlink(cache, obj, &obj_old, &obj_new); + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + cache_on_change(platform, cache_op, obj_old, obj_new); + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } + break; + default: + break; + } + } +} + +/*****************************************************************************/ + +static int +do_add_link_with_lookup(NMPlatform * platform, + NMLinkType link_type, + const char * name, + struct nl_msg * nlmsg, + const NMPlatformLink **out_link) +{ + const NMPObject * obj = NULL; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + NMPCache * cache = nm_platform_get_cache(platform); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)", + name, + nm_link_type_to_string(link_type), + nm_strerror(nle), + -nle); + NM_SET_OUT(out_link, NULL); + return nle; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-link[%s/%s]: %s", + name, + nm_link_type_to_string(link_type), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (out_link) { + obj = nmp_cache_lookup_link_full(cache, 0, name, FALSE, link_type, NULL, NULL); + *out_link = NMP_OBJECT_CAST_LINK(obj); + } + + return wait_for_nl_response_to_nmerr(seq_result); +} + +static int +do_add_addrroute(NMPlatform * platform, + const NMPObject *obj_id, + struct nl_msg * nlmsg, + gboolean suppress_netlink_failure) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + nm_strerror(nle), + -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK + || (suppress_netlink_failure && seq_result < 0)) + ? LOGL_DEBUG + : LOGL_WARN, + "do-add-%s[%s]: %s", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (NMP_OBJECT_GET_TYPE(obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) { + /* In rare cases, the object is not yet ready as we received the ACK from + * kernel. Need to refetch. + * + * We want to safe the expensive refetch, thus we look first into the cache + * whether the object exists. + * + * rh#1484434 */ + if (!nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id)) + do_request_one_type_by_needle_object(platform, obj_id); + } + + return wait_for_nl_response_to_nmerr(seq_result); +} + +static gboolean +do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + gboolean success; + const char * log_detail = ""; + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + nm_strerror(nle), + -nle); + return FALSE; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + success = TRUE; + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + /* ok */ + } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT)) + log_detail = ", meaning the object was already removed"; + else if (NM_IN_SET(-((int) seq_result), ENXIO) + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) { + /* On RHEL7 kernel, deleting a non existing address fails with ENXIO */ + log_detail = ", meaning the address was already removed"; + } else if (NM_IN_SET(-((int) seq_result), EADDRNOTAVAIL) + && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS)) + log_detail = ", meaning the address was already removed"; + else + success = FALSE; + + _NMLOG(success ? LOGL_DEBUG : LOGL_WARN, + "do-delete-%s[%s]: %s%s", + NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name, + nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)), + log_detail); + + if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER)) { + /* In rare cases, the object is still there after we receive the ACK from + * kernel. Need to refetch. + * + * We want to safe the expensive refetch, thus we look first into the cache + * whether the object exists. + * + * rh#1484434 */ + if (nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id)) + do_request_one_type_by_needle_object(platform, obj_id); + } + + return success; +} + +static int +do_change_link(NMPlatform * platform, + ChangeLinkType change_link_type, + int ifindex, + struct nl_msg * nlmsg, + const ChangeLinkData *data) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + int nle; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + char s_buf[256]; + int result = 0; + NMLogLevel log_level = LOGL_DEBUG; + const char * log_result = "failure"; + const char * log_detail = ""; + gs_free char * log_detail_free = NULL; + const NMPObject * obj_cache; + + if (!nm_platform_netns_push(platform, &netns)) { + log_level = LOGL_ERR; + log_detail = ", failure to change network namespace"; + goto out; + } + +retry: + nle = _nl_send_nlmsg(platform, + nlmsg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + log_level = LOGL_ERR; + log_detail_free = + g_strdup_printf(", failure sending netlink request: %s (%d)", nm_strerror(nle), -nle); + log_detail = log_detail_free; + goto out; + } + + /* always refetch the link after changing it. There seems to be issues + * and we sometimes lack events. Nuke it from the orbit... */ + delayed_action_schedule(platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER(ifindex)); + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + if (NM_IN_SET(-((int) seq_result), EOPNOTSUPP) && nlmsg_hdr(nlmsg)->nlmsg_type == RTM_NEWLINK) { + nlmsg_hdr(nlmsg)->nlmsg_type = RTM_SETLINK; + goto retry; + } + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + log_result = "success"; + } else if (NM_IN_SET(-((int) seq_result), EEXIST, EADDRINUSE)) { + /* */ + } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT)) { + log_detail = ", firmware not found"; + result = -NME_PL_NO_FIRMWARE; + } else if (NM_IN_SET(-((int) seq_result), ERANGE) + && change_link_type == CHANGE_LINK_TYPE_SET_MTU) { + log_detail = ", setting MTU to requested size is not possible"; + result = -NME_PL_CANT_SET_MTU; + } else if (NM_IN_SET(-((int) seq_result), ENFILE) + && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS + && (obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex)) + && obj_cache->link.l_address.len == data->set_address.length + && memcmp(obj_cache->link.l_address.data, + data->set_address.address, + data->set_address.length) + == 0) { + /* workaround ENFILE which may be wrongly returned (bgo #770456). + * If the MAC address is as expected, assume success? */ + log_result = "success"; + log_detail = " (assume success changing address)"; + result = 0; + } else if (NM_IN_SET(-((int) seq_result), ENODEV)) { + log_level = LOGL_DEBUG; + result = -NME_PL_NOT_FOUND; + } else if (-((int) seq_result) == EAFNOSUPPORT) { + log_level = LOGL_DEBUG; + result = -NME_PL_OPNOTSUPP; + } else { + log_level = LOGL_WARN; + result = -NME_UNSPEC; + } + +out: + _NMLOG(log_level, + "do-change-link[%d]: %s changing link: %s%s", + ifindex, + log_result, + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)), + log_detail); + return result; +} + +static int +link_add(NMPlatform * platform, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + if (type == NM_LINK_TYPE_BOND) { + /* When the kernel loads the bond module, either via explicit modprobe + * or automatically in response to creating a bond master, it will also + * create a 'bond0' interface. Since the bond we're about to create may + * or may not be named 'bond0' prevent potential confusion about a bond + * that the user didn't want by telling the bonding module not to create + * bond0 automatically. + */ + if (!g_file_test("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS)) + (void) nmp_utils_modprobe(NULL, TRUE, "bonding", "max_bonds=0", NULL); + } + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL, 0, name); + if (!nlmsg) + return -NME_UNSPEC; + + if (parent > 0) + NLA_PUT_U32(nlmsg, IFLA_LINK, parent); + + if (address && address_len) + NLA_PUT(nlmsg, IFLA_ADDRESS, address_len, address); + + if (mtu) + NLA_PUT_U32(nlmsg, IFLA_MTU, mtu); + + if (!_nl_msg_new_link_set_linkinfo(nlmsg, type, extra_data)) + return -NME_UNSPEC; + + return do_add_link_with_lookup(platform, type, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached(-NME_BUG); +} + +static gboolean +link_delete(NMPlatform *platform, int ifindex) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + const NMPObject * obj; + + obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj || !obj->_link.netlink.is_in_netlink) + return FALSE; + + nlmsg = _nl_msg_new_link(RTM_DELLINK, 0, ifindex, NULL); + + nmp_object_stackinit_id_link(&obj_id, ifindex); + return do_delete_object(platform, &obj_id, nlmsg); +} + +static gboolean +link_refresh(NMPlatform *platform, int ifindex) +{ + do_request_link(platform, ifindex, NULL); + return !!nm_platform_link_get_obj(platform, ifindex, TRUE); +} + +static gboolean +link_set_netns(NMPlatform *platform, int ifindex, int netns_fd) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT(nlmsg, IFLA_NET_NS_FD, 4, &netns_fd); + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static int +link_change_flags(NMPlatform *platform, int ifindex, unsigned flags_mask, unsigned flags_set) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + char s_flags[100]; + char s_flags2[100]; + + _LOGD("link: change %d: flags: set 0x%x/0x%x ([%s] / [%s])", + ifindex, + flags_set, + flags_mask, + nm_platform_link_flags2str(flags_set, s_flags, sizeof(s_flags)), + nm_platform_link_flags2str(flags_mask, s_flags2, sizeof(s_flags2))); + + nlmsg = _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, flags_mask, flags_set); + if (!nlmsg) + return -NME_UNSPEC; + return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL); +} + +static int +link_set_user_ipv6ll_enabled(NMPlatform *platform, int ifindex, gboolean enabled) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64; + + _LOGD("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s", + ifindex, + nm_platform_link_inet6_addrgenmode2str(mode, NULL, 0)); + + if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + _LOGD("link: change %d: user-ipv6ll: not supported", ifindex); + return -NME_PL_OPNOTSUPP; + } + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, mode, NULL)) + g_return_val_if_reached(-NME_BUG); + + return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL); +} + +static gboolean +link_set_token(NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _LOGD("link: change %d: token: set IPv6 address generation token to %s", + ifindex, + nm_utils_inet6_interface_identifier_to_token(iid, sbuf)); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, -1, &iid)) + g_return_val_if_reached(FALSE); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +} + +static gboolean +link_supports_carrier_detect(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + /* We use netlink for the actual carrier detection, but netlink can't tell + * us whether the device actually supports carrier detection in the first + * place. We assume any device that does implements one of these two APIs. + */ + return nmp_utils_ethtool_supports_carrier_detect(ifindex) + || nmp_utils_mii_supports_carrier_detect(ifindex); +} + +static gboolean +link_supports_vlans(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + const NMPObject * obj; + + obj = nm_platform_link_get_obj(platform, ifindex, TRUE); + + /* Only ARPHRD_ETHER links can possibly support VLANs. */ + if (!obj || obj->link.arptype != ARPHRD_ETHER) + return FALSE; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + return nmp_utils_ethtool_supports_vlans(ifindex); +} + +static gboolean +link_supports_sriov(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + nm_auto_close int dirfd = -1; + char ifname[IFNAMSIZ]; + int num = -1; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname); + if (dirfd < 0) + return FALSE; + + num = + nm_platform_sysctl_get_int32(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + -1); + + return num != -1; +} + +static int +link_set_address(NMPlatform *platform, int ifindex, gconstpointer address, size_t length) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const ChangeLinkData d = { + .set_address = + { + .address = address, + .length = length, + }, + }; + + if (!address || !length) + g_return_val_if_reached(-NME_BUG); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT(nlmsg, IFLA_ADDRESS, length, address); + + return do_change_link(platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d); +nla_put_failure: + g_return_val_if_reached(-NME_BUG); +} + +static int +link_set_name(NMPlatform *platform, int ifindex, const char *name) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + NLA_PUT(nlmsg, IFLA_IFNAME, strlen(name) + 1, name); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_get_permanent_address(NMPlatform *platform, int ifindex, guint8 *buf, size_t *length) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + return nmp_utils_ethtool_get_permanent_address(ifindex, buf, length); +} + +static int +link_set_mtu(NMPlatform *platform, int ifindex, guint32 mtu) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT_U32(nlmsg, IFLA_MTU, mtu); + + return do_change_link(platform, CHANGE_LINK_TYPE_SET_MTU, ifindex, nlmsg, NULL); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static void +sriov_idle_cb(gpointer user_data, GCancellable *cancellable) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_free_error GError *cancelled_error = NULL; + gs_free_error GError * error = NULL; + NMPlatformAsyncCallback callback; + gpointer callback_data; + + g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); + nm_utils_user_data_unpack(user_data, &platform, &error, &callback, &callback_data); + callback(cancelled_error ?: error, callback_data); +} + +static void +link_set_sriov_params_async(NMPlatform * platform, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + gs_free_error GError *error = NULL; + nm_auto_close int dirfd = -1; + int current_autoprobe; + guint i, total; + gint64 current_num; + char ifname[IFNAMSIZ]; + gpointer packed; + const char * values[3]; + char buf[64]; + + g_return_if_fail(callback || !data); + g_return_if_fail(cancellable); + + if (!nm_platform_netns_push(platform, &netns)) { + g_set_error_literal(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't change namespace"); + goto out_idle; + } + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname); + if (!dirfd) { + g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "couldn't open netdir"); + goto out_idle; + } + + total = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_totalvfs"), + 10, + 0, + G_MAXUINT, + 0); + if (!errno && num_vfs > total) { + _LOGW("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs); + num_vfs = total; + } + + /* + * Take special care when setting new values: + * - don't touch anything if the right values are already set + * - to change the number of VFs or autoprobe we need to destroy existing VFs + * - the autoprobe setting is irrelevant when numvfs is zero + */ + current_num = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + 10, + 0, + G_MAXUINT, + -1); + current_autoprobe = nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"), + 10, + 0, + 1, + -1); + + if (current_autoprobe == -1 && errno == ENOENT) { + /* older kernel versions don't have this sysctl. Assume the value is + * "1". */ + current_autoprobe = 1; + } + + if (current_num == num_vfs + && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe)) + goto out_idle; + + if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE) + && current_autoprobe != autoprobe + && !nm_platform_sysctl_set( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"), + nm_sprintf_buf(buf, "%d", (int) autoprobe))) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't set SR-IOV drivers-autoprobe to %d: %s", + (int) autoprobe, + nm_strerror_native(errno)); + goto out_idle; + } + + if (current_num == 0 && num_vfs == 0) + goto out_idle; + + i = 0; + if (current_num != 0) + values[i++] = "0"; + if (num_vfs != 0) + values[i++] = nm_sprintf_bufa(32, "%u", num_vfs); + values[i++] = NULL; + + sysctl_set_async(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"), + values, + callback, + data, + cancellable); + return; + +out_idle: + if (callback) { + packed = nm_utils_user_data_pack(g_object_ref(platform), + g_steal_pointer(&error), + callback, + data); + nm_utils_invoke_on_idle(cancellable, sriov_idle_cb, packed); + } +} + +static gboolean +link_set_sriov_vfs(NMPlatform *platform, int ifindex, const NMPlatformVF *const *vfs) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr * list, *info, *vlan_list; + guint i; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + if (!(list = nla_nest_start(nlmsg, IFLA_VFINFO_LIST))) + goto nla_put_failure; + + for (i = 0; vfs[i]; i++) { + const NMPlatformVF *vf = vfs[i]; + + if (!(info = nla_nest_start(nlmsg, IFLA_VF_INFO))) + goto nla_put_failure; + + if (vf->spoofchk >= 0) { + struct _ifla_vf_setting ivs = {0}; + + ivs.vf = vf->index; + ivs.setting = vf->spoofchk; + NLA_PUT(nlmsg, IFLA_VF_SPOOFCHK, sizeof(ivs), &ivs); + } + + if (vf->trust >= 0) { + struct _ifla_vf_setting ivs = {0}; + + ivs.vf = vf->index; + ivs.setting = vf->trust; + NLA_PUT(nlmsg, IFLA_VF_TRUST, sizeof(ivs), &ivs); + } + + if (vf->mac.len) { + struct ifla_vf_mac ivm = {0}; + + ivm.vf = vf->index; + memcpy(ivm.mac, vf->mac.data, vf->mac.len); + NLA_PUT(nlmsg, IFLA_VF_MAC, sizeof(ivm), &ivm); + } + + if (vf->min_tx_rate || vf->max_tx_rate) { + struct _ifla_vf_rate ivr = {0}; + + ivr.vf = vf->index; + ivr.min_tx_rate = vf->min_tx_rate; + ivr.max_tx_rate = vf->max_tx_rate; + NLA_PUT(nlmsg, IFLA_VF_RATE, sizeof(ivr), &ivr); + } + + /* Kernel only supports one VLAN per VF now. If this + * changes in the future, we need to figure out how to + * clear existing VLANs and set new ones in one message + * with the new API.*/ + if (vf->num_vlans > 1) { + _LOGW("multiple VLANs per VF are not supported at the moment"); + return FALSE; + } else { + struct _ifla_vf_vlan_info ivvi = {0}; + + if (!(vlan_list = nla_nest_start(nlmsg, IFLA_VF_VLAN_LIST))) + goto nla_put_failure; + + ivvi.vf = vf->index; + if (vf->num_vlans == 1) { + ivvi.vlan = vf->vlans[0].id; + ivvi.qos = vf->vlans[0].qos; + ivvi.vlan_proto = htons(vf->vlans[0].proto_ad ? ETH_P_8021AD : ETH_P_8021Q); + } else { + /* Clear existing VLAN */ + ivvi.vlan = 0; + ivvi.qos = 0; + ivvi.vlan_proto = htons(ETH_P_8021Q); + } + + NLA_PUT(nlmsg, IFLA_VF_VLAN_INFO, sizeof(ivvi), &ivvi); + nla_nest_end(nlmsg, vlan_list); + } + nla_nest_end(nlmsg, info); + } + nla_nest_end(nlmsg, list); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_set_bridge_vlans(NMPlatform * platform, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr * list; + struct bridge_vlan_info vinfo = {}; + guint i; + + nlmsg = + _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK, 0, ifindex, NULL, AF_BRIDGE, 0, 0); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + + if (!(list = nla_nest_start(nlmsg, IFLA_AF_SPEC))) + goto nla_put_failure; + + NLA_PUT_U16(nlmsg, IFLA_BRIDGE_FLAGS, on_master ? BRIDGE_FLAGS_MASTER : BRIDGE_FLAGS_SELF); + + if (vlans) { + /* Add VLANs */ + for (i = 0; vlans[i]; i++) { + const NMPlatformBridgeVlan *vlan = vlans[i]; + gboolean is_range = vlan->vid_start != vlan->vid_end; + + vinfo.vid = vlan->vid_start; + vinfo.flags = is_range ? BRIDGE_VLAN_INFO_RANGE_BEGIN : 0; + + if (vlan->untagged) + vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED; + if (vlan->pvid) + vinfo.flags |= BRIDGE_VLAN_INFO_PVID; + + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + + if (is_range) { + vinfo.vid = vlan->vid_end; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + } + } + } else { + /* Flush existing VLANs */ + vinfo.vid = 1; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + + vinfo.vid = 4094; + vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END; + NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo); + } + + nla_nest_end(nlmsg, list); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static char * +link_get_physical_port_id(NMPlatform *platform, int ifindex) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified); + if (dirfd < 0) + return NULL; + return sysctl_get(platform, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "phys_port_id")); +} + +static guint +link_get_dev_id(NMPlatform *platform, int ifindex) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + + dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified); + if (dirfd < 0) + return 0; + return nm_platform_sysctl_get_int_checked( + platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "dev_id"), + 16, + 0, + G_MAXUINT16, + 0); +} + +static gboolean +link_tun_add(NMPlatform * platform, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd) +{ + const NMPObject * obj; + struct ifreq ifr = {}; + nm_auto_close int fd = -1; + + nm_assert(NM_IN_SET(props->type, IFF_TAP, IFF_TUN)); + nm_assert(props->persist || out_fd); + + fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC); + if (fd < 0) + return FALSE; + + nm_utils_ifname_cpy(ifr.ifr_name, name); + ifr.ifr_flags = ((short) props->type) | ((short) IFF_TUN_EXCL) + | (!props->pi ? (short) IFF_NO_PI : (short) 0) + | (props->vnet_hdr ? (short) IFF_VNET_HDR : (short) 0) + | (props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0); + if (ioctl(fd, TUNSETIFF, &ifr)) + return FALSE; + + if (props->owner_valid) { + if (ioctl(fd, TUNSETOWNER, (uid_t) props->owner)) + return FALSE; + } + + if (props->group_valid) { + if (ioctl(fd, TUNSETGROUP, (gid_t) props->group)) + return FALSE; + } + + if (props->persist) { + if (ioctl(fd, TUNSETPERSIST, 1)) + return FALSE; + } + + do_request_link(platform, 0, name); + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + name, + FALSE, + NM_LINK_TYPE_TUN, + NULL, + NULL); + + if (!obj) + return FALSE; + + NM_SET_OUT(out_link, &obj->link); + NM_SET_OUT(out_fd, nm_steal_fd(&fd)); + return TRUE; +} + +static void +_vlan_change_vlan_qos_mapping_create(gboolean is_ingress_map, + gboolean reset_all, + const NMVlanQosMapping *current_map, + guint current_n_map, + const NMVlanQosMapping *set_map, + guint set_n_map, + NMVlanQosMapping ** out_map, + guint * out_n_map) +{ + NMVlanQosMapping *map; + guint i, j, len; + const guint INGRESS_RANGE_LEN = 8; + + nm_assert(out_map && !*out_map); + nm_assert(out_n_map && !*out_n_map); + + if (!reset_all) + current_n_map = 0; + else if (is_ingress_map) + current_n_map = INGRESS_RANGE_LEN; + + len = current_n_map + set_n_map; + + if (len == 0) + return; + + map = g_new(NMVlanQosMapping, len); + + if (current_n_map) { + if (is_ingress_map) { + /* For the ingress-map, there are only 8 entries (0 to 7). + * When the user requests to reset all entries, we don't actually + * need the cached entries, we can just explicitly clear all possible + * ones. + * + * That makes only a real difference in case our cache is out-of-date. + * + * For the egress map we cannot do that, because there are far too + * many. There we can only clear the entries that we know about. */ + for (i = 0; i < INGRESS_RANGE_LEN; i++) { + map[i].from = i; + map[i].to = 0; + } + } else { + for (i = 0; i < current_n_map; i++) { + map[i].from = current_map[i].from; + map[i].to = 0; + } + } + } + if (set_n_map) + memcpy(&map[current_n_map], set_map, sizeof(*set_map) * set_n_map); + + g_qsort_with_data(map, len, sizeof(*map), _vlan_qos_mapping_cmp_from, NULL); + + for (i = 0, j = 0; i < len; i++) { + if ((is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].from)) + || (!is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].to))) + continue; + if (j > 0 && map[j - 1].from == map[i].from) + map[j - 1] = map[i]; + else + map[j++] = map[i]; + } + + *out_map = map; + *out_n_map = j; +} + +static gboolean +link_vlan_change(NMPlatform * platform, + int ifindex, + _NMVlanFlags flags_mask, + _NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map) +{ + const NMPObject * obj_cache; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + const NMPObjectLnkVlan * lnk; + guint new_n_ingress_map = 0; + guint new_n_egress_map = 0; + gs_free NMVlanQosMapping *new_ingress_map = NULL; + gs_free NMVlanQosMapping *new_egress_map = NULL; + + obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) { + _LOGD("link: change %d: %s: link does not exist", ifindex, "vlan"); + return FALSE; + } + + lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL; + + flags_set &= flags_mask; + + _vlan_change_vlan_qos_mapping_create(TRUE, + ingress_reset_all, + lnk ? lnk->ingress_qos_map : NULL, + lnk ? lnk->n_ingress_qos_map : 0, + ingress_map, + n_ingress_map, + &new_ingress_map, + &new_n_ingress_map); + + _vlan_change_vlan_qos_mapping_create(FALSE, + egress_reset_all, + lnk ? lnk->egress_qos_map : NULL, + lnk ? lnk->n_egress_qos_map : 0, + egress_map, + n_egress_map, + &new_egress_map, + &new_n_egress_map); + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg + || !_nl_msg_new_link_set_linkinfo_vlan(nlmsg, + -1, + flags_mask, + flags_set, + new_ingress_map, + new_n_ingress_map, + new_egress_map, + new_n_egress_map)) + g_return_val_if_reached(FALSE); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +} + +static gboolean +link_enslave(NMPlatform *platform, int master, int slave) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int ifindex = slave; + + nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL); + if (!nlmsg) + return FALSE; + + NLA_PUT_U32(nlmsg, IFLA_MASTER, master); + + return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0); +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +link_release(NMPlatform *platform, int master, int slave) +{ + return link_enslave(platform, 0, slave); +} + +/*****************************************************************************/ + +static gboolean +_infiniband_partition_action(NMPlatform * platform, + InfinibandAction action, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + nm_auto_close int dirfd = -1; + char ifname_parent[IFNAMSIZ]; + const NMPObject * obj; + char id[20]; + char name[IFNAMSIZ]; + gboolean success; + + nm_assert(NM_IN_SET(action, INFINIBAND_ACTION_CREATE_CHILD, INFINIBAND_ACTION_DELETE_CHILD)); + nm_assert(p_key > 0 && p_key <= 0xffff && p_key != 0x8000); + + dirfd = nm_platform_sysctl_open_netdir(platform, parent, ifname_parent); + if (dirfd < 0) { + errno = ENOENT; + return FALSE; + } + + nm_sprintf_buf(id, "0x%04x", p_key); + if (action == INFINIBAND_ACTION_CREATE_CHILD) + success = + nm_platform_sysctl_set(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "create_child"), + id); + else + success = + nm_platform_sysctl_set(platform, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "delete_child"), + id); + + if (!success) { + if (action == INFINIBAND_ACTION_DELETE_CHILD && errno == ENODEV) + return TRUE; + return FALSE; + } + + nmp_utils_new_infiniband_name(name, ifname_parent, p_key); + do_request_link(platform, 0, name); + + if (action == INFINIBAND_ACTION_DELETE_CHILD) + return TRUE; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + name, + FALSE, + NM_LINK_TYPE_INFINIBAND, + NULL, + NULL); + if (out_link) + *out_link = obj ? &obj->link : NULL; + return !!obj; +} + +static gboolean +infiniband_partition_add(NMPlatform * platform, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + return _infiniband_partition_action(platform, + INFINIBAND_ACTION_CREATE_CHILD, + parent, + p_key, + out_link); +} + +static gboolean +infiniband_partition_delete(NMPlatform *platform, int parent, int p_key) +{ + return _infiniband_partition_action(platform, + INFINIBAND_ACTION_DELETE_CHILD, + parent, + p_key, + NULL); +} + +/*****************************************************************************/ + +static GObject * +get_ext_data(NMPlatform *platform, int ifindex) +{ + const NMPObject *obj; + + obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex); + if (!obj) + return NULL; + + return obj->_link.ext_data; +} + +/*****************************************************************************/ + +#define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \ + nm_auto_pop_netns NMPNetns *netns = NULL; \ + NMWifiUtils * wifi_data; \ + if (!nm_platform_netns_push(platform, &netns)) \ + return retval; \ + wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex)); \ + if (!wifi_data) \ + return retval; + +static gboolean +wifi_get_capabilities(NMPlatform *platform, int ifindex, _NMDeviceWifiCapabilities *caps) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + if (caps) + *caps = nm_wifi_utils_get_caps(wifi_data); + return TRUE; +} + +static guint32 +wifi_get_frequency(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_get_freq(wifi_data); +} + +static gboolean +wifi_get_station(NMPlatform * platform, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_get_station(wifi_data, out_bssid, out_quality, out_rate); +} + +static _NM80211Mode +wifi_get_mode(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, _NM_802_11_MODE_UNKNOWN); + return nm_wifi_utils_get_mode(wifi_data); +} + +static void +wifi_set_mode(NMPlatform *platform, int ifindex, _NM80211Mode mode) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_set_mode(wifi_data, mode); +} + +static void +wifi_set_powersave(NMPlatform *platform, int ifindex, guint32 powersave) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_set_powersave(wifi_data, powersave); +} + +static guint32 +wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_find_freq(wifi_data, freqs); +} + +static void +wifi_indicate_addressing_running(NMPlatform *platform, int ifindex, gboolean running) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, ); + nm_wifi_utils_indicate_addressing_running(wifi_data, running); +} + +static _NMSettingWirelessWakeOnWLan +wifi_get_wake_on_wlan(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_get_wake_on_wlan(wifi_data); +} + +static gboolean +wifi_set_wake_on_wlan(NMPlatform *platform, int ifindex, _NMSettingWirelessWakeOnWLan wowl) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_wake_on_wlan(wifi_data, wowl); +} + +/*****************************************************************************/ + +static gboolean +link_can_assume(NMPlatform *platform, int ifindex) +{ + NMPLookup lookup; + const NMPObject *link, *o; + NMDedupMultiIter iter; + NMPCache * cache = nm_platform_get_cache(platform); + + if (ifindex <= 0) + return FALSE; + + link = nm_platform_link_get_obj(platform, ifindex, TRUE); + if (!link) + return FALSE; + + if (!NM_FLAGS_HAS(link->link.n_ifi_flags, IFF_UP)) + return FALSE; + + if (link->link.master > 0) + return TRUE; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex); + if (nmp_cache_lookup(cache, &lookup)) + return TRUE; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); + nmp_cache_iter_for_each (&iter, nmp_cache_lookup(cache, &lookup), &o) { + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP6_ADDRESS); + if (!IN6_IS_ADDR_LINKLOCAL(&o->ip6_address.address)) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static guint32 +mesh_get_channel(NMPlatform *platform, int ifindex) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0); + return nm_wifi_utils_get_mesh_channel(wifi_data); +} + +static gboolean +mesh_set_channel(NMPlatform *platform, int ifindex, guint32 channel) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_mesh_channel(wifi_data, channel); +} + +static gboolean +mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) +{ + WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE); + return nm_wifi_utils_set_mesh_ssid(wifi_data, ssid, len); +} + +/*****************************************************************************/ + +#define WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, retval) \ + NMWpanUtils *wpan_data = NM_WPAN_UTILS(get_ext_data(platform, ifindex)); \ + if (!wpan_data) \ + return retval; + +static guint16 +wpan_get_pan_id(NMPlatform *platform, int ifindex) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16); + return nm_wpan_utils_get_pan_id(wpan_data); +} + +static gboolean +wpan_set_pan_id(NMPlatform *platform, int ifindex, guint16 pan_id) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_pan_id(wpan_data, pan_id); +} + +static guint16 +wpan_get_short_addr(NMPlatform *platform, int ifindex) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16); + return nm_wpan_utils_get_short_addr(wpan_data); +} + +static gboolean +wpan_set_short_addr(NMPlatform *platform, int ifindex, guint16 short_addr) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_short_addr(wpan_data, short_addr); +} + +static gboolean +wpan_set_channel(NMPlatform *platform, int ifindex, guint8 page, guint8 channel) +{ + WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE); + return nm_wpan_utils_set_channel(wpan_data, page, channel); +} + +/*****************************************************************************/ + +static gboolean +link_get_wake_on_lan(NMPlatform *platform, int ifindex) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMLinkType type = nm_platform_link_get_type(platform, ifindex); + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + if (type == NM_LINK_TYPE_ETHERNET) + return nmp_utils_ethtool_get_wake_on_lan(ifindex); + else if (type == NM_LINK_TYPE_WIFI) { + NMWifiUtils *wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex)); + + if (!wifi_data) + return FALSE; + + return !NM_IN_SET(nm_wifi_utils_get_wake_on_wlan(wifi_data), + _NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE, + _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE); + + } else + return FALSE; +} + +static gboolean +link_get_driver_info(NMPlatform *platform, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMPUtilsEthtoolDriverInfo driver_info; + + if (!nm_platform_netns_push(platform, &netns)) + return FALSE; + + if (!nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) + return FALSE; + NM_SET_OUT(out_driver_name, g_strdup(driver_info.driver)); + NM_SET_OUT(out_driver_version, g_strdup(driver_info.version)); + NM_SET_OUT(out_fw_version, g_strdup(driver_info.fw_version)); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +ip4_address_add(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_addr, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_address(RTM_NEWADDR, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET, + ifindex, + &addr, + plen, + &peer_addr, + flags, + nm_utils_ip4_address_is_link_local(addr) ? RT_SCOPE_LINK + : RT_SCOPE_UNIVERSE, + lifetime, + preferred, + broadcast_address, + label); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_addr); + return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0); +} + +static gboolean +ip6_address_add(NMPlatform * platform, + int ifindex, + struct in6_addr addr, + guint8 plen, + struct in6_addr peer_addr, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_address(RTM_NEWADDR, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET6, + ifindex, + &addr, + plen, + IN6_IS_ADDR_UNSPECIFIED(&peer_addr) ? NULL : &peer_addr, + flags, + RT_SCOPE_UNIVERSE, + lifetime, + preferred, + 0, + NULL); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr); + return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0); +} + +static gboolean +ip4_address_delete(NMPlatform *platform, + int ifindex, + in_addr_t addr, + guint8 plen, + in_addr_t peer_address) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + + nlmsg = _nl_msg_new_address(RTM_DELADDR, + 0, + AF_INET, + ifindex, + &addr, + plen, + &peer_address, + 0, + RT_SCOPE_NOWHERE, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0, + NULL); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_address); + return do_delete_object(platform, &obj_id, nlmsg); +} + +static gboolean +ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; + + nlmsg = _nl_msg_new_address(RTM_DELADDR, + 0, + AF_INET6, + ifindex, + &addr, + plen, + NULL, + 0, + RT_SCOPE_NOWHERE, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0, + NULL); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr); + return do_delete_object(platform, &obj_id, nlmsg); +} + +/*****************************************************************************/ + +static int +ip_route_add(NMPlatform * platform, + NMPNlmFlags flags, + int addr_family, + const NMPlatformIPRoute *route) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj; + + nmp_object_stackinit(&obj, + NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), + (const NMPlatformObject *) route); + + nm_platform_ip_route_normalize(addr_family, NMP_OBJECT_CAST_IP_ROUTE(&obj)); + + nlmsg = _nl_msg_new_route(RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + return do_add_addrroute(platform, + &obj, + nlmsg, + NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE)); +} + +static gboolean +object_delete(NMPlatform *platform, const NMPObject *obj) +{ + nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL; + nm_auto_nlmsg struct nl_msg * nlmsg = NULL; + + if (!NMP_OBJECT_IS_STACKINIT(obj)) + obj_keep_alive = nmp_object_ref(obj); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + nlmsg = _nl_msg_new_route(RTM_DELROUTE, 0, obj); + break; + case NMP_OBJECT_TYPE_ROUTING_RULE: + nlmsg = _nl_msg_new_routing_rule(RTM_DELRULE, 0, NMP_OBJECT_CAST_ROUTING_RULE(obj)); + break; + case NMP_OBJECT_TYPE_QDISC: + nlmsg = _nl_msg_new_qdisc(RTM_DELQDISC, 0, NMP_OBJECT_CAST_QDISC(obj)); + break; + case NMP_OBJECT_TYPE_TFILTER: + nlmsg = _nl_msg_new_tfilter(RTM_DELTFILTER, 0, NMP_OBJECT_CAST_TFILTER(obj)); + break; + default: + break; + } + + if (!nlmsg) + g_return_val_if_reached(FALSE); + return do_delete_object(platform, obj, nlmsg); +} + +/*****************************************************************************/ + +static int +ip_route_get(NMPlatform * platform, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route) +{ + const gboolean is_v4 = (addr_family == AF_INET); + const int addr_len = is_v4 ? 4 : 16; + int try_count = 0; + WaitForNlResponseResult seq_result; + int nle; + nm_auto_nmpobj NMPObject *route = NULL; + + nm_assert(NM_IS_LINUX_PLATFORM(platform)); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + nm_assert(address); + + do { + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[64]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETROUTE, + .r.rtm_family = addr_family, + .r.rtm_tos = 0, + .r.rtm_dst_len = is_v4 ? 32 : 128, + .r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */, + }; + + nm_clear_pointer(&route, nmp_object_unref); + + if (!_nl_addattr_l(&req.n, sizeof(req), RTA_DST, address, addr_len)) + nm_assert_not_reached(); + + if (oif_ifindex > 0) { + gint32 ii = oif_ifindex; + + if (!_nl_addattr_l(&req.n, sizeof(req), RTA_OIF, &ii, sizeof(ii))) + nm_assert_not_reached(); + } + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + nle = _nl_send_nlmsghdr(platform, + &req.n, + &seq_result, + NULL, + DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, + &route); + if (nle < 0) { + _LOGE("get-route: failure sending netlink request \"%s\" (%d)", + nm_strerror_native(-nle), + -nle); + return -NME_UNSPEC; + } + + delayed_action_handle_all(platform, FALSE); + + /* Retry, if we failed due to a cache resync. That can happen when the netlink + * socket fills up and we lost the response. */ + } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC && ++try_count < 10); + + if (seq_result < 0) { + /* negative seq_result is an errno from kernel. Map it to negative + * int (which are also errno). */ + return (int) seq_result; + } + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) { + if (route) { + NM_SET_OUT(out_route, g_steal_pointer(&route)); + return 0; + } + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN; + } + + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char * errmsg = NULL; + char s_buf[256]; + int nle; + + msg = _nl_msg_new_routing_rule(RTM_NEWRULE, flags, routing_rule); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-rule: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return seq_result; + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind + * is undefined. */ + + msg = _nl_msg_new_qdisc(RTM_NEWQDISC, flags, qdisc); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-qdisc: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + if (seq_result < 0) + return seq_result; + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static int +tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tfilter) +{ + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char * errmsg = NULL; + int nle; + char s_buf[256]; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind + * and tfilter.action.kind is undefined. */ + + msg = _nl_msg_new_tfilter(RTM_NEWTFILTER, flags, tfilter); + + event_handler_read_netlink(platform, FALSE); + + nle = _nl_send_nlmsg(platform, + msg, + &seq_result, + &errmsg, + DELAYED_ACTION_RESPONSE_TYPE_VOID, + NULL); + if (nle < 0) { + _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle); + return -NME_PL_NETLINK; + } + + delayed_action_handle_all(platform, FALSE); + + nm_assert(seq_result); + + _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN, + "do-add-tfilter: %s", + wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf))); + + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + return 0; + + return -NME_UNSPEC; +} + +/*****************************************************************************/ + +static gboolean +event_handler(int fd, GIOCondition io_condition, gpointer user_data) +{ + delayed_action_handle_all(NM_PLATFORM(user_data), TRUE); + return TRUE; +} + +/*****************************************************************************/ + +/* copied from libnl3's recvmsgs() */ +static int +event_handler_recvmsgs(NMPlatform *platform, gboolean handle_events) +{ + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + struct nl_sock * sk = priv->nlh; + int n; + int err = 0; + gboolean multipart = 0; + gboolean interrupted = FALSE; + struct nlmsghdr * hdr; + WaitForNlResponseResult seq_result; + struct sockaddr_nl nla = {0}; + struct ucred creds; + gboolean creds_has; + nm_auto_free unsigned char *buf = NULL; + +continue_reading: + nm_clear_pointer(&buf, free); + n = nl_recv(sk, &nla, &buf, &creds, &creds_has); + + if (n <= 0) { + if (n == -NME_NL_MSG_TRUNC) { + int buf_size; + + /* the message receive buffer was too small. We lost one message, which + * is unfortunate. Try to double the buffer size for the next time. */ + buf_size = nl_socket_get_msg_buf_size(sk); + if (buf_size < 512 * 1024) { + buf_size *= 2; + _LOGT("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", + buf_size); + if (nl_socket_set_msg_buf_size(sk, buf_size) < 0) + nm_assert_not_reached(); + if (!handle_events) + goto continue_reading; + } + } + + return n; + } + + hdr = (struct nlmsghdr *) buf; + while (nlmsg_ok(hdr, n)) { + nm_auto_nlmsg struct nl_msg *msg = NULL; + gboolean abort_parsing = FALSE; + gboolean process_valid_msg = FALSE; + guint32 seq_number; + char buf_nlmsghdr[400]; + const char * extack_msg = NULL; + + msg = nlmsg_alloc_convert(hdr); + + nlmsg_set_proto(msg, NETLINK_ROUTE); + nlmsg_set_src(msg, &nla); + + if (!creds_has || creds.pid) { + if (!creds_has) + _LOGT("netlink: recvmsg: received message without credentials"); + else + _LOGT("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid); + err = 0; + goto stop; + } + + _LOGt("netlink: recvmsg: new message %s", + nl_nlmsghdr_to_str(hdr, buf_nlmsghdr, sizeof(buf_nlmsghdr))); + + nlmsg_set_creds(msg, &creds); + + if (hdr->nlmsg_flags & NLM_F_MULTI) + multipart = TRUE; + + if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) { + /* + * We have to continue reading to clear + * all messages until a NLMSG_DONE is + * received and report the inconsistency. + */ + interrupted = TRUE; + } + + /* Other side wishes to see an ack for this message */ + if (hdr->nlmsg_flags & NLM_F_ACK) { + /* FIXME: implement */ + } + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN; + + if (hdr->nlmsg_type == NLMSG_DONE) { + /* messages terminates a multipart message, this is + * usually the end of a message and therefore we slip + * out of the loop by default. the user may overrule + * this action by skipping this packet. */ + multipart = FALSE; + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } else if (hdr->nlmsg_type == NLMSG_NOOP) { + /* Message to be ignored, the default action is to + * skip this message if no callback is specified. The + * user may overrule this action by returning + * NL_PROCEED. */ + } else if (hdr->nlmsg_type == NLMSG_OVERRUN) { + /* Data got lost, report back to user. The default action is to + * quit parsing. The user may overrule this action by returning + * NL_SKIP or NL_PROCEED (dangerous) */ + err = -NME_NL_MSG_OVERFLOW; + abort_parsing = TRUE; + } else if (hdr->nlmsg_type == NLMSG_ERROR) { + /* Message carries a nlmsgerr */ + struct nlmsgerr *e = nlmsg_data(hdr); + + if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) { + /* Truncated error message, the default action + * is to stop parsing. The user may overrule + * this action by returning NL_SKIP or + * NL_PROCEED (dangerous) */ + err = -NME_NL_MSG_TRUNC; + abort_parsing = TRUE; + } else if (e->error) { + int errsv = nm_errno_native(e->error); + + if (NM_FLAGS_HAS(hdr->nlmsg_flags, NLM_F_ACK_TLVS) + && hdr->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) { + static const struct nla_policy policy[] = { + [NLMSGERR_ATTR_MSG] = {.type = NLA_STRING}, + [NLMSGERR_ATTR_OFFS] = {.type = NLA_U32}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + struct nlattr *tlvs; + + tlvs = (struct nlattr *) ((char *) e + sizeof(*e) + e->msg.nlmsg_len + - NLMSG_HDRLEN); + if (nla_parse_arr(tb, + tlvs, + hdr->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len, + policy) + >= 0) { + if (tb[NLMSGERR_ATTR_MSG]) + extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]); + } + } + + /* Error message reported back from kernel. */ + _LOGD("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d", + nm_strerror_native(errsv), + errsv, + NM_PRINT_FMT_QUOTED(extack_msg, " \"", extack_msg, "\"", ""), + nlmsg_hdr(msg)->nlmsg_seq); + seq_result = -NM_ERRNO_NATIVE(errsv); + } else + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } else + process_valid_msg = TRUE; + + seq_number = nlmsg_hdr(msg)->nlmsg_seq; + + /* check whether the seq number is different from before, and + * whether the previous number (@nlh_seq_last_seen) is a pending + * refresh-all request. In that case, the pending request is thereby + * completed. + * + * We must do that before processing the message with event_valid_msg(), + * because we must track the completion of the pending request before that. */ + event_seq_check_refresh_all(platform, seq_number); + + if (process_valid_msg) { + /* Valid message (not checking for MULTIPART bit to + * get along with broken kernels. NL_SKIP has no + * effect on this. */ + + event_valid_msg(platform, msg, handle_events); + + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } + + event_seq_check(platform, seq_number, seq_result, extack_msg); + + if (abort_parsing) + goto stop; + + err = 0; + hdr = nlmsg_next(hdr, &n); + } + + if (multipart) { + /* Multipart message not yet complete, continue reading */ + goto continue_reading; + } +stop: + if (!handle_events) { + /* when we don't handle events, we want to drain all messages from the socket + * without handling the messages (but still check for sequence numbers). + * Repeat reading. */ + goto continue_reading; + } + + if (interrupted) + return -NME_NL_DUMP_INTR; + return err; +} + +/*****************************************************************************/ + +static gboolean +event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMLinuxPlatformPrivate * priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int r; + struct pollfd pfd; + gboolean any = FALSE; + int timeout_msec; + struct { + guint32 seq_number; + gint64 timeout_abs_ns; + gint64 now_ns; + } next; + + if (!nm_platform_netns_push(platform, &netns)) { + delayed_action_wait_for_nl_response_complete_all(platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS); + return FALSE; + } + + for (;;) { + for (;;) { + int nle; + + nle = event_handler_recvmsgs(platform, TRUE); + + if (nle < 0) { + switch (nle) { + case -EAGAIN: + goto after_read; + case -NME_NL_DUMP_INTR: + _LOGD("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", + nm_strerror(nle), + nle); + break; + case -NME_NL_MSG_TRUNC: + case -ENOBUFS: + _LOGI("netlink: read: %s. Need to resynchronize platform cache", ({ + const char *_reason = "unknown"; + switch (nle) { + case -NME_NL_MSG_TRUNC: + _reason = "message truncated"; + break; + case -ENOBUFS: + _reason = "too many netlink events"; + break; + } + _reason; + })); + event_handler_recvmsgs(platform, FALSE); + delayed_action_wait_for_nl_response_complete_all( + platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC); + + delayed_action_schedule(platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS + | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + break; + default: + _LOGE("netlink: read: failed to retrieve incoming events: %s (%d)", + nm_strerror(nle), + nle); + break; + } + } + any = TRUE; + } + +after_read: + + if (!NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) + return any; + + delayed_action_wait_for_nl_response_complete_check(platform, + WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN, + &next.seq_number, + &next.timeout_abs_ns, + &next.now_ns); + + if (!wait_for_acks + || !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) + return any; + + nm_assert(next.seq_number); + nm_assert(next.now_ns > 0); + nm_assert(next.timeout_abs_ns > next.now_ns); + + _LOGT("netlink: read: wait for ACK for sequence number %u...", next.seq_number); + + timeout_msec = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NSEC_PER_SEC / 1000); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = nl_socket_get_fd(priv->nlh); + pfd.events = POLLIN; + r = poll(&pfd, 1, MAX(1, timeout_msec)); + + if (r == 0) { + /* timeout and there is nothing to read. */ + goto after_read; + } + + if (r < 0) { + int errsv = errno; + + if (errsv != EINTR) { + _LOGE("netlink: read: poll failed with %s", nm_strerror_native(errsv)); + delayed_action_wait_for_nl_response_complete_all( + platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL); + return any; + } + /* Continue to read again, even if there might be nothing to read after EINTR. */ + } + } +} + +/*****************************************************************************/ + +static void +cache_update_link_udev(NMPlatform *platform, int ifindex, struct udev_device *udevice) +{ + nm_auto_nmpobj const NMPObject *obj_old = NULL; + nm_auto_nmpobj const NMPObject *obj_new = NULL; + NMPCacheOpsType cache_op; + + cache_op = nmp_cache_update_link_udev(nm_platform_get_cache(platform), + ifindex, + udevice, + &obj_old, + &obj_new); + + if (cache_op != NMP_CACHE_OPS_UNCHANGED) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + cache_on_change(platform, cache_op, obj_old, obj_new); + if (!nm_platform_netns_push(platform, &netns)) + return; + nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new); + } +} + +static void +udev_device_added(NMPlatform *platform, struct udev_device *udevice) +{ + const char *ifname; + const char *ifindex_s; + int ifindex; + + ifname = udev_device_get_sysname(udevice); + if (!ifname) { + _LOGD("udev-add: failed to get device's interface"); + return; + } + + ifindex_s = udev_device_get_property_value(udevice, "IFINDEX"); + if (!ifindex_s) { + _LOGW("udev-add[%s]failed to get device's ifindex", ifname); + return; + } + ifindex = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0); + if (ifindex <= 0) { + _LOGW("udev-add[%s]: retrieved invalid IFINDEX=%d", ifname, ifindex); + return; + } + + if (!udev_device_get_syspath(udevice)) { + _LOGD("udev-add[%s,%d]: couldn't determine device path; ignoring...", ifname, ifindex); + return; + } + + _LOGT("udev-add[%s,%d]: device added", ifname, ifindex); + cache_update_link_udev(platform, ifindex, udevice); +} + +static gboolean +_udev_device_removed_match_link(const NMPObject *obj, gpointer udevice) +{ + return obj->_link.udev.device == udevice; +} + +static void +udev_device_removed(NMPlatform *platform, struct udev_device *udevice) +{ + const char *ifindex_s; + int ifindex = 0; + + ifindex_s = udev_device_get_property_value(udevice, "IFINDEX"); + ifindex = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0); + if (ifindex <= 0) { + const NMPObject *obj; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform), + 0, + NULL, + FALSE, + NM_LINK_TYPE_NONE, + _udev_device_removed_match_link, + udevice); + if (obj) + ifindex = obj->link.ifindex; + } + + _LOGD("udev-remove: IFINDEX=%d", ifindex); + if (ifindex <= 0) + return; + + cache_update_link_udev(platform, ifindex, NULL); +} + +static void +handle_udev_event(NMUdevClient *udev_client, struct udev_device *udevice, gpointer user_data) +{ + nm_auto_pop_netns NMPNetns *netns = NULL; + NMPlatform * platform = NM_PLATFORM(user_data); + const char * subsys; + const char * ifindex; + guint64 seqnum; + const char * action; + + action = udev_device_get_action(udevice); + g_return_if_fail(action); + + subsys = udev_device_get_subsystem(udevice); + g_return_if_fail(nm_streq0(subsys, "net")); + + if (!nm_platform_netns_push(platform, &netns)) + return; + + ifindex = udev_device_get_property_value(udevice, "IFINDEX"); + seqnum = udev_device_get_seqnum(udevice); + _LOGD("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, + action, + subsys, + udev_device_get_sysname(udevice), + ifindex ?: "unknown", + seqnum); + + if (NM_IN_STRSET(action, "add", "move")) + udev_device_added(platform, udevice); + else if (NM_IN_STRSET(action, "remove")) + udev_device_removed(platform, udevice); +} + +/*****************************************************************************/ + +static void +nm_linux_platform_init(NMLinuxPlatform *self) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(self); + + c_list_init(&priv->sysctl_clear_cache_lst); + c_list_init(&priv->sysctl_list); + + priv->delayed_action.list_master_connected = g_ptr_array_new(); + priv->delayed_action.list_refresh_link = g_ptr_array_new(); + priv->delayed_action.list_wait_for_nl_response = + g_array_new(FALSE, TRUE, sizeof(DelayedActionWaitForNlResponseData)); +} + +static void +constructed(GObject *_object) +{ + NMPlatform * platform = NM_PLATFORM(_object); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + int nle; + int fd; + + nm_assert(!platform->_netns || platform->_netns == nmp_netns_get_current()); + + if (nm_platform_get_use_udev(platform)) { + priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("net"), handle_udev_event, platform); + } + + _LOGD("create (%s netns, %s, %s udev)", + !platform->_netns ? "ignore" : "use", + !platform->_netns && nmp_netns_is_initial() + ? "initial netns" + : (!nmp_netns_get_current() + ? "no netns support" + : nm_sprintf_bufa(100, + "in netns[%p]%s", + nmp_netns_get_current(), + nmp_netns_get_current() == nmp_netns_get_initial() ? "/main" + : "")), + nm_platform_get_use_udev(platform) ? "use" : "no"); + + priv->genl = nl_socket_alloc(); + g_assert(priv->genl); + + nle = nl_connect(priv->genl, NETLINK_GENERIC); + if (nle) { + _LOGE("unable to connect the generic netlink socket \"%s\" (%d)", nm_strerror(nle), -nle); + nl_socket_free(priv->genl); + priv->genl = NULL; + } + + priv->nlh = nl_socket_alloc(); + g_assert(priv->nlh); + + nle = nl_connect(priv->nlh, NETLINK_ROUTE); + g_assert(!nle); + nle = nl_socket_set_passcred(priv->nlh, 1); + g_assert(!nle); + + /* No blocking for event socket, so that we can drain it safely. */ + nle = nl_socket_set_nonblocking(priv->nlh); + g_assert(!nle); + + /* use 8 MB for receive socket kernel queue. */ + nle = nl_socket_set_buffer_size(priv->nlh, 8 * 1024 * 1024, 0); + g_assert(!nle); + + nle = nl_socket_set_ext_ack(priv->nlh, TRUE); + if (nle) + _LOGD("could not enable extended acks on netlink socket"); + + /* explicitly set the msg buffer size and disable MSG_PEEK. + * If we later encounter NME_NL_MSG_TRUNC, we will adjust the buffer size. */ + nl_socket_disable_msg_peek(priv->nlh); + nle = nl_socket_set_msg_buf_size(priv->nlh, 32 * 1024); + g_assert(!nle); + + nle = nl_socket_add_memberships(priv->nlh, + RTNLGRP_IPV4_IFADDR, + RTNLGRP_IPV4_ROUTE, + RTNLGRP_IPV4_RULE, + RTNLGRP_IPV6_RULE, + RTNLGRP_IPV6_IFADDR, + RTNLGRP_IPV6_ROUTE, + RTNLGRP_LINK, + RTNLGRP_TC, + 0); + g_assert(!nle); + + fd = nl_socket_get_fd(priv->nlh); + + _LOGD("Netlink socket for events established: port=%u, fd=%d", + nl_socket_get_local_port(priv->nlh), + fd); + + priv->event_source = + nm_g_unix_fd_source_new(fd, + G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + event_handler, + platform, + NULL); + g_source_attach(priv->event_source, NULL); + + /* complete construction of the GObject instance before populating the cache. */ + G_OBJECT_CLASS(nm_linux_platform_parent_class)->constructed(_object); + + _LOGD("populate platform cache"); + delayed_action_schedule( + platform, + DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES + | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL + | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, + NULL); + + delayed_action_handle_all(platform, FALSE); + + /* Set up udev monitoring */ + if (priv->udev_client) { + struct udev_enumerate * enumerator; + struct udev_list_entry *devices, *l; + + /* And read initial device list */ + enumerator = nm_udev_client_enumerate_new(priv->udev_client); + + udev_enumerate_add_match_is_initialized(enumerator); + + udev_enumerate_scan_devices(enumerator); + + devices = udev_enumerate_get_list_entry(enumerator); + for (l = devices; l; l = udev_list_entry_get_next(l)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), + udev_list_entry_get_name(l)); + if (!udevice) + continue; + + udev_device_added(platform, udevice); + udev_device_unref(udevice); + } + + udev_enumerate_unref(enumerator); + } +} + +/* Similar to systemd's path_is_read_only_fs(), at + * https://github.com/systemd/systemd/blob/v246/src/basic/stat-util.c#L132 */ +static int +path_is_read_only_fs(const char *path) +{ + struct statvfs st; + + if (statvfs(path, &st) < 0) + return -errno; + + if (st.f_flag & ST_RDONLY) + return TRUE; + + /* On NFS, statvfs() might not reflect whether we can actually + * write to the remote share. Let's try again with + * access(W_OK) which is more reliable, at least sometimes. */ + if (access(path, W_OK) < 0 && errno == EROFS) + return TRUE; + + return FALSE; +} + +NMPlatform * +nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support) +{ + gboolean use_udev = FALSE; + + if (nmp_netns_is_initial() && path_is_read_only_fs("/sys") == FALSE) + use_udev = TRUE; + + return g_object_new(NM_TYPE_LINUX_PLATFORM, + NM_PLATFORM_LOG_WITH_PTR, + log_with_ptr, + NM_PLATFORM_USE_UDEV, + use_udev, + NM_PLATFORM_NETNS_SUPPORT, + netns_support, + NULL); +} + +static void +dispose(GObject *object) +{ + NMPlatform * platform = NM_PLATFORM(object); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + + _LOGD("dispose"); + + delayed_action_wait_for_nl_response_complete_all(platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING); + + priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE; + g_ptr_array_set_size(priv->delayed_action.list_master_connected, 0); + g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0); + + G_OBJECT_CLASS(nm_linux_platform_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(object); + + g_ptr_array_unref(priv->delayed_action.list_master_connected); + g_ptr_array_unref(priv->delayed_action.list_refresh_link); + g_array_unref(priv->delayed_action.list_wait_for_nl_response); + + nl_socket_free(priv->genl); + + nm_clear_g_source_inst(&priv->event_source); + + nl_socket_free(priv->nlh); + + { + NM_G_MUTEX_LOCKED(&sysctl_clear_cache_lock); + + if (priv->sysctl_get_prev_values) { + c_list_unlink(&priv->sysctl_clear_cache_lst); + g_hash_table_destroy(priv->sysctl_get_prev_values); + } + + nm_assert(c_list_is_empty(&priv->sysctl_clear_cache_lst)); + nm_assert(c_list_is_empty(&priv->sysctl_list)); + } + + priv->udev_client = nm_udev_client_destroy(priv->udev_client); + + G_OBJECT_CLASS(nm_linux_platform_parent_class)->finalize(object); +} + +static void +nm_linux_platform_class_init(NMLinuxPlatformClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMPlatformClass *platform_class = NM_PLATFORM_CLASS(klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + platform_class->sysctl_set = sysctl_set; + platform_class->sysctl_set_async = sysctl_set_async; + platform_class->sysctl_get = sysctl_get; + + platform_class->link_add = link_add; + platform_class->link_delete = link_delete; + + platform_class->link_refresh = link_refresh; + + platform_class->link_set_netns = link_set_netns; + + platform_class->link_change_flags = link_change_flags; + + platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled; + platform_class->link_set_token = link_set_token; + + platform_class->link_set_address = link_set_address; + platform_class->link_get_permanent_address = link_get_permanent_address; + platform_class->link_set_mtu = link_set_mtu; + platform_class->link_set_name = link_set_name; + platform_class->link_set_sriov_params_async = link_set_sriov_params_async; + platform_class->link_set_sriov_vfs = link_set_sriov_vfs; + platform_class->link_set_bridge_vlans = link_set_bridge_vlans; + + platform_class->link_get_physical_port_id = link_get_physical_port_id; + platform_class->link_get_dev_id = link_get_dev_id; + platform_class->link_get_wake_on_lan = link_get_wake_on_lan; + platform_class->link_get_driver_info = link_get_driver_info; + + platform_class->link_supports_carrier_detect = link_supports_carrier_detect; + platform_class->link_supports_vlans = link_supports_vlans; + platform_class->link_supports_sriov = link_supports_sriov; + + platform_class->link_enslave = link_enslave; + platform_class->link_release = link_release; + + platform_class->link_can_assume = link_can_assume; + + platform_class->link_vlan_change = link_vlan_change; + platform_class->link_wireguard_change = link_wireguard_change; + + platform_class->infiniband_partition_add = infiniband_partition_add; + platform_class->infiniband_partition_delete = infiniband_partition_delete; + + platform_class->wifi_get_capabilities = wifi_get_capabilities; + platform_class->wifi_get_frequency = wifi_get_frequency; + platform_class->wifi_get_station = wifi_get_station; + platform_class->wifi_get_mode = wifi_get_mode; + platform_class->wifi_set_mode = wifi_set_mode; + platform_class->wifi_set_powersave = wifi_set_powersave; + platform_class->wifi_find_frequency = wifi_find_frequency; + platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running; + platform_class->wifi_get_wake_on_wlan = wifi_get_wake_on_wlan; + platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan; + + platform_class->mesh_get_channel = mesh_get_channel; + platform_class->mesh_set_channel = mesh_set_channel; + platform_class->mesh_set_ssid = mesh_set_ssid; + + platform_class->wpan_get_pan_id = wpan_get_pan_id; + platform_class->wpan_set_pan_id = wpan_set_pan_id; + platform_class->wpan_get_short_addr = wpan_get_short_addr; + platform_class->wpan_set_short_addr = wpan_set_short_addr; + platform_class->wpan_set_channel = wpan_set_channel; + + platform_class->link_tun_add = link_tun_add; + + platform_class->object_delete = object_delete; + platform_class->ip4_address_add = ip4_address_add; + platform_class->ip6_address_add = ip6_address_add; + platform_class->ip4_address_delete = ip4_address_delete; + platform_class->ip6_address_delete = ip6_address_delete; + + platform_class->ip_route_add = ip_route_add; + platform_class->ip_route_get = ip_route_get; + + platform_class->routing_rule_add = routing_rule_add; + + platform_class->qdisc_add = qdisc_add; + platform_class->tfilter_add = tfilter_add; + + platform_class->process_events = process_events; +} diff --git a/src/libnm-platform/nm-linux-platform.h b/src/libnm-platform/nm-linux-platform.h new file mode 100644 index 00000000..26c31e3e --- /dev/null +++ b/src/libnm-platform/nm-linux-platform.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_LINUX_PLATFORM_H__ +#define __NETWORKMANAGER_LINUX_PLATFORM_H__ + +#include "nm-platform.h" + +#define NM_TYPE_LINUX_PLATFORM (nm_linux_platform_get_type()) +#define NM_LINUX_PLATFORM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatform)) +#define NM_LINUX_PLATFORM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) +#define NM_IS_LINUX_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LINUX_PLATFORM)) +#define NM_IS_LINUX_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LINUX_PLATFORM)) +#define NM_LINUX_PLATFORM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass)) + +typedef struct _NMLinuxPlatform NMLinuxPlatform; +typedef struct _NMLinuxPlatformClass NMLinuxPlatformClass; + +GType nm_linux_platform_get_type(void); + +NMPlatform *nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support); + +#endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */ diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c new file mode 100644 index 00000000..e92c4dfe --- /dev/null +++ b/src/libnm-platform/nm-netlink.c @@ -0,0 +1,1524 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-netlink.h" + +#include <unistd.h> +#include <fcntl.h> + +/*****************************************************************************/ + +#ifndef SOL_NETLINK + #define SOL_NETLINK 270 +#endif + +/*****************************************************************************/ + +#define NL_SOCK_PASSCRED (1 << 1) +#define NL_MSG_PEEK (1 << 3) +#define NL_MSG_PEEK_EXPLICIT (1 << 4) +#define NL_NO_AUTO_ACK (1 << 5) + +#ifndef NETLINK_EXT_ACK + #define NETLINK_EXT_ACK 11 +#endif + +struct nl_msg { + int nm_protocol; + struct sockaddr_nl nm_src; + struct sockaddr_nl nm_dst; + struct ucred nm_creds; + struct nlmsghdr * nm_nlh; + size_t nm_size; + bool nm_creds_has : 1; +}; + +struct nl_sock { + struct sockaddr_nl s_local; + struct sockaddr_nl s_peer; + int s_fd; + int s_proto; + unsigned int s_seq_next; + unsigned int s_seq_expect; + int s_flags; + size_t s_bufsize; +}; + +/*****************************************************************************/ + +NM_UTILS_ENUM2STR_DEFINE(nl_nlmsgtype2str, + int, + NM_UTILS_ENUM2STR(NLMSG_NOOP, "NOOP"), + NM_UTILS_ENUM2STR(NLMSG_ERROR, "ERROR"), + NM_UTILS_ENUM2STR(NLMSG_DONE, "DONE"), + NM_UTILS_ENUM2STR(NLMSG_OVERRUN, "OVERRUN"), ); + +NM_UTILS_FLAGS2STR_DEFINE(nl_nlmsg_flags2str, + int, + NM_UTILS_FLAGS2STR(NLM_F_REQUEST, "REQUEST"), + NM_UTILS_FLAGS2STR(NLM_F_MULTI, "MULTI"), + NM_UTILS_FLAGS2STR(NLM_F_ACK, "ACK"), + NM_UTILS_FLAGS2STR(NLM_F_ECHO, "ECHO"), + NM_UTILS_FLAGS2STR(NLM_F_ROOT, "ROOT"), + NM_UTILS_FLAGS2STR(NLM_F_MATCH, "MATCH"), + NM_UTILS_FLAGS2STR(NLM_F_ATOMIC, "ATOMIC"), + NM_UTILS_FLAGS2STR(NLM_F_REPLACE, "REPLACE"), + NM_UTILS_FLAGS2STR(NLM_F_EXCL, "EXCL"), + NM_UTILS_FLAGS2STR(NLM_F_CREATE, "CREATE"), + NM_UTILS_FLAGS2STR(NLM_F_APPEND, "APPEND"), ); + +/*****************************************************************************/ + +const char * +nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len) +{ + const char *b; + const char *s; + guint flags, flags_before; + const char *prefix; + + if (!nm_utils_to_string_buffer_init_null(hdr, &buf, &len)) + return buf; + + b = buf; + + switch (hdr->nlmsg_type) { + case RTM_GETLINK: + s = "RTM_GETLINK"; + break; + case RTM_NEWLINK: + s = "RTM_NEWLINK"; + break; + case RTM_DELLINK: + s = "RTM_DELLINK"; + break; + case RTM_SETLINK: + s = "RTM_SETLINK"; + break; + case RTM_GETADDR: + s = "RTM_GETADDR"; + break; + case RTM_NEWADDR: + s = "RTM_NEWADDR"; + break; + case RTM_DELADDR: + s = "RTM_DELADDR"; + break; + case RTM_GETROUTE: + s = "RTM_GETROUTE"; + break; + case RTM_NEWROUTE: + s = "RTM_NEWROUTE"; + break; + case RTM_DELROUTE: + s = "RTM_DELROUTE"; + break; + case RTM_GETRULE: + s = "RTM_GETRULE"; + break; + case RTM_NEWRULE: + s = "RTM_NEWRULE"; + break; + case RTM_DELRULE: + s = "RTM_DELRULE"; + break; + case RTM_GETQDISC: + s = "RTM_GETQDISC"; + break; + case RTM_NEWQDISC: + s = "RTM_NEWQDISC"; + break; + case RTM_DELQDISC: + s = "RTM_DELQDISC"; + break; + case RTM_GETTFILTER: + s = "RTM_GETTFILTER"; + break; + case RTM_NEWTFILTER: + s = "RTM_NEWTFILTER"; + break; + case RTM_DELTFILTER: + s = "RTM_DELTFILTER"; + break; + case NLMSG_NOOP: + s = "NLMSG_NOOP"; + break; + case NLMSG_ERROR: + s = "NLMSG_ERROR"; + break; + case NLMSG_DONE: + s = "NLMSG_DONE"; + break; + case NLMSG_OVERRUN: + s = "NLMSG_OVERRUN"; + break; + default: + s = NULL; + break; + } + + if (s) + nm_utils_strbuf_append_str(&buf, &len, s); + else + nm_utils_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type); + + flags = hdr->nlmsg_flags; + + if (!flags) { + nm_utils_strbuf_append_str(&buf, &len, ", flags 0"); + goto flags_done; + } + +#define _F(f, n) \ + G_STMT_START \ + { \ + if (NM_FLAGS_ALL(flags, f)) { \ + flags &= ~(f); \ + nm_utils_strbuf_append(&buf, &len, "%s%s", prefix, n); \ + if (!flags) \ + goto flags_done; \ + prefix = ","; \ + } \ + } \ + G_STMT_END + + prefix = ", flags "; + flags_before = flags; + _F(NLM_F_REQUEST, "request"); + _F(NLM_F_MULTI, "multi"); + _F(NLM_F_ACK, "ack"); + _F(NLM_F_ECHO, "echo"); + _F(NLM_F_DUMP_INTR, "dump_intr"); + _F(0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered"); + + if (flags_before != flags) + prefix = ";"; + + switch (hdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_NEWADDR: + case RTM_NEWROUTE: + case RTM_NEWQDISC: + case RTM_NEWTFILTER: + _F(NLM_F_REPLACE, "replace"); + _F(NLM_F_EXCL, "excl"); + _F(NLM_F_CREATE, "create"); + _F(NLM_F_APPEND, "append"); + break; + case RTM_GETLINK: + case RTM_GETADDR: + case RTM_GETROUTE: + case RTM_DELQDISC: + case RTM_DELTFILTER: + _F(NLM_F_DUMP, "dump"); + _F(NLM_F_ROOT, "root"); + _F(NLM_F_MATCH, "match"); + _F(NLM_F_ATOMIC, "atomic"); + break; + } + +#undef _F + + if (flags_before != flags) + prefix = ";"; + nm_utils_strbuf_append(&buf, &len, "%s0x%04x", prefix, flags); + +flags_done: + + nm_utils_strbuf_append(&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq); + + return b; +} + +/*****************************************************************************/ + +struct nlmsghdr * +nlmsg_hdr(struct nl_msg *n) +{ + return n->nm_nlh; +} + +void * +nlmsg_reserve(struct nl_msg *n, size_t len, int pad) +{ + char * buf = (char *) n->nm_nlh; + size_t nlmsg_len = n->nm_nlh->nlmsg_len; + size_t tlen; + + nm_assert(pad >= 0); + + if (len > n->nm_size) + return NULL; + + tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len; + + if ((tlen + nlmsg_len) > n->nm_size) + return NULL; + + buf += nlmsg_len; + n->nm_nlh->nlmsg_len += tlen; + + if (tlen > len) + memset(buf + len, 0, tlen - len); + + return buf; +} + +/*****************************************************************************/ + +struct nlattr * +nla_reserve(struct nl_msg *msg, int attrtype, int attrlen) +{ + struct nlattr *nla; + int tlen; + + if (attrlen < 0) + return NULL; + + tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen); + + if (tlen > msg->nm_size) + return NULL; + + nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh); + nla->nla_type = attrtype; + nla->nla_len = nla_attr_size(attrlen); + + if (attrlen) + memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); + msg->nm_nlh->nlmsg_len = tlen; + + return nla; +} + +/*****************************************************************************/ + +struct nl_msg * +nlmsg_alloc_size(size_t len) +{ + struct nl_msg *nm; + + if (len < sizeof(struct nlmsghdr)) + len = sizeof(struct nlmsghdr); + + nm = g_slice_new(struct nl_msg); + *nm = (struct nl_msg){ + .nm_protocol = -1, + .nm_size = len, + .nm_nlh = g_malloc0(len), + }; + nm->nm_nlh->nlmsg_len = nlmsg_total_size(0); + return nm; +} + +/** + * Allocate a new netlink message with the default maximum payload size. + * + * Allocates a new netlink message without any further payload. The + * maximum payload size defaults to PAGESIZE or as otherwise specified + * with nlmsg_set_default_size(). + * + * @return Newly allocated netlink message or NULL. + */ +struct nl_msg * +nlmsg_alloc(void) +{ + return nlmsg_alloc_size(nm_utils_getpagesize()); +} + +struct nl_msg * +nlmsg_alloc_convert(struct nlmsghdr *hdr) +{ + struct nl_msg *nm; + + nm = nlmsg_alloc_size(NLMSG_ALIGN(hdr->nlmsg_len)); + memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len); + return nm; +} + +struct nl_msg * +nlmsg_alloc_simple(int nlmsgtype, int flags) +{ + struct nl_msg *nm; + struct nlmsghdr *new; + + nm = nlmsg_alloc(); + new = nm->nm_nlh; + new->nlmsg_type = nlmsgtype; + new->nlmsg_flags = flags; + return nm; +} + +void +nlmsg_free(struct nl_msg *msg) +{ + if (!msg) + return; + + g_free(msg->nm_nlh); + g_slice_free(struct nl_msg, msg); +} + +/*****************************************************************************/ + +int +nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad) +{ + void *tmp; + + nm_assert(n); + nm_assert(data); + nm_assert(len > 0); + nm_assert(pad >= 0); + + tmp = nlmsg_reserve(n, len, pad); + if (tmp == NULL) + return -ENOMEM; + + memcpy(tmp, data, len); + return 0; +} + +/*****************************************************************************/ + +int +nlmsg_parse(struct nlmsghdr * nlh, + int hdrlen, + struct nlattr * tb[], + int maxtype, + const struct nla_policy *policy) +{ + if (!nlmsg_valid_hdr(nlh, hdrlen)) + return -NME_NL_MSG_TOOSHORT; + + return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy); +} + +struct nlmsghdr * +nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags) +{ + struct nlmsghdr *nlh; + + if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN) + g_return_val_if_reached(NULL); + + nlh = (struct nlmsghdr *) n->nm_nlh; + nlh->nlmsg_type = type; + nlh->nlmsg_flags = flags; + nlh->nlmsg_pid = pid; + nlh->nlmsg_seq = seq; + + if (payload > 0 && nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL) + return NULL; + + return nlh; +} + +size_t +nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize) +{ + const char *src; + size_t srclen; + size_t len; + + /* - Always writes @dstsize bytes to @dst + * - Copies the first non-NUL characters to @dst. + * Any characters after the first NUL bytes in @nla are ignored. + * - If the string @nla is longer than @dstsize, the string + * gets truncated. @dst will always be NUL terminated. */ + + if (G_UNLIKELY(dstsize <= 1)) { + if (dstsize == 1) + dst[0] = '\0'; + if (nla && (srclen = nla_len(nla)) > 0) + return strnlen(nla_data(nla), srclen); + return 0; + } + + nm_assert(dst); + + if (nla) { + srclen = nla_len(nla); + if (srclen > 0) { + src = nla_data(nla); + srclen = strnlen(src, srclen); + if (srclen > 0) { + len = NM_MIN(dstsize - 1, srclen); + memcpy(dst, src, len); + memset(&dst[len], 0, dstsize - len); + return srclen; + } + } + } + + memset(dst, 0, dstsize); + return 0; +} + +size_t +nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize) +{ + size_t len; + int srclen; + + if (!nla) + return 0; + + srclen = nla_len(nla); + + if (srclen <= 0) { + nm_assert(srclen == 0); + return 0; + } + + len = NM_MIN((size_t) srclen, dstsize); + if (len > 0) { + /* there is a crucial difference between nla_strlcpy() and nla_memcpy(). + * The former always write @dstsize bytes (akin to strncpy()), here, we only + * write the bytes that we actually have (leaving the remainder undefined). */ + memcpy(dst, nla_data(nla), len); + } + + return srclen; +} + +int +nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data) +{ + struct nlattr *nla; + + nla = nla_reserve(msg, attrtype, datalen); + if (!nla) { + if (datalen < 0) + g_return_val_if_reached(-NME_BUG); + + return -ENOMEM; + } + + if (datalen > 0) + memcpy(nla_data(nla), data, datalen); + + return 0; +} + +struct nlattr * +nla_find(const struct nlattr *head, int len, int attrtype) +{ + const struct nlattr *nla; + int rem; + + nla_for_each_attr (nla, head, len, rem) { + if (nla_type(nla) == attrtype) + return (struct nlattr *) nla; + } + + return NULL; +} + +void +nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr) +{ + ssize_t len; + + len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr; + if (len < 0) + g_return_if_reached(); + else if (len > 0) { + msg->nm_nlh->nlmsg_len -= len; + memset(nlmsg_tail(msg->nm_nlh), 0, len); + } +} + +struct nlattr * +nla_nest_start(struct nl_msg *msg, int attrtype) +{ + struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh); + + if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0) + return NULL; + + return start; +} + +static int +_nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty) +{ + size_t pad, len; + + len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start; + + if (len > USHRT_MAX || (!keep_empty && len == NLA_HDRLEN)) { + /* + * Max nlattr size exceeded or empty nested attribute, trim the + * attribute header again + */ + nla_nest_cancel(msg, start); + + /* Return error only if nlattr size was exceeded */ + return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE; + } + + start->nla_len = len; + + pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len; + if (pad > 0) { + /* + * Data inside attribute does not end at a alignment boundary. + * Pad accordingly and account for the additional space in + * the message. nlmsg_reserve() may never fail in this situation, + * the allocate message buffer must be a multiple of NLMSG_ALIGNTO. + */ + if (!nlmsg_reserve(msg, pad, 0)) + g_return_val_if_reached(-NME_BUG); + } + + return 0; +} + +int +nla_nest_end(struct nl_msg *msg, struct nlattr *start) +{ + return _nest_end(msg, start, 0); +} + +static const uint16_t nla_attr_minlen[NLA_TYPE_MAX + 1] = { + [NLA_U8] = sizeof(uint8_t), + [NLA_U16] = sizeof(uint16_t), + [NLA_U32] = sizeof(uint32_t), + [NLA_U64] = sizeof(uint64_t), + [NLA_STRING] = 1, + [NLA_FLAG] = 0, +}; + +static int +validate_nla(const struct nlattr *nla, int maxtype, const struct nla_policy *policy) +{ + const struct nla_policy *pt; + unsigned int minlen = 0; + int type = nla_type(nla); + + if (type < 0 || type > maxtype) + return 0; + + pt = &policy[type]; + + if (pt->type > NLA_TYPE_MAX) + g_return_val_if_reached(-NME_BUG); + + if (pt->minlen) + minlen = pt->minlen; + else if (pt->type != NLA_UNSPEC) + minlen = nla_attr_minlen[pt->type]; + + if (nla_len(nla) < minlen) + return -NME_UNSPEC; + + if (pt->maxlen && nla_len(nla) > pt->maxlen) + return -NME_UNSPEC; + + if (pt->type == NLA_STRING) { + const char *data; + + nm_assert(minlen > 0); + + data = nla_data(nla); + if (data[nla_len(nla) - 1] != '\0') + return -NME_UNSPEC; + } + + return 0; +} + +int +nla_parse(struct nlattr * tb[], + int maxtype, + struct nlattr * head, + int len, + const struct nla_policy *policy) +{ + struct nlattr *nla; + int rem, nmerr; + + memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1)); + + nla_for_each_attr (nla, head, len, rem) { + int type = nla_type(nla); + + if (type > maxtype) + continue; + + if (policy) { + nmerr = validate_nla(nla, maxtype, policy); + if (nmerr < 0) + return nmerr; + } + + tb[type] = nla; + } + + return 0; +} + +/*****************************************************************************/ + +int +nlmsg_get_proto(struct nl_msg *msg) +{ + return msg->nm_protocol; +} + +void +nlmsg_set_proto(struct nl_msg *msg, int protocol) +{ + msg->nm_protocol = protocol; +} + +void +nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr) +{ + memcpy(&msg->nm_src, addr, sizeof(*addr)); +} + +struct ucred * +nlmsg_get_creds(struct nl_msg *msg) +{ + if (msg->nm_creds_has) + return &msg->nm_creds; + return NULL; +} + +void +nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds) +{ + if (creds) { + memcpy(&msg->nm_creds, creds, sizeof(*creds)); + msg->nm_creds_has = TRUE; + } else + msg->nm_creds_has = FALSE; +} + +/*****************************************************************************/ + +void * +genlmsg_put(struct nl_msg *msg, + uint32_t port, + uint32_t seq, + int family, + int hdrlen, + int flags, + uint8_t cmd, + uint8_t version) +{ + struct nlmsghdr * nlh; + struct genlmsghdr hdr = { + .cmd = cmd, + .version = version, + }; + + nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags); + if (nlh == NULL) + return NULL; + + memcpy(nlmsg_data(nlh), &hdr, sizeof(hdr)); + + return (char *) nlmsg_data(nlh) + GENL_HDRLEN; +} + +void * +genlmsg_data(const struct genlmsghdr *gnlh) +{ + return ((unsigned char *) gnlh + GENL_HDRLEN); +} + +void * +genlmsg_user_hdr(const struct genlmsghdr *gnlh) +{ + return genlmsg_data(gnlh); +} + +struct genlmsghdr * +genlmsg_hdr(struct nlmsghdr *nlh) +{ + return nlmsg_data(nlh); +} + +void * +genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen) +{ + return (char *) genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen); +} + +struct nlattr * +genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen) +{ + return genlmsg_user_data(gnlh, hdrlen); +} + +int +genlmsg_len(const struct genlmsghdr *gnlh) +{ + const struct nlmsghdr *nlh; + + nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN); + return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN); +} + +int +genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen) +{ + return genlmsg_len(gnlh) - NLMSG_ALIGN(hdrlen); +} + +int +genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen) +{ + struct genlmsghdr *ghdr; + + if (!nlmsg_valid_hdr(nlh, GENL_HDRLEN)) + return 0; + + ghdr = nlmsg_data(nlh); + if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen)) + return 0; + + return 1; +} + +int +genlmsg_parse(struct nlmsghdr * nlh, + int hdrlen, + struct nlattr * tb[], + int maxtype, + const struct nla_policy *policy) +{ + struct genlmsghdr *ghdr; + + if (!genlmsg_valid_hdr(nlh, hdrlen)) + return -NME_NL_MSG_TOOSHORT; + + ghdr = nlmsg_data(nlh); + return nla_parse(tb, + maxtype, + genlmsg_attrdata(ghdr, hdrlen), + genlmsg_attrlen(ghdr, hdrlen), + policy); +} + +static int +_genl_parse_getfamily(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy ctrl_policy[] = { + [CTRL_ATTR_FAMILY_ID] = {.type = NLA_U16}, + [CTRL_ATTR_FAMILY_NAME] = {.type = NLA_STRING, .maxlen = GENL_NAMSIZ}, + [CTRL_ATTR_VERSION] = {.type = NLA_U32}, + [CTRL_ATTR_HDRSIZE] = {.type = NLA_U32}, + [CTRL_ATTR_MAXATTR] = {.type = NLA_U32}, + [CTRL_ATTR_OPS] = {.type = NLA_NESTED}, + [CTRL_ATTR_MCAST_GROUPS] = {.type = NLA_NESTED}, + }; + struct nlattr * tb[G_N_ELEMENTS(ctrl_policy)]; + struct nlmsghdr *nlh = nlmsg_hdr(msg); + gint32 * response_data = arg; + + if (genlmsg_parse_arr(nlh, 0, tb, ctrl_policy) < 0) + return NL_SKIP; + + if (tb[CTRL_ATTR_FAMILY_ID]) + *response_data = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + + return NL_STOP; +} + +int +genl_ctrl_resolve(struct nl_sock *sk, const char *name) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int nmerr; + gint32 response_data = -1; + const struct nl_cb cb = { + .valid_cb = _genl_parse_getfamily, + .valid_arg = &response_data, + }; + + msg = nlmsg_alloc(); + + if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY, 1)) + return -ENOMEM; + + nmerr = nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, name); + if (nmerr < 0) + return nmerr; + + nmerr = nl_send_auto(sk, msg); + if (nmerr < 0) + return nmerr; + + nmerr = nl_recvmsgs(sk, &cb); + if (nmerr < 0) + return nmerr; + + /* If search was successful, request may be ACKed after data */ + nmerr = nl_wait_for_ack(sk, NULL); + if (nmerr < 0) + return nmerr; + + if (response_data < 0) + return -NME_UNSPEC; + + return response_data; +} + +/*****************************************************************************/ + +struct nl_sock * +nl_socket_alloc(void) +{ + struct nl_sock *sk; + + sk = g_slice_new0(struct nl_sock); + + sk->s_fd = -1; + sk->s_local.nl_family = AF_NETLINK; + sk->s_peer.nl_family = AF_NETLINK; + sk->s_seq_expect = sk->s_seq_next = time(NULL); + + return sk; +} + +void +nl_socket_free(struct nl_sock *sk) +{ + if (!sk) + return; + + if (sk->s_fd >= 0) + nm_close(sk->s_fd); + g_slice_free(struct nl_sock, sk); +} + +int +nl_socket_get_fd(const struct nl_sock *sk) +{ + return sk->s_fd; +} + +uint32_t +nl_socket_get_local_port(const struct nl_sock *sk) +{ + return sk->s_local.nl_pid; +} + +size_t +nl_socket_get_msg_buf_size(struct nl_sock *sk) +{ + return sk->s_bufsize; +} + +int +nl_socket_set_passcred(struct nl_sock *sk, int state) +{ + int err; + + if (sk->s_fd == -1) + return -NME_NL_BAD_SOCK; + + err = setsockopt(sk->s_fd, SOL_SOCKET, SO_PASSCRED, &state, sizeof(state)); + if (err < 0) + return -nm_errno_from_native(errno); + + if (state) + sk->s_flags |= NL_SOCK_PASSCRED; + else + sk->s_flags &= ~NL_SOCK_PASSCRED; + + return 0; +} + +int +nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize) +{ + sk->s_bufsize = bufsize; + + return 0; +} + +struct sockaddr_nl * +nlmsg_get_dst(struct nl_msg *msg) +{ + return &msg->nm_dst; +} + +int +nl_socket_set_nonblocking(const struct nl_sock *sk) +{ + if (sk->s_fd == -1) + return -NME_NL_BAD_SOCK; + + if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0) + return -nm_errno_from_native(errno); + + return 0; +} + +int +nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf) +{ + int err; + + if (rxbuf <= 0) + rxbuf = 32768; + + if (txbuf <= 0) + txbuf = 32768; + + if (sk->s_fd == -1) + return -NME_NL_BAD_SOCK; + + err = setsockopt(sk->s_fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof(txbuf)); + if (err < 0) { + return -nm_errno_from_native(errno); + } + + err = setsockopt(sk->s_fd, SOL_SOCKET, SO_RCVBUF, &rxbuf, sizeof(rxbuf)); + if (err < 0) { + return -nm_errno_from_native(errno); + } + + return 0; +} + +int +nl_socket_add_memberships(struct nl_sock *sk, int group, ...) +{ + int err; + va_list ap; + + if (sk->s_fd == -1) + return -NME_NL_BAD_SOCK; + + va_start(ap, group); + + while (group != 0) { + if (group < 0) { + va_end(ap); + g_return_val_if_reached(-NME_BUG); + } + + err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); + if (err < 0) { + int errsv = errno; + + va_end(ap); + return -nm_errno_from_native(errsv); + } + + group = va_arg(ap, int); + } + + va_end(ap); + + return 0; +} + +int +nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable) +{ + int err, val; + + if (sk->s_fd == -1) + return -NME_NL_BAD_SOCK; + + val = !!enable; + err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof(val)); + if (err < 0) + return -nm_errno_from_native(errno); + + return 0; +} + +void +nl_socket_disable_msg_peek(struct nl_sock *sk) +{ + sk->s_flags |= NL_MSG_PEEK_EXPLICIT; + sk->s_flags &= ~NL_MSG_PEEK; +} + +int +nl_connect(struct nl_sock *sk, int protocol) +{ + int err, nmerr; + socklen_t addrlen; + struct sockaddr_nl local = {0}; + + if (sk->s_fd != -1) + return -NME_NL_BAD_SOCK; + + sk->s_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); + if (sk->s_fd < 0) { + nmerr = -nm_errno_from_native(errno); + goto errout; + } + + nmerr = nl_socket_set_buffer_size(sk, 0, 0); + if (nmerr < 0) + goto errout; + + nm_assert(sk->s_local.nl_pid == 0); + + err = bind(sk->s_fd, (struct sockaddr *) &sk->s_local, sizeof(sk->s_local)); + if (err != 0) { + nmerr = -nm_errno_from_native(errno); + goto errout; + } + + addrlen = sizeof(local); + err = getsockname(sk->s_fd, (struct sockaddr *) &local, &addrlen); + if (err < 0) { + nmerr = -nm_errno_from_native(errno); + goto errout; + } + + if (addrlen != sizeof(local)) { + nmerr = -NME_UNSPEC; + goto errout; + } + + if (local.nl_family != AF_NETLINK) { + nmerr = -NME_UNSPEC; + goto errout; + } + + sk->s_local = local; + sk->s_proto = protocol; + + return 0; + +errout: + if (sk->s_fd != -1) { + close(sk->s_fd); + sk->s_fd = -1; + } + return nmerr; +} + +/*****************************************************************************/ + +static void +_cb_init(struct nl_cb *dst, const struct nl_cb *src) +{ + nm_assert(dst); + + if (src) + *dst = *src; + else + memset(dst, 0, sizeof(*dst)); +} + +static int +ack_wait_handler(struct nl_msg *msg, void *arg) +{ + return NL_STOP; +} + +int +nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb) +{ + struct nl_cb cb2; + + _cb_init(&cb2, cb); + cb2.ack_cb = ack_wait_handler; + return nl_recvmsgs(sk, &cb2); +} + +#define NL_CB_CALL(cb, type, msg) \ + do { \ + const struct nl_cb *_cb = (cb); \ + \ + if (_cb && _cb->type##_cb) { \ + /* the returned value here must be either a negative + * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \ + nmerr = _cb->type##_cb((msg), _cb->type##_arg); \ + switch (nmerr) { \ + case NL_OK: \ + nm_assert(nmerr == 0); \ + break; \ + case NL_SKIP: \ + goto skip; \ + case NL_STOP: \ + goto stop; \ + default: \ + if (nmerr >= 0) { \ + nm_assert_not_reached(); \ + nmerr = -NME_BUG; \ + } \ + goto out; \ + } \ + } \ + } while (0) + +int +nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb) +{ + int n, nmerr = 0, multipart = 0, interrupted = 0, nrecv = 0; + gs_free unsigned char *buf = NULL; + struct nlmsghdr * hdr; + struct sockaddr_nl nla = {0}; + struct ucred creds; + gboolean creds_has; + +continue_reading: + n = nl_recv(sk, &nla, &buf, &creds, &creds_has); + if (n <= 0) + return n; + + hdr = (struct nlmsghdr *) buf; + while (nlmsg_ok(hdr, n)) { + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc_convert(hdr); + + nlmsg_set_proto(msg, sk->s_proto); + nlmsg_set_src(msg, &nla); + nlmsg_set_creds(msg, creds_has ? &creds : NULL); + + nrecv++; + + /* Only do sequence checking if auto-ack mode is enabled */ + if (!(sk->s_flags & NL_NO_AUTO_ACK)) { + if (hdr->nlmsg_seq != sk->s_seq_expect) { + nmerr = -NME_NL_SEQ_MISMATCH; + goto out; + } + } + + if (hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR + || hdr->nlmsg_type == NLMSG_NOOP || hdr->nlmsg_type == NLMSG_OVERRUN) { + /* We can't check for !NLM_F_MULTI since some netlink + * users in the kernel are broken. */ + sk->s_seq_expect++; + } + + if (hdr->nlmsg_flags & NLM_F_MULTI) + multipart = 1; + + if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) { + /* + * We have to continue reading to clear + * all messages until a NLMSG_DONE is + * received and report the inconsistency. + */ + interrupted = 1; + } + + /* messages terminates a multipart message, this is + * usually the end of a message and therefore we slip + * out of the loop by default. the user may overrule + * this action by skipping this packet. */ + if (hdr->nlmsg_type == NLMSG_DONE) { + multipart = 0; + NL_CB_CALL(cb, finish, msg); + } + + /* Message to be ignored, the default action is to + * skip this message if no callback is specified. The + * user may overrule this action by returning + * NL_PROCEED. */ + else if (hdr->nlmsg_type == NLMSG_NOOP) + goto skip; + + /* Data got lost, report back to user. The default action is to + * quit parsing. The user may overrule this action by returning + * NL_SKIP or NL_PROCEED (dangerous) */ + else if (hdr->nlmsg_type == NLMSG_OVERRUN) { + nmerr = -NME_NL_MSG_OVERFLOW; + goto out; + } + + /* Message carries a nlmsgerr */ + else if (hdr->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *e = nlmsg_data(hdr); + + if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) { + /* Truncated error message, the default action + * is to stop parsing. The user may overrule + * this action by returning NL_SKIP or + * NL_PROCEED (dangerous) */ + nmerr = -NME_NL_MSG_TRUNC; + goto out; + } + if (e->error) { + /* Error message reported back from kernel. */ + if (cb && cb->err_cb) { + /* the returned value here must be either a negative + * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ + nmerr = cb->err_cb(&nla, e, cb->err_arg); + if (nmerr < 0) + goto out; + else if (nmerr == NL_SKIP) + goto skip; + else if (nmerr == NL_STOP) { + nmerr = -nm_errno_from_native(e->error); + goto out; + } + nm_assert(nmerr == NL_OK); + } else { + nmerr = -nm_errno_from_native(e->error); + goto out; + } + } else + NL_CB_CALL(cb, ack, msg); + } else { + /* Valid message (not checking for MULTIPART bit to + * get along with broken kernels. NL_SKIP has no + * effect on this. */ + NL_CB_CALL(cb, valid, msg); + } +skip: + nmerr = 0; + hdr = nlmsg_next(hdr, &n); + } + + if (multipart) { + /* Multipart message not yet complete, continue reading */ + nm_clear_g_free(&buf); + + nmerr = 0; + goto continue_reading; + } + +stop: + nmerr = 0; + +out: + if (interrupted) + nmerr = -NME_NL_DUMP_INTR; + + nm_assert(nmerr <= 0); + return nmerr ?: nrecv; +} + +int +nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr) +{ + int ret; + + if (sk->s_fd < 0) + return -NME_NL_BAD_SOCK; + + nlmsg_set_src(msg, &sk->s_local); + + ret = sendmsg(sk->s_fd, hdr, 0); + if (ret < 0) + return -nm_errno_from_native(errno); + + return ret; +} + +int +nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen) +{ + struct sockaddr_nl *dst; + struct ucred * creds; + struct msghdr hdr = { + .msg_name = (void *) &sk->s_peer, + .msg_namelen = sizeof(struct sockaddr_nl), + .msg_iov = iov, + .msg_iovlen = iovlen, + }; + char buf[CMSG_SPACE(sizeof(struct ucred))]; + + /* Overwrite destination if specified in the message itself, defaults + * to the peer address of the socket. + */ + dst = nlmsg_get_dst(msg); + if (dst->nl_family == AF_NETLINK) + hdr.msg_name = dst; + + /* Add credentials if present. */ + creds = nlmsg_get_creds(msg); + if (creds != NULL) { + struct cmsghdr *cmsg; + + hdr.msg_control = buf; + hdr.msg_controllen = sizeof(buf); + + cmsg = CMSG_FIRSTHDR(&hdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + memcpy(CMSG_DATA(cmsg), creds, sizeof(struct ucred)); + } + + return nl_sendmsg(sk, msg, &hdr); +} + +void +nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg) +{ + struct nlmsghdr *nlh; + + nlh = nlmsg_hdr(msg); + if (nlh->nlmsg_pid == NL_AUTO_PORT) + nlh->nlmsg_pid = nl_socket_get_local_port(sk); + + if (nlh->nlmsg_seq == NL_AUTO_SEQ) + nlh->nlmsg_seq = sk->s_seq_next++; + + if (msg->nm_protocol == -1) + msg->nm_protocol = sk->s_proto; + + nlh->nlmsg_flags |= NLM_F_REQUEST; + + if (!(sk->s_flags & NL_NO_AUTO_ACK)) + nlh->nlmsg_flags |= NLM_F_ACK; +} + +int +nl_send(struct nl_sock *sk, struct nl_msg *msg) +{ + struct iovec iov = { + .iov_base = (void *) nlmsg_hdr(msg), + .iov_len = nlmsg_hdr(msg)->nlmsg_len, + }; + + return nl_send_iovec(sk, msg, &iov, 1); +} + +int +nl_send_auto(struct nl_sock *sk, struct nl_msg *msg) +{ + nl_complete_msg(sk, msg); + + return nl_send(sk, msg); +} + +int +nl_recv(struct nl_sock * sk, + struct sockaddr_nl *nla, + unsigned char ** buf, + struct ucred * out_creds, + gboolean * out_creds_has) +{ + /* We really expect msg_contol_buf to be large enough and MSG_CTRUNC not + * happening. We nm_assert() against that. However, in release builds + * we don't assert, so add some extra safety space for the unexpected + * case where we might need more than CMSG_SPACE(sizeof(struct ucred)). + * It should not hurt and should not be necessary. It's just some + * extra defensive space. */ +#define _MSG_CONTROL_BUF_EXTRA_SPACE (NM_MORE_ASSERTS ? 512u : 0u) + union { + struct cmsghdr cmsghdr; + char buf[CMSG_SPACE(sizeof(struct ucred)) + _MSG_CONTROL_BUF_EXTRA_SPACE]; + } msg_contol_buf; + ssize_t n; + int flags = 0; + struct iovec iov; + struct msghdr msg = { + .msg_name = (void *) nla, + .msg_namelen = sizeof(struct sockaddr_nl), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_controllen = 0, + .msg_control = NULL, + }; + struct ucred tmpcreds; + gboolean tmpcreds_has = FALSE; + int retval; + int errsv; + + nm_assert(nla); + nm_assert(buf && !*buf); + nm_assert(!out_creds_has == !out_creds); + + if ((sk->s_flags & NL_MSG_PEEK) + || (!(sk->s_flags & NL_MSG_PEEK_EXPLICIT) && sk->s_bufsize == 0)) + flags |= MSG_PEEK | MSG_TRUNC; + + iov.iov_len = sk->s_bufsize ?: (((size_t) nm_utils_getpagesize()) * 4u); + iov.iov_base = g_malloc(iov.iov_len); + + if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) { + msg.msg_controllen = sizeof(msg_contol_buf); + msg.msg_control = msg_contol_buf.buf; + } + +retry: + n = recvmsg(sk->s_fd, &msg, flags); + if (!n) { + retval = 0; + goto abort; + } + + if (n < 0) { + errsv = errno; + if (errsv == EINTR) + goto retry; + retval = -nm_errno_from_native(errsv); + goto abort; + } + + /* We really don't expect truncation of ancillary data. We provided a large + * enough buffer, so this is likely a bug. In the worst case, we might lack + * the requested credentials and the caller likely will reject the message + * later. */ + nm_assert(!(msg.msg_flags & MSG_CTRUNC)); + + if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) { + /* respond with error to an incomplete message */ + if (flags == 0) { + retval = -NME_NL_MSG_TRUNC; + goto abort; + } + + /* Provided buffer is not long enough, enlarge it + * to size of n (which should be total length of the message) + * and try again. */ + iov.iov_base = g_realloc(iov.iov_base, n); + iov.iov_len = n; + flags = 0; + goto retry; + } + + if (flags != 0) { + /* Buffer is big enough, do the actual reading */ + flags = 0; + goto retry; + } + + if (msg.msg_namelen != sizeof(struct sockaddr_nl)) { + retval = -NME_UNSPEC; + goto abort; + } + + if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) { + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + if (cmsg->cmsg_type != SCM_CREDENTIALS) + continue; + memcpy(&tmpcreds, CMSG_DATA(cmsg), sizeof(tmpcreds)); + tmpcreds_has = TRUE; + break; + } + } + + retval = n; + +abort: + if (retval <= 0) { + g_free(iov.iov_base); + return retval; + } + + *buf = iov.iov_base; + if (out_creds && tmpcreds_has) + *out_creds = tmpcreds; + NM_SET_OUT(out_creds_has, tmpcreds_has); + return retval; +} diff --git a/src/libnm-platform/nm-netlink.h b/src/libnm-platform/nm-netlink.h new file mode 100644 index 00000000..ab355f74 --- /dev/null +++ b/src/libnm-platform/nm-netlink.h @@ -0,0 +1,616 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_NETLINK_H__ +#define __NM_NETLINK_H__ + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/genetlink.h> + +#include "libnm-std-aux/unaligned.h" + +/*****************************************************************************/ + +#define NLMSGERR_ATTR_UNUSED 0 +#define NLMSGERR_ATTR_MSG 1 +#define NLMSGERR_ATTR_OFFS 2 +#define NLMSGERR_ATTR_COOKIE 3 +#define NLMSGERR_ATTR_MAX 3 + +#ifndef NLM_F_ACK_TLVS + #define NLM_F_ACK_TLVS 0x200 +#endif + +/*****************************************************************************/ + +/* Basic attribute data types */ +enum { + NLA_UNSPEC, /* Unspecified type, binary data chunk */ + NLA_U8, /* 8 bit integer */ + NLA_U16, /* 16 bit integer */ + NLA_U32, /* 32 bit integer */ + NLA_U64, /* 64 bit integer */ + NLA_STRING, /* NUL terminated character string */ + NLA_FLAG, /* Flag */ + NLA_MSECS, /* Micro seconds (64bit) */ + NLA_NESTED, /* Nested attributes */ + NLA_NESTED_COMPAT, + NLA_NUL_STRING, + NLA_BINARY, + NLA_S8, + NLA_S16, + NLA_S32, + NLA_S64, + __NLA_TYPE_MAX, +}; + +#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1) + +struct nl_msg; + +/*****************************************************************************/ + +const char *nl_nlmsgtype2str(int type, char *buf, size_t size); + +const char *nl_nlmsg_flags2str(int flags, char *buf, size_t len); + +const char *nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len); + +/*****************************************************************************/ + +struct nla_policy { + /* Type of attribute or NLA_UNSPEC */ + uint16_t type; + + /* Minimal length of payload required */ + uint16_t minlen; + + /* Maximal length of payload allowed */ + uint16_t maxlen; +}; + +/*****************************************************************************/ + +/* static asserts that @tb and @policy are suitable arguments to nla_parse(). */ +#define _nl_static_assert_tb(tb, policy) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0); \ + \ + /* We allow @policy to be either a C array or NULL. The sizeof() + * must either match the expected array size or the sizeof(NULL), + * but not both. */ \ + G_STATIC_ASSERT_EXPR((sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)) \ + ^ (sizeof(policy) == sizeof(NULL))); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static inline int +nla_attr_size(int payload) +{ + nm_assert(payload >= 0); + + return NLA_HDRLEN + payload; +} + +static inline int +nla_total_size(int payload) +{ + return NLA_ALIGN(nla_attr_size(payload)); +} + +static inline int +nla_padlen(int payload) +{ + return nla_total_size(payload) - nla_attr_size(payload); +} + +struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int attrlen); + +static inline int +nla_len(const struct nlattr *nla) +{ + nm_assert(nla); + nm_assert(nla->nla_len >= NLA_HDRLEN); + + return ((int) nla->nla_len) - NLA_HDRLEN; +} + +static inline int +nla_type(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= 0); + + return nla->nla_type & NLA_TYPE_MASK; +} + +static inline void * +nla_data(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= 0); + + return &(((char *) nla)[NLA_HDRLEN]); +} + +#define nla_data_as(type, nla) \ + ({ \ + const struct nlattr *_nla = (nla); \ + \ + nm_assert(nla_len(_nla) >= sizeof(type)); \ + \ + /* note that casting the pointer is undefined behavior in C, if + * the data has wrong alignment. Netlink data is aligned to 4 bytes, + * that means, if the alignment is larger than 4, this is invalid. */ \ + G_STATIC_ASSERT_EXPR(_nm_alignof(type) <= NLA_ALIGNTO); \ + \ + (type *) nla_data(_nla); \ + }) + +static inline uint8_t +nla_get_u8(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(uint8_t)); + + return *((const uint8_t *) nla_data(nla)); +} + +static inline int8_t +nla_get_s8(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(int8_t)); + + return *((const int8_t *) nla_data(nla)); +} + +static inline uint8_t +nla_get_u8_cond(/*const*/ struct nlattr *const *tb, int attr, uint8_t default_val) +{ + nm_assert(tb); + nm_assert(attr >= 0); + + return tb[attr] ? nla_get_u8(tb[attr]) : default_val; +} + +static inline uint16_t +nla_get_u16(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(uint16_t)); + + return *((const uint16_t *) nla_data(nla)); +} + +static inline uint32_t +nla_get_u32(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(uint32_t)); + + return *((const uint32_t *) nla_data(nla)); +} + +static inline int32_t +nla_get_s32(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(int32_t)); + + return *((const int32_t *) nla_data(nla)); +} + +static inline uint64_t +nla_get_u64(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(uint64_t)); + + return unaligned_read_ne64(nla_data(nla)); +} + +static inline uint64_t +nla_get_be64(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= sizeof(uint64_t)); + + return unaligned_read_be64(nla_data(nla)); +} + +static inline char * +nla_get_string(const struct nlattr *nla) +{ + nm_assert(nla_len(nla) >= 0); + + return (char *) nla_data(nla); +} + +size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize); + +size_t nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize); + +#define nla_memcpy_checked_size(dst, nla, dstsize) \ + G_STMT_START \ + { \ + void *const _dst = (dst); \ + const struct nlattr *const _nla = (nla); \ + const size_t _dstsize = (dstsize); \ + size_t _srcsize; \ + \ + /* assert that, if @nla is given, that it has the exact expected + * size. This implies that the caller previously verified the length + * of the attribute (via minlen/maxlen at nla_parse()). */ \ + \ + if (_nla) { \ + _srcsize = nla_memcpy(_dst, _nla, _dstsize); \ + nm_assert(_srcsize == _dstsize); \ + } \ + } \ + G_STMT_END + +int nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data); + +static inline int +nla_put_string(struct nl_msg *msg, int attrtype, const char *str) +{ + nm_assert(str); + + return nla_put(msg, attrtype, strlen(str) + 1, str); +} + +static inline int +nla_put_uint8(struct nl_msg *msg, int attrtype, uint8_t val) +{ + return nla_put(msg, attrtype, sizeof(val), &val); +} + +static inline int +nla_put_uint16(struct nl_msg *msg, int attrtype, uint16_t val) +{ + return nla_put(msg, attrtype, sizeof(val), &val); +} + +static inline int +nla_put_uint32(struct nl_msg *msg, int attrtype, uint32_t val) +{ + return nla_put(msg, attrtype, sizeof(val), &val); +} + +#define NLA_PUT(msg, attrtype, attrlen, data) \ + G_STMT_START \ + { \ + if (nla_put(msg, attrtype, attrlen, data) < 0) \ + goto nla_put_failure; \ + } \ + G_STMT_END + +#define NLA_PUT_TYPE(msg, type, attrtype, value) \ + G_STMT_START \ + { \ + type __nla_tmp = value; \ + NLA_PUT(msg, attrtype, sizeof(type), &__nla_tmp); \ + } \ + G_STMT_END + +#define NLA_PUT_U8(msg, attrtype, value) NLA_PUT_TYPE(msg, uint8_t, attrtype, value) + +#define NLA_PUT_S8(msg, attrtype, value) NLA_PUT_TYPE(msg, int8_t, attrtype, value) + +#define NLA_PUT_U16(msg, attrtype, value) NLA_PUT_TYPE(msg, uint16_t, attrtype, value) + +#define NLA_PUT_U32(msg, attrtype, value) NLA_PUT_TYPE(msg, uint32_t, attrtype, value) + +#define NLA_PUT_S32(msg, attrtype, value) NLA_PUT_TYPE(msg, int32_t, attrtype, value) + +#define NLA_PUT_U64(msg, attrtype, value) NLA_PUT_TYPE(msg, uint64_t, attrtype, value) + +#define NLA_PUT_STRING(msg, attrtype, value) NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value) + +#define NLA_PUT_FLAG(msg, attrtype) NLA_PUT(msg, attrtype, 0, NULL) + +struct nlattr *nla_find(const struct nlattr *head, int len, int attrtype); + +static inline int +nla_ok(const struct nlattr *nla, int remaining) +{ + return remaining >= (int) sizeof(*nla) && nla->nla_len >= sizeof(*nla) + && nla->nla_len <= remaining; +} + +static inline struct nlattr * +nla_next(const struct nlattr *nla, int *remaining) +{ + int totlen = NLA_ALIGN(nla->nla_len); + + *remaining -= totlen; + return (struct nlattr *) ((char *) nla + totlen); +} + +#define nla_for_each_attr(pos, head, len, rem) \ + for (pos = head, rem = len; nla_ok(pos, rem); pos = nla_next(pos, &(rem))) + +#define nla_for_each_nested(pos, nla, rem) \ + for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); nla_ok(pos, rem); \ + pos = nla_next(pos, &(rem))) + +void nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr); +struct nlattr *nla_nest_start(struct nl_msg *msg, int attrtype); +int nla_nest_end(struct nl_msg *msg, struct nlattr *start); + +int nla_parse(struct nlattr * tb[], + int maxtype, + struct nlattr * head, + int len, + const struct nla_policy *policy); + +#define nla_parse_arr(tb, head, len, policy) \ + ({ \ + _nl_static_assert_tb((tb), (policy)); \ + \ + nla_parse((tb), G_N_ELEMENTS(tb) - 1, (head), (len), (policy)); \ + }) + +static inline int +nla_parse_nested(struct nlattr * tb[], + int maxtype, + struct nlattr * nla, + const struct nla_policy *policy) +{ + return nla_parse(tb, maxtype, nla_data(nla), nla_len(nla), policy); +} + +#define nla_parse_nested_arr(tb, nla, policy) \ + ({ \ + _nl_static_assert_tb((tb), (policy)); \ + \ + nla_parse_nested((tb), G_N_ELEMENTS(tb) - 1, (nla), (policy)); \ + }) + +/*****************************************************************************/ + +struct nl_msg *nlmsg_alloc(void); + +struct nl_msg *nlmsg_alloc_size(size_t max); + +struct nl_msg *nlmsg_alloc_convert(struct nlmsghdr *hdr); + +struct nl_msg *nlmsg_alloc_simple(int nlmsgtype, int flags); + +void *nlmsg_reserve(struct nl_msg *n, size_t len, int pad); + +int nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad); + +#define nlmsg_append_struct(n, data) nlmsg_append(n, (data), sizeof(*(data)), NLMSG_ALIGNTO) + +void nlmsg_free(struct nl_msg *msg); + +static inline int +nlmsg_size(int payload) +{ + nm_assert(payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4); + return NLMSG_HDRLEN + payload; +} + +static inline int +nlmsg_total_size(int payload) +{ + return NLMSG_ALIGN(nlmsg_size(payload)); +} + +static inline int +nlmsg_ok(const struct nlmsghdr *nlh, int remaining) +{ + return (remaining >= (int) sizeof(struct nlmsghdr) && nlh->nlmsg_len >= sizeof(struct nlmsghdr) + && nlh->nlmsg_len <= remaining); +} + +static inline struct nlmsghdr * +nlmsg_next(struct nlmsghdr *nlh, int *remaining) +{ + int totlen = NLMSG_ALIGN(nlh->nlmsg_len); + + *remaining -= totlen; + + return (struct nlmsghdr *) ((unsigned char *) nlh + totlen); +} + +int nlmsg_get_proto(struct nl_msg *msg); +void nlmsg_set_proto(struct nl_msg *msg, int protocol); + +void nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr); + +struct ucred *nlmsg_get_creds(struct nl_msg *msg); +void nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds); + +static inline void +_nm_auto_nl_msg_cleanup(struct nl_msg **ptr) +{ + nlmsg_free(*ptr); +} +#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup) + +static inline void * +nlmsg_data(const struct nlmsghdr *nlh) +{ + return (unsigned char *) nlh + NLMSG_HDRLEN; +} + +static inline void * +nlmsg_tail(const struct nlmsghdr *nlh) +{ + return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len); +} + +struct nlmsghdr *nlmsg_hdr(struct nl_msg *n); + +static inline int +nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen) +{ + if (nlh->nlmsg_len < nlmsg_size(hdrlen)) + return 0; + + return 1; +} + +static inline int +nlmsg_datalen(const struct nlmsghdr *nlh) +{ + return nlh->nlmsg_len - NLMSG_HDRLEN; +} + +static inline int +nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen) +{ + return NM_MAX((int) (nlmsg_datalen(nlh) - NLMSG_ALIGN(hdrlen)), 0); +} + +static inline struct nlattr * +nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen) +{ + unsigned char *data = nlmsg_data(nlh); + return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen)); +} + +static inline struct nlattr * +nlmsg_find_attr(struct nlmsghdr *nlh, int hdrlen, int attrtype) +{ + return nla_find(nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), attrtype); +} + +int nlmsg_parse(struct nlmsghdr * nlh, + int hdrlen, + struct nlattr * tb[], + int maxtype, + const struct nla_policy *policy); + +#define nlmsg_parse_arr(nlh, hdrlen, tb, policy) \ + ({ \ + _nl_static_assert_tb((tb), (policy)); \ + G_STATIC_ASSERT_EXPR((hdrlen) >= 0); \ + \ + nlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \ + }) + +struct nlmsghdr * +nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags); + +/*****************************************************************************/ + +#define NL_AUTO_PORT 0 +#define NL_AUTO_SEQ 0 + +struct nl_sock; + +struct nl_sock *nl_socket_alloc(void); + +void nl_socket_free(struct nl_sock *sk); + +int nl_socket_get_fd(const struct nl_sock *sk); + +struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *msg); + +size_t nl_socket_get_msg_buf_size(struct nl_sock *sk); +int nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize); + +int nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf); + +int nl_socket_set_passcred(struct nl_sock *sk, int state); + +int nl_socket_set_nonblocking(const struct nl_sock *sk); + +void nl_socket_disable_msg_peek(struct nl_sock *sk); + +uint32_t nl_socket_get_local_port(const struct nl_sock *sk); + +int nl_socket_add_memberships(struct nl_sock *sk, int group, ...); + +int nl_connect(struct nl_sock *sk, int protocol); + +int nl_recv(struct nl_sock * sk, + struct sockaddr_nl *nla, + unsigned char ** buf, + struct ucred * out_creds, + gboolean * out_creds_has); + +int nl_send(struct nl_sock *sk, struct nl_msg *msg); + +int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg); + +/*****************************************************************************/ + +enum nl_cb_action { + /* Proceed with wathever would come next */ + NL_OK, + /* Skip this message */ + NL_SKIP, + /* Stop parsing altogether and discard remaining messages */ + NL_STOP, +}; + +typedef int (*nl_recvmsg_msg_cb_t)(struct nl_msg *msg, void *arg); + +typedef int (*nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg); + +struct nl_cb { + nl_recvmsg_msg_cb_t valid_cb; + void * valid_arg; + + nl_recvmsg_msg_cb_t finish_cb; + void * finish_arg; + + nl_recvmsg_msg_cb_t ack_cb; + void * ack_arg; + + nl_recvmsg_err_cb_t err_cb; + void * err_arg; +}; + +int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr); + +int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen); + +void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg); + +int nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb); + +int nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb); + +int nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable); + +/*****************************************************************************/ + +void * genlmsg_put(struct nl_msg *msg, + uint32_t port, + uint32_t seq, + int family, + int hdrlen, + int flags, + uint8_t cmd, + uint8_t version); +void * genlmsg_data(const struct genlmsghdr *gnlh); +void * genlmsg_user_hdr(const struct genlmsghdr *gnlh); +struct genlmsghdr *genlmsg_hdr(struct nlmsghdr *nlh); +void * genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen); +struct nlattr * genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen); +int genlmsg_len(const struct genlmsghdr *gnlh); +int genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen); +int genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen); + +int genlmsg_parse(struct nlmsghdr * nlh, + int hdrlen, + struct nlattr * tb[], + int maxtype, + const struct nla_policy *policy); + +#define genlmsg_parse_arr(nlh, hdrlen, tb, policy) \ + ({ \ + _nl_static_assert_tb((tb), (policy)); \ + G_STATIC_ASSERT_EXPR((hdrlen) >= 0); \ + \ + genlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \ + }) + +int genl_ctrl_resolve(struct nl_sock *sk, const char *name); + +/*****************************************************************************/ + +#endif /* __NM_NETLINK_H__ */ diff --git a/src/libnm-platform/nm-platform-private.h b/src/libnm-platform/nm-platform-private.h new file mode 100644 index 00000000..cf805689 --- /dev/null +++ b/src/libnm-platform/nm-platform-private.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#ifndef __NM_PLATFORM_PRIVATE_H__ +#define __NM_PLATFORM_PRIVATE_H__ + +#include "nm-platform.h" +#include "nmp-object.h" + +NMPCache *nm_platform_get_cache(NMPlatform *self); + +#define NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform) \ + G_STMT_START \ + { \ + NMPlatform *_platform = (platform); \ + \ + nm_assert(NM_IS_PLATFORM(_platform)); \ + nm_assert(NM_IN_SET(nm_platform_netns_get(_platform), NULL, nmp_netns_get_current())); \ + } \ + G_STMT_END + +void nm_platform_cache_update_emit_signal(NMPlatform * platform, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new); + +#endif /* __NM_PLATFORM_PRIVATE_H__ */ diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c new file mode 100644 index 00000000..ce847451 --- /dev/null +++ b/src/libnm-platform/nm-platform-utils.c @@ -0,0 +1,2258 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-platform-utils.h" + +#include "libnm-std-aux/nm-linux-compat.h" + +#include <unistd.h> +#include <sys/ioctl.h> +#include <linux/sockios.h> +#include <linux/mii.h> +#include <linux/if.h> +#include <linux/version.h> +#include <linux/rtnetlink.h> +#include <fcntl.h> +#include <libudev.h> + +#include "libnm-base/nm-ethtool-base.h" +#include "libnm-log-core/nm-logging.h" +#include "libnm-glib-aux/nm-time-utils.h" + +/*****************************************************************************/ + +#define ONOFF(bool_val) ((bool_val) ? "on" : "off") + +/****************************************************************************** + * utils + *****************************************************************************/ + +extern char *if_indextoname(unsigned __ifindex, char *__ifname); +unsigned if_nametoindex(const char *__ifname); + +const char * +nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/) +{ + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(out_ifname, NULL); + + return if_indextoname(ifindex, out_ifname); +} + +int +nmp_utils_if_nametoindex(const char *ifname) +{ + g_return_val_if_fail(ifname, 0); + + return if_nametoindex(ifname); +} + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE(nm_platform_link_duplex_type_to_string, + NMPlatformLinkDuplexType, + NM_UTILS_LOOKUP_DEFAULT_WARN(NULL), + NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_UNKNOWN, "unknown"), + NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_FULL, "full"), + NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_HALF, "half"), ); + +/*****************************************************************************/ + +typedef struct { + int fd; + const int ifindex; + char ifname[IFNAMSIZ]; +} SocketHandle; + +#define SOCKET_HANDLE_INIT(_ifindex) \ + { \ + .fd = -1, .ifindex = (_ifindex), \ + } + +static void +_nm_auto_socket_handle(SocketHandle *shandle) +{ + if (shandle->fd >= 0) + nm_close(shandle->fd); +} + +#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle) + +/*****************************************************************************/ + +typedef enum { + IOCTL_CALL_DATA_TYPE_NONE, + IOCTL_CALL_DATA_TYPE_IFRDATA, + IOCTL_CALL_DATA_TYPE_IFRU, +} IoctlCallDataType; + +static int +_ioctl_call(const char * log_ioctl_type, + const char * log_subtype, + unsigned long int ioctl_request, + int ifindex, + int * inout_fd, + char * inout_ifname, + IoctlCallDataType edata_type, + gpointer edata, + gsize edata_size, + struct ifreq * out_ifreq) +{ + nm_auto_close int fd_close = -1; + int fd; + int r; + gpointer edata_backup = NULL; + gs_free gpointer edata_backup_free = NULL; + guint try_count; + char known_ifnames[2][IFNAMSIZ]; + const char * failure_reason = NULL; + struct ifreq ifr; + + nm_assert(ifindex > 0); + nm_assert(NM_IN_SET(edata_type, + IOCTL_CALL_DATA_TYPE_NONE, + IOCTL_CALL_DATA_TYPE_IFRDATA, + IOCTL_CALL_DATA_TYPE_IFRU)); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_NONE || edata_size == 0); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0); + nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRU + || (edata_size > 0 && edata_size <= sizeof(ifr.ifr_ifru))); + nm_assert(edata_size == 0 || edata); + + /* open a file descriptor (or use the one provided). */ + if (inout_fd && *inout_fd >= 0) + fd = *inout_fd; + else { + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + r = -NM_ERRNO_NATIVE(errno); + failure_reason = "failed creating socket or ioctl"; + goto out; + } + if (inout_fd) + *inout_fd = fd; + else + fd_close = fd; + } + + /* resolve the ifindex to name (or use the one provided). */ + if (inout_ifname && inout_ifname[0]) + nm_utils_ifname_cpy(known_ifnames[0], inout_ifname); + else { + if (!nmp_utils_if_indextoname(ifindex, known_ifnames[0])) { + failure_reason = "cannot resolve ifindex"; + r = -ENODEV; + goto out; + } + if (inout_ifname) + nm_utils_ifname_cpy(inout_ifname, known_ifnames[0]); + } + + /* we might need to retry the request. Backup edata so that we can + * restore it on retry. */ + if (edata_size > 0) + edata_backup = nm_memdup_maybe_a(500, edata, edata_size, &edata_backup_free); + + try_count = 0; + +again: +{ + const char *ifname = known_ifnames[try_count % 2]; + + nm_assert(ifindex > 0); + nm_assert(ifname && nm_utils_ifname_valid_kernel(ifname, NULL)); + nm_assert(fd >= 0); + + memset(&ifr, 0, sizeof(ifr)); + nm_utils_ifname_cpy(ifr.ifr_name, ifname); + if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA) + ifr.ifr_data = edata; + else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU) + memcpy(&ifr.ifr_ifru, edata, NM_MIN(edata_size, sizeof(ifr.ifr_ifru))); + + if (ioctl(fd, ioctl_request, &ifr) < 0) { + r = -NM_ERRNO_NATIVE(errno); + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s, %s: failed: %s", + log_ioctl_type, + ifindex, + log_subtype, + ifname, + nm_strerror_native(-r)); + } else { + r = 0; + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s, %s: success", + log_ioctl_type, + ifindex, + log_subtype, + ifname); + } +} + + try_count++; + + /* resolve the name again to see whether the ifindex still has the same name. */ + if (!nmp_utils_if_indextoname(ifindex, known_ifnames[try_count % 2])) { + /* we could not find the ifindex again. Probably the device just got + * removed. + * + * In both cases we return the error code we got from ioctl above. + * Either it failed because the device was gone already or it still + * managed to complete the call. In both cases, the error code is good. */ + failure_reason = + "cannot resolve ifindex after ioctl call. Probably the device was just removed"; + goto out; + } + + /* check whether the ifname changed in the meantime. If yes, would render the result + * invalid. Note that this cannot detect every race regarding renames, for example: + * + * - if_indextoname(#10) gives eth0 + * - rename(#10) => eth0_tmp + * - rename(#11) => eth0 + * - ioctl(eth0) (wrongly fetching #11, formerly eth1) + * - rename(#11) => eth_something + * - rename(#10) => eth0 + * - if_indextoname(#10) gives eth0 + */ + if (!nm_streq(known_ifnames[0], known_ifnames[1])) { + gboolean retry; + + /* we detected a possible(!) rename. + * + * For getters it's straight forward to just retry the call. + * + * For setters we also always retry. If our previous call operated on the right device, + * calling it again should have no bad effect (just setting the same thing more than once). + * + * The only potential bad thing is if there was a race involving swapping names, and we just + * set the ioctl option on the wrong device. But then the bad thing already happenned and + * we cannot detect it (nor do anything about it). At least, we can retry and set the + * option on the right interface. */ + retry = (try_count < 5); + + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s", + log_ioctl_type, + ifindex, + log_subtype, + known_ifnames[(try_count - 1) % 2], + known_ifnames[try_count % 2], + retry ? "Retry" : "No retry"); + if (inout_ifname) + nm_utils_ifname_cpy(inout_ifname, known_ifnames[try_count % 2]); + if (retry) { + if (edata_size > 0) + memcpy(edata, edata_backup, edata_size); + goto again; + } + } + +out: + if (failure_reason) { + nm_log_trace(LOGD_PLATFORM, + "%s[%d]: %s: %s: %s", + log_ioctl_type, + ifindex, + log_subtype, + failure_reason, + r < 0 ? nm_strerror_native(-r) : "assume success"); + } + if (r >= 0) + NM_SET_OUT(out_ifreq, ifr); + return r; +} + +/****************************************************************************** + * ethtool + *****************************************************************************/ + +static NM_UTILS_ENUM2STR_DEFINE(_ethtool_cmd_to_string, + guint32, + NM_UTILS_ENUM2STR(ETHTOOL_GCOALESCE, "ETHTOOL_GCOALESCE"), + NM_UTILS_ENUM2STR(ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"), + NM_UTILS_ENUM2STR(ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"), + NM_UTILS_ENUM2STR(ETHTOOL_GLINK, "ETHTOOL_GLINK"), + NM_UTILS_ENUM2STR(ETHTOOL_GLINKSETTINGS, "ETHTOOL_GLINKSETTINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_GPERMADDR, "ETHTOOL_GPERMADDR"), + NM_UTILS_ENUM2STR(ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_GPAUSEPARAM, "ETHTOOL_GPAUSEPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_GSET, "ETHTOOL_GSET"), + NM_UTILS_ENUM2STR(ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"), + NM_UTILS_ENUM2STR(ETHTOOL_GSTATS, "ETHTOOL_GSTATS"), + NM_UTILS_ENUM2STR(ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_GWOL, "ETHTOOL_GWOL"), + NM_UTILS_ENUM2STR(ETHTOOL_SCOALESCE, "ETHTOOL_SCOALESCE"), + NM_UTILS_ENUM2STR(ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"), + NM_UTILS_ENUM2STR(ETHTOOL_SLINKSETTINGS, "ETHTOOL_SLINKSETTINGS"), + NM_UTILS_ENUM2STR(ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_SPAUSEPARAM, "ETHTOOL_SPAUSEPARAM"), + NM_UTILS_ENUM2STR(ETHTOOL_SSET, "ETHTOOL_SSET"), + NM_UTILS_ENUM2STR(ETHTOOL_SWOL, "ETHTOOL_SWOL"), ); + +static const char * +_ethtool_edata_to_string(gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len) +{ + nm_assert(edata); + nm_assert(edata_size >= sizeof(guint32)); + nm_assert((((intptr_t) edata) % _nm_alignof(guint32)) == 0); + + return _ethtool_cmd_to_string(*((guint32 *) edata), sbuf, sbuf_len); +} + +/*****************************************************************************/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + #define ethtool_cmd_speed(pedata) ((pedata)->speed) + + #define ethtool_cmd_speed_set(pedata, speed) \ + G_STMT_START \ + { \ + (pedata)->speed = (guint16) (speed); \ + } \ + G_STMT_END +#endif + +static int +_ethtool_call_handle(SocketHandle *shandle, gpointer edata, gsize edata_size) +{ + char sbuf[50]; + + return _ioctl_call("ethtool", + _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), + SIOCETHTOOL, + shandle->ifindex, + &shandle->fd, + shandle->ifname, + IOCTL_CALL_DATA_TYPE_IFRDATA, + edata, + edata_size, + NULL); +} + +static int +_ethtool_call_once(int ifindex, gpointer edata, gsize edata_size) +{ + char sbuf[50]; + + return _ioctl_call("ethtool", + _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)), + SIOCETHTOOL, + ifindex, + NULL, + NULL, + IOCTL_CALL_DATA_TYPE_IFRDATA, + edata, + edata_size, + NULL); +} + +/*****************************************************************************/ + +static struct ethtool_gstrings * +ethtool_get_stringset(SocketHandle *shandle, int stringset_id) +{ + struct { + struct ethtool_sset_info info; + guint32 sentinel; + } sset_info = { + .info.cmd = ETHTOOL_GSSET_INFO, + .info.reserved = 0, + .info.sset_mask = (1ULL << stringset_id), + }; + const guint32 * pdata; + gs_free struct ethtool_gstrings *gstrings = NULL; + gsize gstrings_len; + guint32 i, len; + + if (_ethtool_call_handle(shandle, &sset_info, sizeof(sset_info)) < 0) + return NULL; + if (!sset_info.info.sset_mask) + return NULL; + + pdata = (guint32 *) sset_info.info.data; + + len = *pdata; + + gstrings_len = sizeof(*gstrings) + (len * ETH_GSTRING_LEN); + gstrings = g_malloc0(gstrings_len); + gstrings->cmd = ETHTOOL_GSTRINGS; + gstrings->string_set = stringset_id; + gstrings->len = len; + if (gstrings->len > 0) { + if (_ethtool_call_handle(shandle, gstrings, gstrings_len) < 0) + return NULL; + for (i = 0; i < gstrings->len; i++) { + /* ensure NUL terminated */ + gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0'; + } + } + + return g_steal_pointer(&gstrings); +} + +static int +ethtool_gstrings_find(const struct ethtool_gstrings *gstrings, const char *needle) +{ + guint32 i; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); + + for (i = 0; i < gstrings->len; i++) { + if (nm_streq((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle)) + return i; + } + return -1; +} + +static int +ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char *needle) +{ + gs_free struct ethtool_gstrings *gstrings = NULL; + + /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN. + * that means, we cannot possibly request longer names. */ + nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN); + + gstrings = ethtool_get_stringset(shandle, stringset_id); + if (gstrings) + return ethtool_gstrings_find(gstrings, needle); + return -1; +} + +/*****************************************************************************/ + +static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = { +#define ETHT_FEAT(eid, ...) \ + { \ + .ethtool_id = eid, .n_kernel_names = NM_NARG(__VA_ARGS__), \ + .kernel_names = ((const char *const[]){__VA_ARGS__}), \ + } + + /* the order does only matter for one thing: if it happens that more than one NMEthtoolID + * reference the same kernel-name, then the one that is mentioned *later* will win in + * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which + * refer to multiple features ("feature-tso"), while also having more specific ids + * ("feature-tx-tcp-segmentation"). */ + + /* names from ethtool utility, which are aliases for multiple features. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", "tx-scatter-gather-fraglist"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TSO, + "tx-tcp-segmentation", + "tx-tcp-ecn-segmentation", + "tx-tcp-mangleid-segmentation", + "tx-tcp6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX, + "tx-checksum-ipv4", + "tx-checksum-ip-generic", + "tx-checksum-ipv6", + "tx-checksum-fcoe-crc", + "tx-checksum-sctp"), + + /* names from ethtool utility, which are aliases for one feature. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"), + + /* names of features, as known by kernel. */ + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION, + "tx-tunnel-remcsum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"), + ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"), +}; + +/* the number of kernel features that we handle. It essentially is the sum of all + * kernel_names. So, all ethtool-ids that reference exactly one kernel-name + * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases + * for multiple kernel-names. */ +#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u) + +static void +_ASSERT_ethtool_feature_infos(void) +{ +#if NM_MORE_ASSERTS > 10 + guint i, k, n; + bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = {}; + + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM); + + n = 0; + for (i = 0; i < G_N_ELEMENTS(_ethtool_feature_infos); i++) { + NMEthtoolFeatureState kstate; + const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i]; + + g_assert(inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST); + g_assert(inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST); + g_assert(inf->n_kernel_names > 0); + + for (k = 0; k < i; k++) + g_assert(inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id); + + g_assert(!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)]); + found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)] = TRUE; + + kstate.idx_kernel_name = inf->n_kernel_names - 1; + g_assert((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1)); + + n += inf->n_kernel_names; + for (k = 0; k < inf->n_kernel_names; k++) { + const char *name = inf->kernel_names[k]; + + g_assert(nm_utils_strv_find_first((char **) inf->kernel_names, k, name) < 0); + + /* these offload features are only informational and cannot be set from user-space + * (NETIF_F_NEVER_CHANGE). We should not track them in _ethtool_feature_infos. */ + g_assert(!nm_streq(name, "netns-local")); + g_assert(!nm_streq(name, "tx-lockless")); + g_assert(!nm_streq(name, "vlan-challenged")); + } + } + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + g_assert(found[i]); + + g_assert(n == N_ETHTOOL_KERNEL_FEATURES); +#endif +} + +static NMEthtoolFeatureStates * +ethtool_get_features(SocketHandle *shandle) +{ + gs_free NMEthtoolFeatureStates * states = NULL; + gs_free struct ethtool_gstrings *ss_features = NULL; + + _ASSERT_ethtool_feature_infos(); + + ss_features = ethtool_get_stringset(shandle, ETH_SS_FEATURES); + if (!ss_features) + return NULL; + + if (ss_features->len > 0) { + gs_free struct ethtool_gfeatures * gfeatures_free = NULL; + struct ethtool_gfeatures * gfeatures; + gsize gfeatures_len; + guint idx; + const NMEthtoolFeatureState * states_list0 = NULL; + const NMEthtoolFeatureState *const *states_plist0 = NULL; + guint states_plist_n = 0; + + gfeatures_len = sizeof(struct ethtool_gfeatures) + + (NM_DIV_ROUND_UP(ss_features->len, 32u) * sizeof(gfeatures->features[0])); + gfeatures = nm_malloc0_maybe_a(300, gfeatures_len, &gfeatures_free); + gfeatures->cmd = ETHTOOL_GFEATURES; + gfeatures->size = NM_DIV_ROUND_UP(ss_features->len, 32u); + if (_ethtool_call_handle(shandle, gfeatures, gfeatures_len) < 0) + return NULL; + + for (idx = 0; idx < G_N_ELEMENTS(_ethtool_feature_infos); idx++) { + const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx]; + guint idx_kernel_name; + + for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) { + NMEthtoolFeatureState *kstate; + const char * kernel_name = info->kernel_names[idx_kernel_name]; + int i_feature; + guint i_block; + guint32 i_flag; + + i_feature = ethtool_gstrings_find(ss_features, kernel_name); + if (i_feature < 0) + continue; + + i_block = ((guint) i_feature) / 32u; + i_flag = (guint32) (1u << (((guint) i_feature) % 32u)); + + if (!states) { + states = g_malloc0( + sizeof(NMEthtoolFeatureStates) + + (N_ETHTOOL_KERNEL_FEATURES * sizeof(NMEthtoolFeatureState)) + + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)) + * sizeof(NMEthtoolFeatureState *))); + states_list0 = &states->states_list[0]; + states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES]; + states->n_ss_features = ss_features->len; + } + + nm_assert(states->n_states < N_ETHTOOL_KERNEL_FEATURES); + kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states]; + states->n_states++; + + kstate->info = info; + kstate->idx_ss_features = i_feature; + kstate->idx_kernel_name = idx_kernel_name; + kstate->available = !!(gfeatures->features[i_block].available & i_flag); + kstate->requested = !!(gfeatures->features[i_block].requested & i_flag); + kstate->active = !!(gfeatures->features[i_block].active & i_flag); + kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag); + + nm_assert(states_plist_n + < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); + + if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) + states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)] = + &states_plist0[states_plist_n]; + ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate; + states_plist_n++; + } + + if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) { + nm_assert(states_plist_n + < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos)); + nm_assert(!states_plist0[states_plist_n]); + states_plist_n++; + } + } + } + + return g_steal_pointer(&states); +} + +NMEthtoolFeatureStates * +nmp_utils_ethtool_get_features(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + NMEthtoolFeatureStates * features; + + g_return_val_if_fail(ifindex > 0, 0); + + features = ethtool_get_features(&shandle); + + if (!features) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting features", + ifindex, + "get-features"); + return NULL; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel features", + ifindex, + "get-features"); + return features; +} + +static const char * +_ethtool_feature_state_to_string(char * buf, + gsize buf_size, + const NMEthtoolFeatureState *s, + const char * prefix) +{ + int l; + + l = g_snprintf(buf, + buf_size, + "%s %s%s", + prefix ?: "", + ONOFF(s->active), + (!s->available || s->never_changed) + ? ", [fixed]" + : ((s->requested != s->active) + ? (s->requested ? ", [requested on]" : ", [requested off]") + : "")); + nm_assert(l < buf_size); + return buf; +} + +gboolean +nmp_utils_ethtool_set_features( + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gs_free struct ethtool_sfeatures * sfeatures_free = NULL; + struct ethtool_sfeatures * sfeatures; + gsize sfeatures_len; + int r; + guint i, j; + struct { + const NMEthtoolFeatureState *f_state; + NMOptionBool requested; + } set_states[N_ETHTOOL_KERNEL_FEATURES]; + guint set_states_n = 0; + gboolean success = TRUE; + + g_return_val_if_fail(ifindex > 0, 0); + g_return_val_if_fail(features, 0); + g_return_val_if_fail(requested, 0); + + nm_assert(features->n_states <= N_ETHTOOL_KERNEL_FEATURES); + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { + const NMEthtoolFeatureState *const *states_indexed; + + if (requested[i] == NM_OPTION_BOOL_DEFAULT) + continue; + + if (!(states_indexed = features->states_indexed[i])) { + if (do_set) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: set feature %s: skip (not found)", + ifindex, + "set-features", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname); + success = FALSE; + } + continue; + } + + for (j = 0; states_indexed[j]; j++) { + const NMEthtoolFeatureState *s = states_indexed[j]; + char sbuf[255]; + + if (set_states_n >= G_N_ELEMENTS(set_states)) + g_return_val_if_reached(FALSE); + + if (s->never_changed) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as " + "never changed)", + ifindex, + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), + _ethtool_feature_state_to_string(sbuf, + sizeof(sbuf), + s, + do_set ? " currently:" : " before:")); + continue; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s feature %s (%s): %s, %s", + ifindex, + "set-features", + do_set ? "set" : "reset", + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + s->info->kernel_names[s->idx_kernel_name], + ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active), + _ethtool_feature_state_to_string(sbuf, + sizeof(sbuf), + s, + do_set ? " currently:" : " before:")); + + if (do_set && (!s->available || s->never_changed) + && (s->active != (requested[i] == NM_OPTION_BOOL_TRUE))) { + /* we request to change a flag which kernel reported as fixed. + * While the ethtool operation will silently succeed, mark the request + * as failure. */ + success = FALSE; + } + + set_states[set_states_n].f_state = s; + set_states[set_states_n].requested = requested[i]; + set_states_n++; + } + } + + if (set_states_n == 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: no feature requested", + ifindex, + "set-features"); + return TRUE; + } + + sfeatures_len = + sizeof(struct ethtool_sfeatures) + + (NM_DIV_ROUND_UP(features->n_ss_features, 32U) * sizeof(sfeatures->features[0])); + sfeatures = nm_malloc0_maybe_a(300, sfeatures_len, &sfeatures_free); + sfeatures->cmd = ETHTOOL_SFEATURES; + sfeatures->size = NM_DIV_ROUND_UP(features->n_ss_features, 32U); + + for (i = 0; i < set_states_n; i++) { + const NMEthtoolFeatureState *s = set_states[i].f_state; + guint i_block; + guint32 i_flag; + gboolean is_requested; + + i_block = s->idx_ss_features / 32u; + i_flag = (guint32) (1u << (s->idx_ss_features % 32u)); + + sfeatures->features[i_block].valid |= i_flag; + + if (do_set) + is_requested = (set_states[i].requested == NM_OPTION_BOOL_TRUE); + else + is_requested = s->active; + + if (is_requested) + sfeatures->features[i_block].requested |= i_flag; + else + sfeatures->features[i_block].requested &= ~i_flag; + } + + r = _ethtool_call_handle(&shandle, sfeatures, sfeatures_len); + if (r < 0) { + success = FALSE; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting features (%s)", + ifindex, + "set-features", + nm_strerror_native(-r)); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: %s", + ifindex, + "set-features", + success ? "successfully setting features" + : "at least some of the features were not successfully set"); + return success; +} + +gboolean +nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce) +{ + struct ethtool_coalesce eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + eth_data.cmd = ETHTOOL_GCOALESCE; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting coalesce settings", + ifindex, + "get-coalesce"); + return FALSE; + } + + *coalesce = (NMEthtoolCoalesceState){ + .s = { + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] = + eth_data.rx_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)] = + eth_data.rx_max_coalesced_frames, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)] = + eth_data.rx_coalesce_usecs_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)] = + eth_data.rx_max_coalesced_frames_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)] = + eth_data.tx_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)] = + eth_data.tx_max_coalesced_frames, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)] = + eth_data.tx_coalesce_usecs_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)] = + eth_data.tx_max_coalesced_frames_irq, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] = + eth_data.stats_block_coalesce_usecs, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)] = + eth_data.use_adaptive_rx_coalesce, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)] = + eth_data.use_adaptive_tx_coalesce, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)] = + eth_data.pkt_rate_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)] = + eth_data.rx_coalesce_usecs_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)] = + eth_data.rx_max_coalesced_frames_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)] = + eth_data.tx_coalesce_usecs_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)] = + eth_data.tx_max_coalesced_frames_low, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)] = + eth_data.pkt_rate_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)] = + eth_data.rx_coalesce_usecs_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)] = + eth_data.rx_max_coalesced_frames_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)] = + eth_data.tx_coalesce_usecs_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)] = + eth_data.tx_max_coalesced_frames_high, + [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)] = + eth_data.rate_sample_interval, + }}; + return TRUE; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel coalesce settings", + ifindex, + "get-coalesce"); + return TRUE; +} + +gboolean +nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce) +{ + struct ethtool_coalesce eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + eth_data = (struct ethtool_coalesce){ + .cmd = ETHTOOL_SCOALESCE, + .rx_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)], + .rx_max_coalesced_frames = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)], + .rx_coalesce_usecs_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)], + .rx_max_coalesced_frames_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)], + .tx_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)], + .tx_max_coalesced_frames = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)], + .tx_coalesce_usecs_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)], + .tx_max_coalesced_frames_irq = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)], + .stats_block_coalesce_usecs = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)], + .use_adaptive_rx_coalesce = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)], + .use_adaptive_tx_coalesce = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)], + .pkt_rate_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)], + .rx_coalesce_usecs_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)], + .rx_max_coalesced_frames_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)], + .tx_coalesce_usecs_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)], + .tx_max_coalesced_frames_low = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)], + .pkt_rate_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)], + .rx_coalesce_usecs_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)], + .rx_max_coalesced_frames_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)], + .tx_coalesce_usecs_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)], + .tx_max_coalesced_frames_high = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)], + .rate_sample_interval = + coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)], + }; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting coalesce settings", + ifindex, + "set-coalesce"); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: set kernel coalesce settings", + ifindex, + "set-coalesce"); + return TRUE; +} + +gboolean +nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring) +{ + struct ethtool_ringparam eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + eth_data.cmd = ETHTOOL_GRINGPARAM; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting ring settings", + ifindex, + "get-ring"); + return FALSE; + } + + *ring = (NMEthtoolRingState){ + .rx_pending = eth_data.rx_pending, + .rx_jumbo_pending = eth_data.rx_jumbo_pending, + .rx_mini_pending = eth_data.rx_mini_pending, + .tx_pending = eth_data.tx_pending, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel ring settings", + ifindex, + "get-ring"); + return TRUE; +} + +gboolean +nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring) +{ + struct ethtool_ringparam eth_data; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + eth_data = (struct ethtool_ringparam){ + .cmd = ETHTOOL_SRINGPARAM, + .rx_pending = ring->rx_pending, + .rx_jumbo_pending = ring->rx_jumbo_pending, + .rx_mini_pending = ring->rx_mini_pending, + .tx_pending = ring->tx_pending, + }; + + if (_ethtool_call_once(ifindex, ð_data, sizeof(eth_data)) < 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting ring settings", + ifindex, + "set-ring"); + return FALSE; + } + + nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring"); + return TRUE; +} + +gboolean +nmp_utils_ethtool_get_pause(int ifindex, NMEthtoolPauseState *pause) +{ + struct ethtool_pauseparam eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); + + eth_data.cmd = ETHTOOL_GPAUSEPARAM; + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure getting pause settings", + ifindex, + "get-pause"); + return FALSE; + } + + *pause = (NMEthtoolPauseState){ + .autoneg = eth_data.autoneg == 1, + .rx = eth_data.rx_pause == 1, + .tx = eth_data.tx_pause == 1, + }; + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: retrieved kernel pause settings", + ifindex, + "get-pause"); + return TRUE; +} + +gboolean +nmp_utils_ethtool_set_pause(int ifindex, const NMEthtoolPauseState *pause) +{ + struct ethtool_pauseparam eth_data; + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); + + eth_data = (struct ethtool_pauseparam){ + .cmd = ETHTOOL_SPAUSEPARAM, + .autoneg = pause->autoneg ? 1 : 0, + .rx_pause = pause->rx ? 1 : 0, + .tx_pause = pause->tx ? 1 : 0, + }; + + if (_ethtool_call_handle(&shandle, ð_data, sizeof(struct ethtool_pauseparam)) != 0) { + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %s: failure setting pause settings", + ifindex, + "set-pause"); + return FALSE; + } + nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel puase settings", ifindex, "set-pause"); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data) +{ + struct ethtool_drvinfo *drvinfo; + + G_STATIC_ASSERT_EXPR(sizeof(*data) == sizeof(*drvinfo)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, driver) + == offsetof(struct ethtool_drvinfo, driver)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, version) + == offsetof(struct ethtool_drvinfo, version)); + G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, fw_version) + == offsetof(struct ethtool_drvinfo, fw_version)); + G_STATIC_ASSERT_EXPR(sizeof(data->driver) == sizeof(drvinfo->driver)); + G_STATIC_ASSERT_EXPR(sizeof(data->version) == sizeof(drvinfo->version)); + G_STATIC_ASSERT_EXPR(sizeof(data->fw_version) == sizeof(drvinfo->fw_version)); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(data, FALSE); + + drvinfo = (struct ethtool_drvinfo *) data; + *drvinfo = (struct ethtool_drvinfo){ + .cmd = ETHTOOL_GDRVINFO, + }; + return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0; +} + +gboolean +nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length) +{ + struct { + struct ethtool_perm_addr e; + guint8 _extra_data[_NM_UTILS_HWADDR_LEN_MAX + 1]; + } edata = { + .e.cmd = ETHTOOL_GPERMADDR, + .e.size = _NM_UTILS_HWADDR_LEN_MAX, + }; + const guint8 *pdata; + + guint i; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) + return FALSE; + + if (edata.e.size > _NM_UTILS_HWADDR_LEN_MAX) + return FALSE; + if (edata.e.size < 1) + return FALSE; + + pdata = (const guint8 *) edata.e.data; + + if (NM_IN_SET(pdata[0], 0, 0xFF)) { + /* Some drivers might return a permanent address of all zeros. + * Reject that (rh#1264024) + * + * Some drivers return a permanent address of all ones. Reject that too */ + for (i = 1; i < edata.e.size; i++) { + if (pdata[0] != pdata[i]) + goto not_all_0or1; + } + return FALSE; + } + +not_all_0or1: + memcpy(buf, pdata, edata.e.size); + *length = edata.e.size; + return TRUE; +} + +gboolean +nmp_utils_ethtool_supports_carrier_detect(int ifindex) +{ + struct ethtool_cmd edata = {.cmd = ETHTOOL_GLINK}; + + g_return_val_if_fail(ifindex > 0, FALSE); + + /* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we + * assume the device supports carrier-detect, otherwise we assume it + * doesn't. + */ + return _ethtool_call_once(ifindex, &edata, sizeof(edata)) >= 0; +} + +gboolean +nmp_utils_ethtool_supports_vlans(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gs_free struct ethtool_gfeatures * features_free = NULL; + struct ethtool_gfeatures * features; + gsize features_len; + int idx, block, bit, size; + + g_return_val_if_fail(ifindex > 0, FALSE); + + idx = ethtool_get_stringset_index(&shandle, ETH_SS_FEATURES, "vlan-challenged"); + if (idx < 0) { + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: vlan-challenged ethtool feature does not exist?", + ifindex); + return FALSE; + } + + block = idx / 32; + bit = idx % 32; + size = block + 1; + + features_len = sizeof(*features) + (size * sizeof(struct ethtool_get_features_block)); + features = nm_malloc0_maybe_a(300, features_len, &features_free); + features->cmd = ETHTOOL_GFEATURES; + features->size = size; + + if (_ethtool_call_handle(&shandle, features, features_len) < 0) + return FALSE; + + return !(features->features[block].active & (1 << bit)); +} + +int +nmp_utils_ethtool_get_peer_ifindex(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + gsize stats_len; + gs_free struct ethtool_stats * stats_free = NULL; + struct ethtool_stats * stats; + int peer_ifindex_stat; + + g_return_val_if_fail(ifindex > 0, 0); + + peer_ifindex_stat = ethtool_get_stringset_index(&shandle, ETH_SS_STATS, "peer_ifindex"); + if (peer_ifindex_stat < 0) { + nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex); + return FALSE; + } + + stats_len = sizeof(*stats) + (peer_ifindex_stat + 1) * sizeof(guint64); + stats = nm_malloc0_maybe_a(300, stats_len, &stats_free); + stats->cmd = ETHTOOL_GSTATS; + stats->n_stats = peer_ifindex_stat + 1; + if (_ethtool_call_handle(&shandle, stats, stats_len) < 0) + return 0; + + return stats->data[peer_ifindex_stat]; +} + +gboolean +nmp_utils_ethtool_get_wake_on_lan(int ifindex) +{ + struct ethtool_wolinfo wol = { + .cmd = ETHTOOL_GWOL, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &wol, sizeof(wol)) < 0) + return FALSE; + + return wol.wolopts != 0; +} + +gboolean +nmp_utils_ethtool_get_link_settings(int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex) +{ + struct ethtool_cmd edata = { + .cmd = ETHTOOL_GSET, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0) + return FALSE; + + NM_SET_OUT(out_autoneg, (edata.autoneg == AUTONEG_ENABLE)); + + if (out_speed) { + guint32 speed; + + speed = ethtool_cmd_speed(&edata); + if (speed == G_MAXUINT16 || speed == G_MAXUINT32) + speed = 0; + + *out_speed = speed; + } + + if (out_duplex) { + switch (edata.duplex) { + case DUPLEX_HALF: + *out_duplex = NM_PLATFORM_LINK_DUPLEX_HALF; + break; + case DUPLEX_FULL: + *out_duplex = NM_PLATFORM_LINK_DUPLEX_FULL; + break; + default: /* DUPLEX_UNKNOWN */ + *out_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN; + break; + } + } + + return TRUE; +} + +#define ADVERTISED_INVALID 0 + +static guint32 +get_baset_mode(guint32 speed, NMPlatformLinkDuplexType duplex) +{ + if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN) + return ADVERTISED_INVALID; + + if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) { + switch (speed) { + case 10: + return ADVERTISED_10baseT_Half; + case 100: + return ADVERTISED_100baseT_Half; + case 1000: + return ADVERTISED_1000baseT_Half; + default: + return ADVERTISED_INVALID; + } + } else { + switch (speed) { + case 10: + return ADVERTISED_10baseT_Full; + case 100: + return ADVERTISED_100baseT_Full; + case 1000: + return ADVERTISED_1000baseT_Full; + case 10000: + return ADVERTISED_10000baseT_Full; + default: + return ADVERTISED_INVALID; + } + } +} + +static gboolean +platform_link_duplex_type_to_native(NMPlatformLinkDuplexType duplex_type, guint8 *out_native) +{ + switch (duplex_type) { + case NM_PLATFORM_LINK_DUPLEX_HALF: + *out_native = DUPLEX_HALF; + return TRUE; + case NM_PLATFORM_LINK_DUPLEX_FULL: + *out_native = DUPLEX_FULL; + return TRUE; + case NM_PLATFORM_LINK_DUPLEX_UNKNOWN: + return FALSE; + default: + g_return_val_if_reached(FALSE); + } +} + +const guint8 _nmp_link_mode_all_advertised_modes_bits[] = { + ETHTOOL_LINK_MODE_10baseT_Half_BIT, + ETHTOOL_LINK_MODE_10baseT_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, + ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT, + ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, + ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT, + ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, + /* 32 bit flags start here. */ + ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, + ETHTOOL_LINK_MODE_10000baseER_Full_BIT, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, + ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT, + ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT, + ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT1_Full_BIT, + ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT, + ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT, + ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT, + ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT, + ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseDR_Full_BIT, + ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT, + ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT, + ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT, + ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT, + ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_100baseFX_Half_BIT, + ETHTOOL_LINK_MODE_100baseFX_Full_BIT, +}; + +/* these are the bits from _nmp_link_mode_all_advertised_modes_bits set. */ +const guint32 _nmp_link_mode_all_advertised_modes[] = { + 0xfffe903fu, + 0xfff1ffffu, + 0x0ffffbffu, +}; + +static NMOptionBool +set_link_settings_new(SocketHandle * shandle, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + struct ethtool_link_settings edata0; + gs_free struct ethtool_link_settings *edata = NULL; + gsize edata_size; + guint nwords; + guint i; + + edata0 = (struct ethtool_link_settings){ + .cmd = ETHTOOL_GLINKSETTINGS, + .link_mode_masks_nwords = 0, + }; + + /* perform the handshake to find the size of masks */ + if (_ethtool_call_handle(shandle, &edata0, sizeof(edata0)) < 0 + || edata0.link_mode_masks_nwords >= 0) { + /* new API not supported */ + return NM_OPTION_BOOL_DEFAULT; + } + + nwords = -edata0.link_mode_masks_nwords; + edata_size = sizeof(*edata) + sizeof(guint32) * nwords * 3; + edata = g_malloc0(edata_size); + edata->cmd = ETHTOOL_GLINKSETTINGS; + edata->link_mode_masks_nwords = nwords; + + /* retrieve first current settings */ + if (_ethtool_call_handle(shandle, edata, edata_size) < 0) + return FALSE; + + /* then change the needed ones */ + edata->cmd = ETHTOOL_SLINKSETTINGS; + + { + const guint32 *v_map_supported = &edata->link_mode_masks[0]; + guint32 * v_map_advertising = &edata->link_mode_masks[nwords]; + guint32 * v_map_lp_advertising = &edata->link_mode_masks[2 * nwords]; + + memcpy(v_map_advertising, v_map_supported, sizeof(guint32) * nwords); + (void) v_map_lp_advertising; + + if (speed != 0) { + guint32 mode; + + mode = get_baset_mode(speed, duplex); + + if (mode == ADVERTISED_INVALID) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", + shandle->ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + + if (!(v_map_supported[0] & mode)) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: device does not support %uBASE-T %s duplex mode", + shandle->ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + + for (i = 0; i < (guint) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes); i++) + v_map_advertising[i] &= ~_nmp_link_mode_all_advertised_modes[i]; + v_map_advertising[0] |= mode; + } + } + +set_autoneg: + if (autoneg) + edata->autoneg = AUTONEG_ENABLE; + else { + edata->autoneg = AUTONEG_DISABLE; + + if (speed) + edata->speed = speed; + + platform_link_duplex_type_to_native(duplex, &edata->duplex); + } + + return _ethtool_call_handle(shandle, edata, edata_size) >= 0; +} + +gboolean +nmp_utils_ethtool_set_link_settings(int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + struct ethtool_cmd edata = { + .cmd = ETHTOOL_GSET, + }; + NMOptionBool ret; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail((speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN) + || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), + FALSE); + + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: set link: autoneg=%d, speed=%d, duplex=%s", + ifindex, + autoneg, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + + ret = set_link_settings_new(&shandle, autoneg, speed, duplex); + if (ret != NM_OPTION_BOOL_DEFAULT) + return ret; + + /* new ETHTOOL_GLINKSETTINGS API not supported, fall back to GSET */ + + /* retrieve first current settings */ + if (_ethtool_call_handle(&shandle, &edata, sizeof(edata)) < 0) + return FALSE; + + /* then change the needed ones */ + edata.cmd = ETHTOOL_SSET; + + edata.advertising = edata.supported; + if (speed != 0) { + guint32 mode; + + mode = get_baset_mode(speed, duplex); + + if (mode == ADVERTISED_INVALID) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised", + ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + if (!(edata.supported & mode)) { + if (!autoneg) + goto set_autoneg; + nm_log_trace(LOGD_PLATFORM, + "ethtool[%d]: device does not support %uBASE-T %s duplex mode", + ifindex, + speed, + nm_platform_link_duplex_type_to_string(duplex)); + return FALSE; + } + edata.advertising &= ~_nmp_link_mode_all_advertised_modes[0]; + edata.advertising |= mode; + } + +set_autoneg: + if (autoneg) + edata.autoneg = AUTONEG_ENABLE; + else { + edata.autoneg = AUTONEG_DISABLE; + + if (speed) + ethtool_cmd_speed_set(&edata, speed); + + platform_link_duplex_type_to_native(duplex, &edata.duplex); + } + + return _ethtool_call_handle(&shandle, &edata, sizeof(edata)) >= 0; +} + +gboolean +nmp_utils_ethtool_set_wake_on_lan(int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password) +{ + struct ethtool_wolinfo wol_info = { + .cmd = ETHTOOL_SWOL, + .wolopts = 0, + }; + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (wol == _NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE) + return TRUE; + + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'", + ifindex, + (unsigned) wol, + wol_password); + + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_PHY)) + wol_info.wolopts |= WAKE_PHY; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST)) + wol_info.wolopts |= WAKE_UCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST)) + wol_info.wolopts |= WAKE_MCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST)) + wol_info.wolopts |= WAKE_BCAST; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_ARP)) + wol_info.wolopts |= WAKE_ARP; + if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) + wol_info.wolopts |= WAKE_MAGIC; + + if (wol_password) { + if (!_nm_utils_hwaddr_aton_exact(wol_password, wol_info.sopass, ETH_ALEN)) { + nm_log_dbg(LOGD_PLATFORM, + "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", + ifindex, + wol_password); + return FALSE; + } + wol_info.wolopts |= WAKE_MAGICSECURE; + } + + return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0; +} + +/****************************************************************************** + * mii + *****************************************************************************/ + +gboolean +nmp_utils_mii_supports_carrier_detect(int ifindex) +{ + nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex); + int r; + struct ifreq ifr; + struct mii_ioctl_data * mii; + + g_return_val_if_fail(ifindex > 0, FALSE); + + r = _ioctl_call("mii", + "SIOCGMIIPHY", + SIOCGMIIPHY, + shandle.ifindex, + &shandle.fd, + shandle.ifname, + IOCTL_CALL_DATA_TYPE_NONE, + NULL, + 0, + &ifr); + if (r < 0) + return FALSE; + + /* If we can read the BMSR register, we assume that the card supports MII link detection */ + mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; + mii->reg_num = MII_BMSR; + + r = _ioctl_call("mii", + "SIOCGMIIREG", + SIOCGMIIREG, + shandle.ifindex, + &shandle.fd, + shandle.ifname, + IOCTL_CALL_DATA_TYPE_IFRU, + mii, + sizeof(*mii), + &ifr); + if (r < 0) + return FALSE; + + mii = (struct mii_ioctl_data *) &ifr.ifr_ifru; + nm_log_trace(LOGD_PLATFORM, + "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", + ifindex, + shandle.ifname, + mii->val_out); + return TRUE; +} + +/****************************************************************************** + * udev + *****************************************************************************/ + +const char * +nmp_utils_udev_get_driver(struct udev_device *udevice) +{ + struct udev_device *parent = NULL, *grandparent = NULL; + const char * driver, *subsys; + + driver = udev_device_get_driver(udevice); + if (driver) + goto out; + + /* Try the parent */ + parent = udev_device_get_parent(udevice); + if (parent) { + driver = udev_device_get_driver(parent); + if (!driver) { + /* Try the grandparent if it's an ibmebus device or if the + * subsys is NULL which usually indicates some sort of + * platform device like a 'gadget' net interface. + */ + subsys = udev_device_get_subsystem(parent); + if ((g_strcmp0(subsys, "ibmebus") == 0) || (subsys == NULL)) { + grandparent = udev_device_get_parent(parent); + if (grandparent) + driver = udev_device_get_driver(grandparent); + } + } + } + +out: + /* Intern the string so we don't have to worry about memory + * management in NMPlatformLink. */ + return g_intern_string(driver); +} + +/****************************************************************************** + * utils + *****************************************************************************/ + +NMIPConfigSource +nmp_utils_ip_config_source_from_rtprot(guint8 rtprot) +{ + return ((int) rtprot) + 1; +} + +NMIPConfigSource +nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source) +{ + /* when adding a route to kernel for a give @source, the resulting route + * will be put into the cache with a source of NM_IP_CONFIG_SOURCE_RTPROT_*. + * This function returns that. */ + return nmp_utils_ip_config_source_from_rtprot( + nmp_utils_ip_config_source_coerce_to_rtprot(source)); +} + +guint8 +nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source) +{ + /* when adding a route to kernel, we coerce the @source field + * to rtm_protocol. This is not lossless as we map different + * source values to the same RTPROT uint8 value. */ + if (source <= NM_IP_CONFIG_SOURCE_UNKNOWN) + return RTPROT_UNSPEC; + + if (source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST) + return source - 1; + + switch (source) { + case NM_IP_CONFIG_SOURCE_KERNEL: + return RTPROT_KERNEL; + case NM_IP_CONFIG_SOURCE_IP6LL: + return RTPROT_KERNEL; + case NM_IP_CONFIG_SOURCE_DHCP: + return RTPROT_DHCP; + case NM_IP_CONFIG_SOURCE_NDISC: + return RTPROT_RA; + + default: + return RTPROT_STATIC; + } +} + +NMIPConfigSource +nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source) +{ + /* When we receive a route from kernel and put it into the platform cache, + * we preserve the protocol field by converting it to a NMIPConfigSource + * via nmp_utils_ip_config_source_from_rtprot(). + * + * However, that is not the inverse of nmp_utils_ip_config_source_coerce_to_rtprot(). + * Instead, to go back to the original value, you need another step: + * nmp_utils_ip_config_source_coerce_from_rtprot (nmp_utils_ip_config_source_from_rtprot (rtprot)). + * + * This might partly restore the original source value, but of course that + * is not really possible because nmp_utils_ip_config_source_coerce_to_rtprot() + * is not injective. + * */ + switch (source) { + case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC: + return NM_IP_CONFIG_SOURCE_UNKNOWN; + + case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL: + case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT: + return NM_IP_CONFIG_SOURCE_KERNEL; + + case NM_IP_CONFIG_SOURCE_RTPROT_RA: + return NM_IP_CONFIG_SOURCE_NDISC; + + case NM_IP_CONFIG_SOURCE_RTPROT_DHCP: + return NM_IP_CONFIG_SOURCE_DHCP; + + default: + return NM_IP_CONFIG_SOURCE_USER; + } +} + +const char * +nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len) +{ + const char *s = NULL; + nm_utils_to_string_buffer_init(&buf, &len); + + if (!len) + return buf; + + switch (source) { + case NM_IP_CONFIG_SOURCE_UNKNOWN: + s = "unknown"; + break; + + case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC: + s = "rt-unspec"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT: + s = "rt-redirect"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL: + s = "rt-kernel"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_BOOT: + s = "rt-boot"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_STATIC: + s = "rt-static"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_DHCP: + s = "rt-dhcp"; + break; + case NM_IP_CONFIG_SOURCE_RTPROT_RA: + s = "rt-ra"; + break; + + case NM_IP_CONFIG_SOURCE_KERNEL: + s = "kernel"; + break; + case NM_IP_CONFIG_SOURCE_SHARED: + s = "shared"; + break; + case NM_IP_CONFIG_SOURCE_IP4LL: + s = "ipv4ll"; + break; + case NM_IP_CONFIG_SOURCE_IP6LL: + s = "ipv6ll"; + break; + case NM_IP_CONFIG_SOURCE_PPP: + s = "ppp"; + break; + case NM_IP_CONFIG_SOURCE_WWAN: + s = "wwan"; + break; + case NM_IP_CONFIG_SOURCE_VPN: + s = "vpn"; + break; + case NM_IP_CONFIG_SOURCE_DHCP: + s = "dhcp"; + break; + case NM_IP_CONFIG_SOURCE_NDISC: + s = "ndisc"; + break; + case NM_IP_CONFIG_SOURCE_USER: + s = "user"; + break; + default: + break; + } + + if (source >= 1 && source <= 0x100) { + if (s) + g_snprintf(buf, len, "%s", s); + else + g_snprintf(buf, len, "rt-%d", ((int) source) - 1); + } else { + if (s) + g_strlcpy(buf, s, len); + else + g_snprintf(buf, len, "(%d)", source); + } + return buf; +} + +/** + * nmp_utils_sysctl_open_netdir: + * @ifindex: the ifindex for which to open "/sys/class/net/%s" + * @ifname_guess: (allow-none): optional argument, if present used as initial + * guess as the current name for @ifindex. If guessed right, + * it saves an additional if_indextoname() call. + * @out_ifname: (allow-none): if present, must be at least IFNAMSIZ + * characters. On success, this will contain the actual ifname + * found while opening the directory. + * + * Returns: a negative value on failure, on success returns the open fd + * to the "/sys/class/net/%s" directory for @ifindex. + */ +int +nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname) +{ +#define SYS_CLASS_NET "/sys/class/net/" + const char *ifname = ifname_guess; + char ifname_buf_last_try[IFNAMSIZ]; + char ifname_buf[IFNAMSIZ]; + guint try_count = 0; + char sysdir[NM_STRLEN(SYS_CLASS_NET) + IFNAMSIZ] = SYS_CLASS_NET; + char fd_buf[256]; + ssize_t nn; + + g_return_val_if_fail(ifindex >= 0, -1); + + ifname_buf_last_try[0] = '\0'; + + for (try_count = 0; try_count < 10; try_count++, ifname = NULL) { + nm_auto_close int fd_dir = -1; + nm_auto_close int fd_ifindex = -1; + + if (!ifname) { + ifname = nmp_utils_if_indextoname(ifindex, ifname_buf); + if (!ifname) + return -1; + } + + nm_assert(nm_utils_ifname_valid_kernel(ifname, NULL)); + + if (g_strlcpy(&sysdir[NM_STRLEN(SYS_CLASS_NET)], ifname, IFNAMSIZ) >= IFNAMSIZ) + g_return_val_if_reached(-1); + + /* we only retry, if the name changed since previous attempt. + * Hence, it is extremely unlikely that this loop runes until the + * end of the @try_count. */ + if (nm_streq(ifname, ifname_buf_last_try)) + return -1; + + if (g_strlcpy(ifname_buf_last_try, ifname, IFNAMSIZ) >= IFNAMSIZ) + nm_assert_not_reached(); + + fd_dir = open(sysdir, O_DIRECTORY | O_CLOEXEC); + if (fd_dir < 0) + continue; + + fd_ifindex = openat(fd_dir, "ifindex", O_CLOEXEC); + if (fd_ifindex < 0) + continue; + + nn = nm_utils_fd_read_loop(fd_ifindex, fd_buf, sizeof(fd_buf) - 2, FALSE); + if (nn <= 0) + continue; + fd_buf[nn] = '\0'; + + if (ifindex != (int) _nm_utils_ascii_str_to_int64(fd_buf, 10, 1, G_MAXINT, -1)) + continue; + + if (out_ifname) + strcpy(out_ifname, ifname); + + return nm_steal_fd(&fd_dir); + } + + return -1; +} + +/*****************************************************************************/ + +char * +nmp_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id) +{ + guint id_len; + gsize parent_len; + char *ifname; + + g_return_val_if_fail(parent_iface && *parent_iface, NULL); + + if (vlan_id < 10) + id_len = 2; + else if (vlan_id < 100) + id_len = 3; + else if (vlan_id < 1000) + id_len = 4; + else { + g_return_val_if_fail(vlan_id < 4095, NULL); + id_len = 5; + } + + ifname = g_new(char, IFNAMSIZ); + + parent_len = strlen(parent_iface); + parent_len = MIN(parent_len, IFNAMSIZ - 1 - id_len); + memcpy(ifname, parent_iface, parent_len); + g_snprintf(&ifname[parent_len], IFNAMSIZ - parent_len, ".%u", vlan_id); + + return ifname; +} + +/*****************************************************************************/ + +/* nmp_utils_new_infiniband_name: + * @name: the output-buffer where the value will be written. Must be + * not %NULL and point to a string buffer of at least IFNAMSIZ bytes. + * @parent_name: the parent interface name + * @p_key: the partition key. + * + * Returns: the infiniband name will be written to @name and @name + * is returned. + */ +const char * +nmp_utils_new_infiniband_name(char *name, const char *parent_name, int p_key) +{ + g_return_val_if_fail(name, NULL); + g_return_val_if_fail(parent_name && parent_name[0], NULL); + g_return_val_if_fail(strlen(parent_name) < IFNAMSIZ, NULL); + + /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't + * want to assert against that in nmp_utils_new_infiniband_name(). So be more + * resilient here, and accept those. */ + g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, NULL); + + /* If parent+suffix is too long, kernel would just truncate + * the name. We do the same. See ipoib_vlan_add(). */ + g_snprintf(name, IFNAMSIZ, "%s.%04x", parent_name, p_key); + return name; +} + +/*****************************************************************************/ + +/** + * Takes a pair @timestamp and @duration, and returns the remaining duration based + * on the new timestamp @now. + */ +guint32 +nmp_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now) +{ + gint64 t; + + nm_assert(now >= 0); + + if (duration == NM_PLATFORM_LIFETIME_PERMANENT) + return NM_PLATFORM_LIFETIME_PERMANENT; + + if (timestamp == 0) { + /* if the @timestamp is zero, assume it was just left unset and that the relative + * @duration starts counting from @now. This is convenient to construct an address + * and print it in nm_platform_ip4_address_to_string(). + * + * In general it does not make sense to set the @duration without anchoring at + * @timestamp because you don't know the absolute expiration time when looking + * at the address at a later moment. */ + timestamp = now; + } + + /* For timestamp > now, just accept it and calculate the expected(?) result. */ + t = (gint64) timestamp + (gint64) duration - (gint64) now; + + if (t <= 0) + return 0; + if (t >= NM_PLATFORM_LIFETIME_PERMANENT) + return NM_PLATFORM_LIFETIME_PERMANENT - 1; + return t; +} + +guint32 +nmp_utils_lifetime_get(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + gint32 now, + guint32 *out_preferred) +{ + guint32 t_lifetime, t_preferred; + + nm_assert(now >= 0); + + if (timestamp == 0 && lifetime == 0) { + /* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy + * creation of such addresses (without requiring to set the lifetime fields to + * NM_PLATFORM_LIFETIME_PERMANENT). The real lifetime==0 addresses (E.g. DHCP6 telling us + * to drop an address will have timestamp set. + */ + NM_SET_OUT(out_preferred, NM_PLATFORM_LIFETIME_PERMANENT); + g_return_val_if_fail(preferred == 0, NM_PLATFORM_LIFETIME_PERMANENT); + return NM_PLATFORM_LIFETIME_PERMANENT; + } + + if (now <= 0) + now = nm_utils_get_monotonic_timestamp_sec(); + + t_lifetime = nmp_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now); + if (!t_lifetime) { + NM_SET_OUT(out_preferred, 0); + return 0; + } + + t_preferred = nmp_utils_lifetime_rebase_relative_time_on_now(timestamp, preferred, now); + + NM_SET_OUT(out_preferred, MIN(t_preferred, t_lifetime)); + + /* Assert that non-permanent addresses have a (positive) @timestamp. nmp_utils_lifetime_rebase_relative_time_on_now() + * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always + * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew. + */ + g_return_val_if_fail(timestamp != 0 + || (lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && preferred == NM_PLATFORM_LIFETIME_PERMANENT), + t_lifetime); + g_return_val_if_fail(t_preferred <= t_lifetime, t_lifetime); + + return t_lifetime; +} + +/*****************************************************************************/ + +static const char * +_trunk_first_line(char *str) +{ + char *s; + + s = strchr(str, '\n'); + if (s) + s[0] = '\0'; + return str; +} + +int +nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) +{ + gs_unref_ptrarray GPtrArray *argv = NULL; + int exit_status; + gs_free char * _log_str = NULL; +#define ARGV_TO_STR(argv) \ + (_log_str ? _log_str : (_log_str = g_strjoinv(" ", (char **) argv->pdata))) + GError * local = NULL; + va_list ap; + NMLogLevel llevel = suppress_error_logging ? LOGL_DEBUG : LOGL_ERR; + gs_free char *std_out = NULL, *std_err = NULL; + + g_return_val_if_fail(!error || !*error, -1); + g_return_val_if_fail(arg1, -1); + + /* construct the argument list */ + argv = g_ptr_array_sized_new(4); + g_ptr_array_add(argv, "/sbin/modprobe"); + g_ptr_array_add(argv, "--use-blacklist"); + g_ptr_array_add(argv, (char *) arg1); + + va_start(ap, arg1); + while ((arg1 = va_arg(ap, const char *))) + g_ptr_array_add(argv, (char *) arg1); + va_end(ap); + + g_ptr_array_add(argv, NULL); + + nm_log_dbg(LOGD_CORE, "modprobe: '%s'", ARGV_TO_STR(argv)); + if (!g_spawn_sync(NULL, + (char **) argv->pdata, + NULL, + 0, + NULL, + NULL, + &std_out, + &std_err, + &exit_status, + &local)) { + nm_log(llevel, + LOGD_CORE, + NULL, + NULL, + "modprobe: '%s' failed: %s", + ARGV_TO_STR(argv), + local->message); + g_propagate_error(error, local); + return -1; + } else if (exit_status != 0) { + nm_log(llevel, + LOGD_CORE, + NULL, + NULL, + "modprobe: '%s' exited with error %d%s%s%s%s%s%s", + ARGV_TO_STR(argv), + exit_status, + std_out && *std_out ? " (" : "", + std_out && *std_out ? _trunk_first_line(std_out) : "", + std_out && *std_out ? ")" : "", + std_err && *std_err ? " (" : "", + std_err && *std_err ? _trunk_first_line(std_err) : "", + std_err && *std_err ? ")" : ""); + } + + return exit_status; +} diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h new file mode 100644 index 00000000..5511e8af --- /dev/null +++ b/src/libnm-platform/nm-platform-utils.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_PLATFORM_UTILS_H__ +#define __NM_PLATFORM_UTILS_H__ + +#include "libnm-base/nm-base.h" +#include "libnm-platform/nmp-base.h" + +/*****************************************************************************/ + +const char *nmp_utils_ethtool_get_driver(int ifindex); +gboolean nmp_utils_ethtool_supports_carrier_detect(int ifindex); +gboolean nmp_utils_ethtool_supports_vlans(int ifindex); +int nmp_utils_ethtool_get_peer_ifindex(int ifindex); +gboolean nmp_utils_ethtool_get_wake_on_lan(int ifindex); +gboolean nmp_utils_ethtool_set_wake_on_lan(int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password); + +const char *nm_platform_link_duplex_type_to_string(NMPlatformLinkDuplexType duplex); + +extern const guint8 _nmp_link_mode_all_advertised_modes_bits[79]; +extern const guint32 _nmp_link_mode_all_advertised_modes[3]; + +gboolean nmp_utils_ethtool_get_link_settings(int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex); +gboolean nmp_utils_ethtool_set_link_settings(int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex); + +gboolean nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length); + +gboolean nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data); + +NMEthtoolFeatureStates *nmp_utils_ethtool_get_features(int ifindex); + +gboolean nmp_utils_ethtool_set_features( + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */); + +gboolean nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce); + +gboolean nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce); + +gboolean nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring); + +gboolean nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring); + +gboolean nmp_utils_ethtool_get_pause(int ifindex, NMEthtoolPauseState *pause); + +gboolean nmp_utils_ethtool_set_pause(int ifindex, const NMEthtoolPauseState *pause); + +/*****************************************************************************/ + +gboolean nmp_utils_mii_supports_carrier_detect(int ifindex); + +struct udev_device; + +const char *nmp_utils_udev_get_driver(struct udev_device *udevice); + +NMIPConfigSource nmp_utils_ip_config_source_from_rtprot(guint8 rtprot) _nm_const; +guint8 nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source) _nm_const; +NMIPConfigSource nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source) _nm_const; +NMIPConfigSource nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source) _nm_const; +const char *nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len); + +const char *nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/); +int nmp_utils_if_nametoindex(const char *ifname); + +int nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname); + +char * nmp_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id); +const char *nmp_utils_new_infiniband_name(char *name, const char *parent_name, int p_key); + +guint32 +nmp_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now); + +guint32 nmp_utils_lifetime_get(guint32 timestamp, + guint32 lifetime, + guint32 preferred, + gint32 now, + guint32 *out_preferred); + +int nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) + G_GNUC_NULL_TERMINATED; + +#endif /* __NM_PLATFORM_UTILS_H__ */ diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c new file mode 100644 index 00000000..b7a65df5 --- /dev/null +++ b/src/libnm-platform/nm-platform.c @@ -0,0 +1,9042 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2012 - 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-platform.h" + +#include <stdlib.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#include <linux/fib_rules.h> +#include <linux/ip.h> +#include <linux/if.h> +#include <linux/if_tun.h> +#include <linux/if_tunnel.h> +#include <linux/rtnetlink.h> +#include <linux/tc_act/tc_mirred.h> +#include <libudev.h> + +#include "libnm-base/nm-net-aux.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-platform-utils.h" +#include "libnm-platform/nmp-netns.h" +#include "libnm-udev-aux/nm-udev-utils.h" +#include "nm-platform-private.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Route, network)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Route, network)); + +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP4Route)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP6Route)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIPXRoute)); + +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP4Address)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP6Address)); +G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIPXAddress)); + +/*****************************************************************************/ + +G_STATIC_ASSERT(sizeof(((NMPLinkAddress *) NULL)->data) == _NM_UTILS_HWADDR_LEN_MAX); +G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_address.data) == _NM_UTILS_HWADDR_LEN_MAX); +G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_broadcast.data) == _NM_UTILS_HWADDR_LEN_MAX); + +static const char * +_nmp_link_address_to_string(const NMPLinkAddress *addr, + char buf[static(_NM_UTILS_HWADDR_LEN_MAX * 3)]) +{ + nm_assert(addr); + + if (addr->len > 0) { + if (!_nm_utils_hwaddr_ntoa(addr->data, + addr->len, + TRUE, + buf, + _NM_UTILS_HWADDR_LEN_MAX * 3)) { + buf[0] = '\0'; + g_return_val_if_reached(buf); + } + } else + buf[0] = '\0'; + + return buf; +} + +gconstpointer +nmp_link_address_get(const NMPLinkAddress *addr, size_t *length) +{ + if (!addr || addr->len <= 0) { + NM_SET_OUT(length, 0); + return NULL; + } + + if (addr->len > _NM_UTILS_HWADDR_LEN_MAX) { + NM_SET_OUT(length, 0); + g_return_val_if_reached(NULL); + } + + NM_SET_OUT(length, addr->len); + return addr->data; +} + +GBytes * +nmp_link_address_get_as_bytes(const NMPLinkAddress *addr) +{ + gconstpointer data; + size_t length; + + data = nmp_link_address_get(addr, &length); + + return length > 0 ? g_bytes_new(data, length) : NULL; +} + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "platform" + +#define NMLOG_COMMON(level, name, ...) \ + char __prefix[32]; \ + const char * __p_prefix = _NMLOG_PREFIX_NAME; \ + const NMPlatform *const __self = (self); \ + const char * __name = name; \ + \ + if (__self && NM_PLATFORM_GET_PRIVATE(__self)->log_with_ptr) { \ + g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ + __p_prefix = __prefix; \ + } \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + __name, \ + NULL, \ + "%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __p_prefix, \ + NM_PRINT_FMT_QUOTED(__name, "(", __name, ") ", "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, NULL, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _NMLOG2(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, name, __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define _NMLOG3(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + NMLOG_COMMON(level, \ + ifindex > 0 ? nm_platform_link_get_name(self, ifindex) : NULL, \ + __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = {0}; + +enum { + PROP_0, + PROP_NETNS_SUPPORT, + PROP_USE_UDEV, + PROP_LOG_WITH_PTR, + LAST_PROP, +}; + +typedef struct _NMPlatformPrivate { + bool use_udev : 1; + bool log_with_ptr : 1; + + guint ip4_dev_route_blacklist_check_id; + guint ip4_dev_route_blacklist_gc_timeout_id; + GHashTable * ip4_dev_route_blacklist_hash; + NMDedupMultiIndex *multi_idx; + NMPCache * cache; +} NMPlatformPrivate; + +G_DEFINE_TYPE(NMPlatform, nm_platform, G_TYPE_OBJECT) + +#define NM_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMPlatform, NM_IS_PLATFORM) + +/*****************************************************************************/ + +static void _ip4_dev_route_blacklist_schedule(NMPlatform *self); + +/*****************************************************************************/ + +gboolean +nm_platform_get_use_udev(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->use_udev; +} + +gboolean +nm_platform_get_log_with_ptr(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->log_with_ptr; +} + +/*****************************************************************************/ + +guint +_nm_platform_signal_id_get(NMPlatformSignalIdType signal_type) +{ + nm_assert(signal_type > 0 && signal_type != NM_PLATFORM_SIGNAL_ID_NONE + && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST); + + return signals[signal_type]; +} + +/*****************************************************************************/ + +/* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS() + * is only a plain read on the self instance, which the compiler + * like can optimize out. + */ +#define _CHECK_SELF_VOID(self, klass) \ + NMPlatformClass *klass; \ + do { \ + g_return_if_fail(NM_IS_PLATFORM(self)); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + } while (0) + +#define _CHECK_SELF(self, klass, err_val) \ + NMPlatformClass *klass; \ + do { \ + g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + } while (0) + +#define _CHECK_SELF_NETNS(self, klass, netns, err_val) \ + nm_auto_pop_netns NMPNetns *netns = NULL; \ + NMPlatformClass * klass; \ + do { \ + g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \ + klass = NM_PLATFORM_GET_CLASS(self); \ + (void) klass; \ + if (!nm_platform_netns_push(self, &netns)) \ + return (err_val); \ + } while (0) + +/*****************************************************************************/ + +NMDedupMultiIndex * +nm_platform_get_multi_idx(NMPlatform *self) +{ + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + + return NM_PLATFORM_GET_PRIVATE(self)->multi_idx; +} + +/*****************************************************************************/ + +static NM_UTILS_LOOKUP_STR_DEFINE( + _nmp_nlm_flag_to_string_lookup, + NMPNlmFlags, + NM_UTILS_LOOKUP_DEFAULT(NULL), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_ADD, "add"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_CHANGE, "change"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_REPLACE, "replace"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_PREPEND, "prepend"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_APPEND, "append"), + NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_TEST, "test"), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_APPEND), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_FMASK), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE), + NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_ECHO), ); + +#define _nmp_nlm_flag_to_string(flags) \ + ({ \ + NMPNlmFlags _flags = (flags); \ + \ + _nmp_nlm_flag_to_string_lookup(flags) \ + ?: nm_sprintf_bufa(100, "new[0x%x]", (unsigned) _flags); \ + }) + +/*****************************************************************************/ + +volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = {}; + +static const struct { + bool compile_time_default; + const char *name; + const char *desc; +} _nm_platform_kernel_support_info[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = { + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS] = + { + .compile_time_default = TRUE, + .name = "EXTENDED_IFA_FLAGS", + .desc = "IPv6 temporary addresses support", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL] = + { + .compile_time_default = TRUE, + .name = "USER_IPV6LL", + .desc = "IFLA_INET6_ADDR_GEN_MODE support", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF] = + { + .compile_time_default = (RTA_MAX >= 20 /* RTA_PREF */), + .name = "RTA_PREF", + .desc = "ability to set router preference for IPv6 routes", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV] = + { + .compile_time_default = (FRA_MAX >= 19 /* FRA_L3MDEV */), + .name = "FRA_L3MDEV", + .desc = "FRA_L3MDEV attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE] = + { + .compile_time_default = (FRA_MAX >= 20 /* FRA_UID_RANGE */), + .name = "FRA_UID_RANGE", + .desc = "FRA_UID_RANGE attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL] = + { + .compile_time_default = (FRA_MAX >= 21 /* FRA_PROTOCOL */), + .name = "FRA_PROTOCOL", + .desc = "FRA_PROTOCOL attribute for policy routing rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO] = + { + .compile_time_default = (FRA_MAX >= 22 /* FRA_IP_PROTO */), + .name = "FRA_IP_PROTO", + .desc = "FRA_IP_PROTO, FRA_SPORT_RANGE, FRA_DPORT_RANGE attributes for policy routing " + "rules", + }, + [NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED] = + { + .compile_time_default = (IFLA_BR_MAX >= 41 /* IFLA_BR_VLAN_STATS_ENABLED */), + .name = "IFLA_BR_VLAN_STATS_ENABLE", + .desc = "IFLA_BR_VLAN_STATS_ENABLE bridge link attribute", + }, +}; + +int +_nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value) +{ + volatile int *p_state; + gboolean set_default = FALSE; + + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + p_state = &_nm_platform_kernel_support_state[type]; + + if (value == 0) { + set_default = TRUE; + value = _nm_platform_kernel_support_info[type].compile_time_default ? 1 : -1; + } + + nm_assert(NM_IN_SET(value, -1, 1)); + + if (!g_atomic_int_compare_and_exchange(p_state, 0, value)) { + value = g_atomic_int_get(p_state); + nm_assert(NM_IN_SET(value, -1, 1)); + return value; + } + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 0 + + if (set_default) { + nm_log_dbg(LOGD_PLATFORM, + "platform: kernel-support for %s (%s) not detected: assume %ssupported", + _nm_platform_kernel_support_info[type].name, + _nm_platform_kernel_support_info[type].desc, + value >= 0 ? "" : "not "); + } else { + nm_log_dbg(LOGD_PLATFORM, + "platform: kernel-support for %s (%s) detected: %ssupported", + _nm_platform_kernel_support_info[type].name, + _nm_platform_kernel_support_info[type].desc, + value >= 0 ? "" : "not "); + } + +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 1 + + return value; +} + +/*****************************************************************************/ + +/** + * nm_platform_process_events: + * @self: platform instance + * + * Process pending events or handle pending delayed-actions. + * Effectively, this reads the netlink socket and processes + * new netlink messages. Possibly it will raise change signals. + */ +void +nm_platform_process_events(NMPlatform *self) +{ + _CHECK_SELF_VOID(self, klass); + + if (klass->process_events) + klass->process_events(self); +} + +const NMPlatformLink * +nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname) +{ + const NMPObject *obj; + gboolean refreshed = FALSE; + + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + + if (ifindex <= 0 && !ifname) + return NULL; + + /* we look into the cache, whether a link for given ifindex/ifname + * exits. If not, we poll the netlink socket, maybe the event + * with the link is waiting. + * + * Then we try again to find the object. + * + * If the link is already cached the first time, we avoid polling + * the netlink socket. */ +again: + obj = nmp_cache_lookup_link_full( + nm_platform_get_cache(self), + ifindex, + ifname, + FALSE, /* also invisible. We don't care here whether udev is ready */ + NM_LINK_TYPE_NONE, + NULL, + NULL); + if (obj) + return NMP_OBJECT_CAST_LINK(obj); + if (!refreshed) { + refreshed = TRUE; + nm_platform_process_events(self); + goto again; + } + + return NULL; +} + +/*****************************************************************************/ + +/** + * nm_platform_sysctl_open_netdir: + * @self: platform instance + * @ifindex: the ifindex for which to open /sys/class/net/%s + * @out_ifname: optional output argument of the found ifname. + * + * Wraps nmp_utils_sysctl_open_netdir() by first changing into the right + * network-namespace. + * + * Returns: on success, the open file descriptor to the /sys/class/net/%s + * directory. + */ +int +nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname) +{ + const char *ifname_guess; + _CHECK_SELF_NETNS(self, klass, netns, -1); + + g_return_val_if_fail(ifindex > 0, -1); + + /* we don't have an @ifname_guess argument to make the API nicer. + * But still do a cache-lookup first. Chances are good that we have + * the right ifname cached and save if_indextoname() */ + ifname_guess = nm_platform_link_get_name(self, ifindex); + + return nmp_utils_sysctl_open_netdir(ifindex, ifname_guess, out_ifname); +} + +/** + * nm_platform_sysctl_set: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: optional file descriptor for parent directory for openat() + * @path: Absolute option path + * @value: Value to write + * + * This function is intended to be used for writing values to sysctl-style + * virtual runtime configuration files. This includes not only /proc/sys + * but also for example /sys/class. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_sysctl_set(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(path, FALSE); + g_return_val_if_fail(value, FALSE); + + return klass->sysctl_set(self, pathid, dirfd, path, value); +} + +/** + * nm_platform_sysctl_set_async: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up + * @dirfd: optional file descriptor for parent directory for openat() + * @path: absolute option path + * @values: NULL-terminated array of strings to be written + * @callback: function called on termination + * @data: data passed to callback function + * @cancellable: to cancel the operation + * + * This function is intended to be used for writing values to sysctl-style + * virtual runtime configuration files. This includes not only /proc/sys + * but also for example /sys/class. The function does not block and returns + * immediately. The callback is always invoked, and asynchronously. The file + * is closed after writing each value and reopened to write the next one so + * that the function can be used safely on all /proc and /sys files, + * independently of how /proc/sys/kernel/sysctl_writes_strict is configured. + */ +void +nm_platform_sysctl_set_async(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable) +{ + _CHECK_SELF_VOID(self, klass); + + klass->sysctl_set_async(self, pathid, dirfd, path, values, callback, data, cancellable); +} + +gboolean +nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value) +{ + const char *path; + gint64 cur; + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + _CHECK_SELF(self, klass, FALSE); + + /* the hop-limit provided via RA is uint8. */ + if (value > 0xFF) + return FALSE; + + /* don't allow unreasonable small values */ + if (value < 10) + return FALSE; + + path = nm_utils_sysctl_ip_conf_path(AF_INET6, buf, iface, "hop_limit"); + cur = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_ABSOLUTE(path), + 10, + 1, + G_MAXINT32, + -1); + + /* only allow increasing the hop-limit to avoid DOS by an attacker + * setting a low hop-limit (CVE-2015-2924, rh#1209902) */ + + if (value < cur) + return FALSE; + if (value != cur) { + char svalue[20]; + + sprintf(svalue, "%d", value); + nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), svalue); + } + + return TRUE; +} + +gboolean +nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self, + const char *iface, + guint value_ms) +{ + char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char str[128]; + guint clamped; + + _CHECK_SELF(self, klass, FALSE); + + if (!value_ms) + return TRUE; + + /* RFC 4861 says the value can't be greater than one hour. + * Also use a reasonable lower threshold. */ + clamped = NM_CLAMP(value_ms, 100, 3600000); + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", iface); + nm_sprintf_buf(str, "%u", clamped); + if (!nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str)) + return FALSE; + + /* Set stale time in the same way as kernel */ + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/gc_stale_time", iface); + nm_sprintf_buf(str, "%u", clamped * 3 / 1000); + + return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str); +} + +gboolean +nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self, + const char *iface, + guint value_ms) +{ + char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char str[128]; + + _CHECK_SELF(self, klass, FALSE); + + if (!value_ms) + return TRUE; + + nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/retrans_time_ms", iface); + nm_sprintf_buf(str, "%u", NM_CLAMP(value_ms, 10, 3600000)); + + return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str); +} + +/** + * nm_platform_sysctl_get: + * @self: platform instance + * @dirfd: if non-negative, used to lookup the path via openat(). + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @path: Absolute path to sysctl + * + * Returns: (transfer full): Contents of the virtual sysctl file. + * + * If the path does not exist, %NULL is returned and %errno set to %ENOENT. + */ +char * +nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(path, NULL); + + return klass->sysctl_get(self, pathid, dirfd, path); +} + +/** + * nm_platform_sysctl_get_int32: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: if non-negative, used to lookup the path via openat(). + * @path: Absolute path to sysctl + * @fallback: default value, if the content of path could not be read + * as decimal integer. + * + * Returns: contents of the sysctl file parsed as s32 integer, or + * @fallback on error. On error, %errno will be set to a non-zero + * value, on success %errno will be set to zero. + */ +gint32 +nm_platform_sysctl_get_int32(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + gint32 fallback) +{ + return nm_platform_sysctl_get_int_checked(self, + pathid, + dirfd, + path, + 10, + G_MININT32, + G_MAXINT32, + fallback); +} + +/** + * nm_platform_sysctl_get_int_checked: + * @self: platform instance + * @pathid: if @dirfd is present, this must be the full path that is looked up. + * It is required for logging. + * @dirfd: if non-negative, used to lookup the path via openat(). + * @path: Absolute path to sysctl + * @base: base of numeric conversion + * @min: minimal value that is still valid + * @max: maximal value that is still valid + * @fallback: default value, if the content of path could not be read + * as valid integer. + * + * Returns: contents of the sysctl file parsed as s64 integer, or + * @fallback on error. On error, %errno will be set to a non-zero + * value. On success, %errno will be set to zero. The returned value + * will always be in the range between @min and @max + * (inclusive) or @fallback. + * If the file does not exist, the fallback is returned and %errno + * is set to ENOENT. + */ +gint64 +nm_platform_sysctl_get_int_checked(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + char * value = NULL; + gint32 ret; + int errsv; + + _CHECK_SELF(self, klass, fallback); + + g_return_val_if_fail(path, fallback); + + if (!path) { + errno = EINVAL; + return fallback; + } + + value = nm_platform_sysctl_get(self, pathid, dirfd, path); + if (!value) { + /* nm_platform_sysctl_get() set errno to ENOENT if the file does not exist. + * Propagate/preserve that. */ + if (errno != ENOENT) + errno = EINVAL; + return fallback; + } + + ret = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback); + errsv = errno; + g_free(value); + errno = errsv; + return ret; +} + +/*****************************************************************************/ + +char * +nm_platform_sysctl_ip_conf_get(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_get( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property))); +} + +gint64 +nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + guint base, + gint64 min, + gint64 max, + gint64 fallback) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_get_int_checked( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + base, + min, + max, + fallback); +} + +gboolean +nm_platform_sysctl_ip_conf_set(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + const char *value) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + + return nm_platform_sysctl_set( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + value); +} + +gboolean +nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + gint64 value) +{ + char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE]; + char s[64]; + + return nm_platform_sysctl_set( + self, + NMP_SYSCTL_PATHID_ABSOLUTE( + nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)), + nm_sprintf_buf(s, "%" G_GINT64_FORMAT, value)); +} + +int +nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *self, + const char *ifname, + gboolean consider_all, + gboolean * out_due_to_all) +{ + int val, val_all; + + NM_SET_OUT(out_due_to_all, FALSE); + + if (!ifname) + return -1; + + val = nm_platform_sysctl_ip_conf_get_int_checked(self, + AF_INET, + ifname, + "rp_filter", + 10, + 0, + 2, + -1); + if (val == -1) + return -1; + + /* the effectively used value is the rp_filter sysctl value of MAX(all,ifname). + * Note that this is the numerical MAX(), despite rp_filter "1" being more strict + * than "2". */ + if (val < 2 && consider_all && !nm_streq(ifname, "all")) { + val_all = nm_platform_sysctl_ip_conf_get_int_checked(self, + AF_INET, + "all", + "rp_filter", + 10, + 0, + 2, + val); + if (val_all > val) { + val = val_all; + NM_SET_OUT(out_due_to_all, TRUE); + } + } + + return val; +} + +/*****************************************************************************/ + +static int +_link_get_all_presort(gconstpointer p_a, gconstpointer p_b, gpointer sort_by_name) +{ + const NMPlatformLink *a = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_a)); + const NMPlatformLink *b = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_b)); + + /* Loopback always first */ + if (a->ifindex == 1) + return -1; + if (b->ifindex == 1) + return 1; + + if (GPOINTER_TO_INT(sort_by_name)) { + /* Initialized links first */ + if (a->initialized > b->initialized) + return -1; + if (a->initialized < b->initialized) + return 1; + + return strcmp(a->name, b->name); + } else + return a->ifindex - b->ifindex; +} + +/** + * nm_platform_link_get_all: + * @self: platform instance + * @sort_by_name: whether to sort by name or ifindex. + * + * Retrieve a snapshot of configuration for all links at once. The result is + * owned by the caller and should be freed with g_ptr_array_unref(). + */ +GPtrArray * +nm_platform_link_get_all(NMPlatform *self, gboolean sort_by_name) +{ + gs_unref_ptrarray GPtrArray *links = NULL; + GPtrArray * result; + guint i, nresult; + gs_unref_hashtable GHashTable *unseen = NULL; + const NMPlatformLink * item; + NMPLookup lookup; + + _CHECK_SELF(self, klass, NULL); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + links = nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, &lookup), NULL, NULL); + if (!links) + return NULL; + + for (i = 0; i < links->len;) { + if (!nmp_object_is_visible(links->pdata[i])) + g_ptr_array_remove_index_fast(links, i); + else + i++; + } + + if (links->len == 0) + return NULL; + + /* first sort the links by their ifindex or name. Below we will sort + * further by moving children/slaves to the end. */ + g_ptr_array_sort_with_data(links, _link_get_all_presort, GINT_TO_POINTER(sort_by_name)); + + unseen = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + nm_assert(item->ifindex > 0); + if (!g_hash_table_insert(unseen, GINT_TO_POINTER(item->ifindex), NULL)) + nm_assert_not_reached(); + } + +#if NM_MORE_ASSERTS + /* Ensure that link_get_all returns a consistent and valid result. */ + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + + if (!item->ifindex) + continue; + if (item->master != 0) { + g_warn_if_fail(item->master > 0); + g_warn_if_fail(item->master != item->ifindex); + g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->master))); + } + if (item->parent != 0) { + if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) { + g_warn_if_fail(item->parent > 0); + g_warn_if_fail(item->parent != item->ifindex); + g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent))); + } + } + } +#endif + + /* Re-order the links list such that children/slaves come after all ancestors */ + nm_assert(g_hash_table_size(unseen) == links->len); + nresult = links->len; + result = g_ptr_array_new_full(nresult, (GDestroyNotify) nmp_object_unref); + + while (TRUE) { + gboolean found_something = FALSE; + guint first_idx = G_MAXUINT; + + for (i = 0; i < links->len; i++) { + item = NMP_OBJECT_CAST_LINK(links->pdata[i]); + + if (!item) + continue; + + g_assert(g_hash_table_contains(unseen, GINT_TO_POINTER(item->ifindex))); + + if (item->master > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->master))) + goto skip; + if (item->parent > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent))) + goto skip; + + g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex)); + g_ptr_array_add(result, links->pdata[i]); + links->pdata[i] = NULL; + found_something = TRUE; + continue; +skip: + if (first_idx == G_MAXUINT) + first_idx = i; + } + + if (found_something) { + if (first_idx == G_MAXUINT) + break; + } else { + nm_assert(first_idx != G_MAXUINT); + /* There is a loop, pop the first (remaining) element from the list. + * This can happen for veth pairs where each peer is parent of the other end. */ + item = NMP_OBJECT_CAST_LINK(links->pdata[first_idx]); + nm_assert(item); + g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex)); + g_ptr_array_add(result, links->pdata[first_idx]); + links->pdata[first_idx] = NULL; + } + nm_assert(result->len < nresult); + } + nm_assert(result->len == nresult); + + return result; +} + +/*****************************************************************************/ + +const NMPObject * +nm_platform_link_get_obj(NMPlatform *self, int ifindex, gboolean visible_only) +{ + const NMPObject *obj_cache; + + _CHECK_SELF(self, klass, NULL); + + obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(self), ifindex); + if (!obj_cache || (visible_only && !nmp_object_is_visible(obj_cache))) + return NULL; + return obj_cache; +} + +/*****************************************************************************/ + +/** + * nm_platform_link_get: + * @self: platform instance + * @ifindex: ifindex of the link + * + * Lookup the internal NMPlatformLink object. + * + * Returns: %NULL, if such a link exists or the internal + * platform link object. Do not modify the returned value. + * Also, be aware that any subsequent platform call might + * invalidate/modify the returned instance. + **/ +const NMPlatformLink * +nm_platform_link_get(NMPlatform *self, int ifindex) +{ + return NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, TRUE)); +} + +/** + * nm_platform_link_get_by_ifname: + * @self: platform instance + * @ifname: the ifname + * + * Returns: the first #NMPlatformLink instance with the given name. + **/ +const NMPlatformLink * +nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname) +{ + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + if (!ifname || !*ifname) + return NULL; + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self), + 0, + ifname, + TRUE, + NM_LINK_TYPE_NONE, + NULL, + NULL); + return NMP_OBJECT_CAST_LINK(obj); +} + +struct _nm_platform_link_get_by_address_data { + gconstpointer data; + guint8 len; +}; + +static gboolean +_nm_platform_link_get_by_address_match_link(const NMPObject * obj, + struct _nm_platform_link_get_by_address_data *d) +{ + return obj->link.l_address.len == d->len && !memcmp(obj->link.l_address.data, d->data, d->len); +} + +/** + * nm_platform_link_get_by_address: + * @self: platform instance + * @address: a pointer to the binary hardware address + * @length: the size of @address in bytes + * + * Returns: the first #NMPlatformLink object with a matching + * address. + **/ +const NMPlatformLink * +nm_platform_link_get_by_address(NMPlatform * self, + NMLinkType link_type, + gconstpointer address, + size_t length) +{ + const NMPObject * obj; + struct _nm_platform_link_get_by_address_data d = { + .data = address, + .len = length, + }; + + _CHECK_SELF(self, klass, NULL); + + if (length == 0) + return NULL; + + if (length > _NM_UTILS_HWADDR_LEN_MAX) + g_return_val_if_reached(NULL); + if (!address) + g_return_val_if_reached(NULL); + + obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self), + 0, + NULL, + TRUE, + link_type, + (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, + &d); + return NMP_OBJECT_CAST_LINK(obj); +} + +static int +_link_add_check_existing(NMPlatform * self, + const char * name, + NMLinkType type, + const NMPlatformLink **out_link) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get_by_ifname(self, name); + if (pllink) { + gboolean wrong_type; + + wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type; + _LOG2D("link: skip adding link due to existing interface of type %s%s%s", + nm_link_type_to_string(pllink->type), + wrong_type ? ", expected " : "", + wrong_type ? nm_link_type_to_string(type) : ""); + if (out_link) + *out_link = pllink; + if (wrong_type) + return -NME_PL_WRONG_TYPE; + return -NME_PL_EXISTS; + } + if (out_link) + *out_link = NULL; + return 0; +} + +/** + * nm_platform_link_add: + * @self: platform instance + * @type: Interface type + * @name: Interface name + * @parent: the IFLA_LINK parameter or 0. + * @address: (allow-none): set the mac address of the link + * @address_len: the length of the @address + * @extra_data: depending on @type, additional data. + * @out_link: on success, the link object + * + * Add a software interface. If the interface already exists and is of type + * @type, return -NME_PL_EXISTS and returns the link + * in @out_link. If the interface already exists and is not of type @type, + * return -NME_PL_WRONG_TYPE. + * + * Any link-changed ADDED signal will be emitted directly, before this + * function finishes. + * + * Returns: the negative nm-error on failure. + */ +int +nm_platform_link_add(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link) +{ + int r; + char addr_buf[_NM_UTILS_HWADDR_LEN_MAX * 3]; + char mtu_buf[16]; + char parent_buf[64]; + char buf[512]; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(name, -NME_BUG); + g_return_val_if_fail((address != NULL) ^ (address_len == 0), -NME_BUG); + g_return_val_if_fail(address_len <= _NM_UTILS_HWADDR_LEN_MAX, -NME_BUG); + g_return_val_if_fail(parent >= 0, -NME_BUG); + + r = _link_add_check_existing(self, name, type, out_link); + if (r < 0) + return r; + + _LOG2D("link: adding link: " + "%s " /* type */ + "\"%s\"" /* name */ + "%s%s" /* parent */ + "%s%s" /* address */ + "%s%s" /* mtu */ + "%s" /* extra_data */ + "", + nm_link_type_to_string(type), + name, + parent > 0 ? ", parent " : "", + parent > 0 ? nm_sprintf_buf(parent_buf, "%d", parent) : "", + address ? ", address: " : "", + address ? _nm_utils_hwaddr_ntoa(address, address_len, FALSE, addr_buf, sizeof(addr_buf)) + : "", + mtu ? ", mtu: " : "", + mtu ? nm_sprintf_buf(mtu_buf, "%u", mtu) : "", + ({ + char *buf_p = buf; + gsize buf_len = sizeof(buf); + + buf[0] = '\0'; + + switch (type) { + case NM_LINK_TYPE_BRIDGE: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_bridge_to_string((const NMPlatformLnkBridge *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VLAN: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vlan_to_string((const NMPlatformLnkVlan *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VRF: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vrf_to_string((const NMPlatformLnkVrf *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VXLAN: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_vxlan_to_string((const NMPlatformLnkVxlan *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_VETH: + nm_sprintf_buf(buf, ", veth-peer \"%s\"", (const char *) extra_data); + break; + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_GRETAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_gre_to_string((const NMPlatformLnkGre *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_SIT: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_sit_to_string((const NMPlatformLnkSit *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_IP6TNL: + case NM_LINK_TYPE_IP6GRE: + case NM_LINK_TYPE_IP6GRETAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_ip6tnl_to_string((const NMPlatformLnkIp6Tnl *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_IPIP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_ipip_to_string((const NMPlatformLnkIpIp *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_MACSEC: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_macsec_to_string((const NMPlatformLnkMacsec *) extra_data, + buf_p, + buf_len); + break; + case NM_LINK_TYPE_MACVLAN: + case NM_LINK_TYPE_MACVTAP: + nm_utils_strbuf_append_str(&buf_p, &buf_len, ", "); + nm_platform_lnk_macvlan_to_string((const NMPlatformLnkMacvlan *) extra_data, + buf_p, + buf_len); + break; + default: + nm_assert(!extra_data); + break; + } + + buf; + })); + + return klass + ->link_add(self, type, name, parent, address, address_len, mtu, extra_data, out_link); +} + +/** + * nm_platform_link_delete: + * @self: platform instance + * @ifindex: Interface index + */ +gboolean +nm_platform_link_delete(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: deleting"); + return klass->link_delete(self, ifindex); +} + +/** + * nm_platform_link_set_netns: + * @self: platform instance + * @ifindex: Interface index + * @netns_fd: the file descriptor for the new netns. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(netns_fd > 0, FALSE); + + _LOG3D("link: move link to network namespace with fd %d", netns_fd); + return klass->link_set_netns(self, ifindex, netns_fd); +} + +/** + * nm_platform_link_get_index: + * @self: platform instance + * @name: Interface name + * + * Returns: The interface index corresponding to the given interface name + * or 0. Interface name is owned by #NMPlatform, don't free it. + */ +int +nm_platform_link_get_ifindex(NMPlatform *self, const char *name) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get_by_ifname(self, name); + return pllink ? pllink->ifindex : 0; +} + +const char * +nm_platform_if_indextoname(NMPlatform *self, int ifindex, char out_ifname[static 16 /* IFNAMSIZ */]) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + return nmp_utils_if_indextoname(ifindex, out_ifname); +} + +int +nm_platform_if_nametoindex(NMPlatform *self, const char *ifname) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + return nmp_utils_if_nametoindex(ifname); +} + +/** + * nm_platform_link_get_name: + * @self: platform instance + * @name: Interface name + * + * Returns: The interface name corresponding to the given interface index + * or %NULL. + */ +const char * +nm_platform_link_get_name(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->name : NULL; +} + +/** + * nm_platform_link_get_type: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: Link type constant as defined in nm-platform.h. On error, + * NM_LINK_TYPE_NONE is returned. + */ +NMLinkType +nm_platform_link_get_type(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->type : NM_LINK_TYPE_NONE; +} + +/** + * nm_platform_link_get_type_name: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: A string describing the type of link. In some cases this + * may be more specific than nm_platform_link_get_type(), but in + * other cases it may not. On error, %NULL is returned. + */ +const char * +nm_platform_link_get_type_name(NMPlatform *self, int ifindex) +{ + const NMPObject *obj; + + obj = nm_platform_link_get_obj(self, ifindex, TRUE); + if (!obj) + return NULL; + + if (obj->link.type != NM_LINK_TYPE_UNKNOWN) { + /* We could detect the @link_type. In this case the function returns + * our internal module names, which differs from rtnl_link_get_type(): + * - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib") + * - NM_LINK_TYPE_TAP (gives "tap", instead of "tun"). + * Note that this functions is only used by NMDeviceGeneric to + * set type_description. */ + return nm_link_type_to_string(obj->link.type); + } + /* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */ + return obj->link.kind ?: "unknown"; +} + +gboolean +nm_platform_link_get_udev_property(NMPlatform * self, + int ifindex, + const char * name, + const char **out_value) +{ + struct udev_device *udevice = NULL; + const char * uproperty; + + udevice = nm_platform_link_get_udev_device(self, ifindex); + if (!udevice) + return FALSE; + + uproperty = udev_device_get_property_value(udevice, name); + if (!uproperty) + return FALSE; + + NM_SET_OUT(out_value, uproperty); + return TRUE; +} + +/** + * nm_platform_link_get_unmanaged: + * @self: platform instance + * @ifindex: interface index + * @unmanaged: management status (in case %TRUE is returned) + * + * Returns: %TRUE if platform overrides NM default-unmanaged status, + * %FALSE otherwise (with @unmanaged unmodified). + */ +gboolean +nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged) +{ + const char *value; + + if (nm_platform_link_get_udev_property(self, ifindex, "NM_UNMANAGED", &value)) { + NM_SET_OUT(unmanaged, _nm_utils_ascii_str_to_bool(value, FALSE)); + return TRUE; + } + + return FALSE; +} + +/** + * nm_platform_link_is_software: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: %TRUE if ifindex belongs to a software interface, not backed by + * a physical device. + */ +gboolean +nm_platform_link_is_software(NMPlatform *self, int ifindex) +{ + return nm_link_type_is_software(nm_platform_link_get_type(self, ifindex)); +} + +/** + * nm_platform_link_supports_slaves: + * @self: platform instance + * @ifindex: Interface index. + * + * Returns: %TRUE if ifindex belongs to an interface capable of enslaving + * other interfaces. + */ +gboolean +nm_platform_link_supports_slaves(NMPlatform *self, int ifindex) +{ + return nm_link_type_supports_slaves(nm_platform_link_get_type(self, ifindex)); +} + +/** + * nm_platform_link_refresh: + * @self: platform instance + * @ifindex: Interface index + * + * Reload the cache for ifindex synchronously. + */ +gboolean +nm_platform_link_refresh(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + if (klass->link_refresh) + return klass->link_refresh(self, ifindex); + + return TRUE; +} + +int +nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags) +{ + const NMPlatformLink *pllink; + + /* include invisible links (only in netlink, not udev). */ + pllink = NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, FALSE)); + if (!pllink) + return -ENODEV; + + /* Errors are signaled as negative values. That means, you cannot request + * the most significant bit (2^31) with this API. Assert against that. */ + nm_assert((int) requested_flags >= 0); + nm_assert(requested_flags < (guint) G_MAXINT); + + return (int) (pllink->n_ifi_flags & requested_flags); +} + +/** + * nm_platform_link_is_up: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is up. + */ +gboolean +nm_platform_link_is_up(NMPlatform *self, int ifindex) +{ + return nm_platform_link_get_ifi_flags(self, ifindex, IFF_UP) == IFF_UP; +} + +/** + * nm_platform_link_is_connected: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is connected. + */ +gboolean +nm_platform_link_is_connected(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->connected : FALSE; +} + +/** + * nm_platform_link_uses_arp: + * @self: platform instance + * @ifindex: Interface index + * + * Check if the interface is configured to use ARP. + */ +gboolean +nm_platform_link_uses_arp(NMPlatform *self, int ifindex) +{ + int f; + + f = nm_platform_link_get_ifi_flags(self, ifindex, IFF_NOARP); + + if (f < 0) + return FALSE; + if (f == IFF_NOARP) + return FALSE; + return TRUE; +} + +/** + * nm_platform_link_set_ipv6_token: + * @self: platform instance + * @ifindex: Interface index + * @iid: Tokenized interface identifier + * + * Sets then IPv6 tokenized interface identifier. + * + * Returns: %TRUE a tokenized identifier was available + */ +gboolean +nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + if (klass->link_set_token) + return klass->link_set_token(self, ifindex, iid); + return FALSE; +} + +const char * +nm_platform_link_get_udi(NMPlatform *self, int ifindex) +{ + struct udev_device *device; + + device = nm_platform_link_get_udev_device(self, ifindex); + return device ? udev_device_get_syspath(device) : NULL; +} + +const char * +nm_platform_link_get_path(NMPlatform *self, int ifindex) +{ + const char *value = NULL; + + nm_platform_link_get_udev_property(self, ifindex, "ID_PATH", &value); + return value; +} + +struct udev_device * +nm_platform_link_get_udev_device(NMPlatform *self, int ifindex) +{ + const NMPObject *obj_cache; + + obj_cache = nm_platform_link_get_obj(self, ifindex, FALSE); + return obj_cache ? obj_cache->_link.udev.device : NULL; +} + +/** + * nm_platform_link_get_user_ip6vll_enabled: + * @self: platform instance + * @ifindex: Interface index + * + * Check whether NM handles IPv6LL address creation for the link. If the + * platform or OS doesn't support changing the IPv6LL address mode, this call + * will fail and return %FALSE. + * + * Returns: %TRUE if NM handles the IPv6LL address for @ifindex + */ +gboolean +nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + if (pllink && pllink->inet6_addr_gen_mode_inv) + return _nm_platform_uint8_inv(pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE; + return FALSE; +} + +/** + * nm_platform_link_set_user_ip6vll_enabled: + * @self: platform instance + * @ifindex: Interface index + * + * Set whether NM handles IPv6LL address creation for the link. If the + * platform or OS doesn't support changing the IPv6LL address mode, this call + * will fail and return %FALSE. + * + * Returns: the negative nm-error on failure. + */ +int +nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex > 0, -NME_BUG); + + return klass->link_set_user_ipv6ll_enabled(self, ifindex, enabled); +} + +/** + * nm_platform_link_set_address: + * @self: platform instance + * @ifindex: Interface index + * @address: The new MAC address + * + * Set interface MAC address. + */ +int +nm_platform_link_set_address(NMPlatform *self, int ifindex, gconstpointer address, size_t length) +{ + gs_free char *mac = NULL; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex > 0, -NME_BUG); + g_return_val_if_fail(address, -NME_BUG); + g_return_val_if_fail(length > 0, -NME_BUG); + + _LOG3D("link: setting hardware address to %s", + _nm_utils_hwaddr_ntoa_maybe_a(address, length, &mac)); + + return klass->link_set_address(self, ifindex, address, length); +} + +/** + * nm_platform_link_get_address: + * @self: platform instance + * @ifindex: Interface index + * @length: Pointer to a variable to store address length + * + * Returns: the interface hardware address as an array of bytes of + * length @length. + */ +gconstpointer +nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return nmp_link_address_get(pllink ? &pllink->l_address : NULL, length); +} + +/** + * nm_platform_link_get_permanent_address: + * @self: platform instance + * @ifindex: Interface index + * @buf: buffer of at least %_NM_UTILS_HWADDR_LEN_MAX bytes, on success + * the permanent hardware address + * @length: Pointer to a variable to store address length + * + * Returns: %TRUE on success, %FALSE on failure to read the permanent hardware + * address. + */ +gboolean +nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length) +{ + _CHECK_SELF(self, klass, FALSE); + + if (length) + *length = 0; + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(buf, FALSE); + g_return_val_if_fail(length, FALSE); + + if (klass->link_get_permanent_address) + return klass->link_get_permanent_address(self, ifindex, buf, length); + return FALSE; +} + +gboolean +nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_carrier_detect(self, ifindex); +} + +gboolean +nm_platform_link_supports_vlans(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_vlans(self, ifindex); +} + +gboolean +nm_platform_link_supports_sriov(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_supports_sriov(self, ifindex); +} + +/** + * nm_platform_link_set_sriov_params: + * @self: platform instance + * @ifindex: the index of the interface to change + * @num_vfs: the number of VFs to create + * @autoprobe: the new autoprobe-drivers value (pass + * %NM_OPTION_BOOL_DEFAULT to keep current value) + * @callback: called when the operation finishes + * @callback_data: data passed to @callback + * @cancellable: cancellable to abort the operation + * + * Sets SR-IOV parameters asynchronously without + * blocking the main thread. The callback function is + * always invoked, and asynchronously. + */ +void +nm_platform_link_set_sriov_params_async(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + _LOG3D("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe); + klass->link_set_sriov_params_async(self, + ifindex, + num_vfs, + autoprobe, + callback, + callback_data, + cancellable); +} + +gboolean +nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs) +{ + guint i; + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: setting VFs"); + for (i = 0; vfs[i]; i++) { + const NMPlatformVF *vf = vfs[i]; + + _LOG3D("link: VF %s", nm_platform_vf_to_string(vf, NULL, 0)); + } + + return klass->link_set_sriov_vfs(self, ifindex, vfs); +} + +gboolean +nm_platform_link_set_bridge_vlans(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans) +{ + guint i; + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: %s bridge VLANs on %s", + vlans ? "setting" : "clearing", + on_master ? "master" : "self"); + if (vlans) { + for (i = 0; vlans[i]; i++) { + const NMPlatformBridgeVlan *vlan = vlans[i]; + + _LOG3D("link: bridge VLAN %s", nm_platform_bridge_vlan_to_string(vlan, NULL, 0)); + } + } + + return klass->link_set_bridge_vlans(self, ifindex, on_master, vlans); +} + +/** + * nm_platform_link_change_flags_full: + * @self: platform instance + * @ifindex: interface index + * @flags_mask: flag mask to be set + * @flags_set: flag to be set on the flag mask + * + * Change the interface flag mask to the value specified. + * + * Returns: nm-errno code. + * + */ +int +nm_platform_link_change_flags_full(NMPlatform *self, + int ifindex, + unsigned flags_mask, + unsigned flags_set) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, -NME_BUG); + + return klass->link_change_flags(self, ifindex, flags_mask, flags_set); +} + +/** + * nm_platform_link_set_mtu: + * @self: platform instance + * @ifindex: Interface index + * @mtu: The new MTU value + * + * Set interface MTU. + */ +int +nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + g_return_val_if_fail(mtu > 0, FALSE); + + _LOG3D("link: setting mtu %" G_GUINT32_FORMAT, mtu); + return klass->link_set_mtu(self, ifindex, mtu); +} + +/** + * nm_platform_link_get_mtu: + * @self: platform instance + * @ifindex: Interface index + * + * Returns: MTU value for the interface or 0 on error. + */ +guint32 +nm_platform_link_get_mtu(NMPlatform *self, int ifindex) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, ifindex); + return pllink ? pllink->mtu : 0; +} + +/** + * nm_platform_link_set_name: + * @self: platform instance + * @ifindex: Interface index + * @name: The new interface name + * + * Set interface name. + */ +gboolean +nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + g_return_val_if_fail(name, FALSE); + + _LOG3D("link: setting name %s", name); + + if (strlen(name) + 1 > IFNAMSIZ) + return FALSE; + + return klass->link_set_name(self, ifindex, name); +} + +/** + * nm_platform_link_get_physical_port_id: + * @self: platform instance + * @ifindex: Interface index + * + * The physical port ID, if present, indicates some unique identifier of + * the parent interface (eg, the physical port of which this link is a child). + * Two links that report the same physical port ID can be assumed to be + * children of the same physical port and may share resources that limit + * their abilities. + * + * Returns: physical port ID for the interface, or %NULL on error + * or if the interface has no physical port ID. + */ +char * +nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex >= 0, NULL); + + if (klass->link_get_physical_port_id) + return klass->link_get_physical_port_id(self, ifindex); + return NULL; +} + +/** + * nm_platform_link_get_dev_id: + * @self: platform instance + * @ifindex: Interface index + * + * In contrast to the physical device ID (which indicates which parent a + * child has) the device ID differentiates sibling devices that may share + * the same MAC address. + * + * Returns: device ID for the interface, or 0 on error or if the + * interface has no device ID. + */ +guint +nm_platform_link_get_dev_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex >= 0, 0); + + if (klass->link_get_dev_id) + return klass->link_get_dev_id(self, ifindex); + return 0; +} + +/** + * nm_platform_link_get_wake_onlan: + * @self: platform instance + * @ifindex: Interface index + * + * Returns: the "Wake-on-LAN" status for @ifindex. + */ +gboolean +nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + if (klass->link_get_wake_on_lan) + return klass->link_get_wake_on_lan(self, ifindex); + return FALSE; +} + +/** + * nm_platform_link_get_driver_info: + * @self: platform instance + * @ifindex: Interface index + * @out_driver_name: (transfer full): on success, the driver name if available + * @out_driver_version: (transfer full): on success, the driver version if available + * @out_fw_version: (transfer full): on success, the firmware version if available + * + * Returns: %TRUE on success (though @out_driver_name, @out_driver_version and + * @out_fw_version can be %NULL if no information was available), %FALSE on + * failure. + */ +gboolean +nm_platform_link_get_driver_info(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex >= 0, FALSE); + + return klass->link_get_driver_info(self, + ifindex, + out_driver_name, + out_driver_version, + out_fw_version); +} + +/** + * nm_platform_link_enslave: + * @self: platform instance + * @master: Interface index of the master + * @ifindex: Interface index of the slave + * + * Enslave @ifindex to @master. + */ +gboolean +nm_platform_link_enslave(NMPlatform *self, int master, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(master > 0, FALSE); + g_return_val_if_fail(ifindex > 0, FALSE); + + _LOG3D("link: enslaving to master '%s'", nm_platform_link_get_name(self, master)); + return klass->link_enslave(self, master, ifindex); +} + +/** + * nm_platform_link_release: + * @self: platform instance + * @master: Interface index of the master + * @ifindex: Interface index of the slave + * + * Release @slave from @master. + */ +gboolean +nm_platform_link_release(NMPlatform *self, int master, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(master > 0, FALSE); + g_return_val_if_fail(ifindex > 0, FALSE); + + if (nm_platform_link_get_master(self, ifindex) != master) + return FALSE; + + _LOG3D("link: releasing from master '%s'", nm_platform_link_get_name(self, master)); + return klass->link_release(self, master, ifindex); +} + +/** + * nm_platform_link_get_master: + * @self: platform instance + * @slave: Interface index of the slave. + * + * Returns: Interface index of the slave's master. + */ +int +nm_platform_link_get_master(NMPlatform *self, int slave) +{ + const NMPlatformLink *pllink; + + pllink = nm_platform_link_get(self, slave); + return pllink ? pllink->master : 0; +} + +/*****************************************************************************/ + +gboolean +nm_platform_link_can_assume(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + if (klass->link_can_assume) + return klass->link_can_assume(self, ifindex); + g_return_val_if_reached(FALSE); +} + +/*****************************************************************************/ + +/** + * nm_platform_link_get_lnk: + * @self: the platform instance + * @ifindex: the link ifindex to lookup + * @link_type: filter by link-type. + * @out_link: (allow-none): returns the platform link instance + * + * If the function returns %NULL, that could mean that no such ifindex + * exists, of that the link has no lnk data. You can find that out + * by checking @out_link. @out_link will always be set if a link + * with @ifindex exists. + * + * If @link_type is %NM_LINK_TYPE_NONE, the function returns the lnk + * object if it is present. If you set link-type, you can be sure + * that only a link type of the matching type is returned (or %NULL). + * + * Returns: the internal link lnk object. The returned object + * is owned by the platform cache and must not be modified. Note + * however, that the object is guaranteed to be immutable, so + * you can safely take a reference and keep it for yourself + * (but don't modify it). + */ +const NMPObject * +nm_platform_link_get_lnk(NMPlatform * self, + int ifindex, + NMLinkType link_type, + const NMPlatformLink **out_link) +{ + const NMPObject *obj; + + obj = nm_platform_link_get_obj(self, ifindex, TRUE); + if (!obj) { + NM_SET_OUT(out_link, NULL); + return NULL; + } + + NM_SET_OUT(out_link, &obj->link); + + if (!obj->_link.netlink.lnk) + return NULL; + if (link_type != NM_LINK_TYPE_NONE + && (link_type != obj->link.type + || link_type != NMP_OBJECT_GET_CLASS(obj->_link.netlink.lnk)->lnk_link_type)) + return NULL; + + return obj->_link.netlink.lnk; +} + +static gconstpointer +_link_get_lnk(NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link) +{ + const NMPObject *lnk; + + lnk = nm_platform_link_get_lnk(self, ifindex, link_type, out_link); + return lnk ? &lnk->object : NULL; +} + +const NMPlatformLnkBridge * +nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_BRIDGE, out_link); +} + +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRE, out_link); +} + +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRETAP, out_link); +} + +const NMPlatformLnkInfiniband * +nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_INFINIBAND, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6TNL, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRE, out_link); +} + +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRETAP, out_link); +} + +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IPIP, out_link); +} + +const NMPlatformLnkMacsec * +nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACSEC, out_link); +} + +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACVLAN, out_link); +} + +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvtap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACVTAP, out_link); +} + +const NMPlatformLnkSit * +nm_platform_link_get_lnk_sit(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_SIT, out_link); +} + +const NMPlatformLnkTun * +nm_platform_link_get_lnk_tun(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_TUN, out_link); +} + +const NMPlatformLnkVlan * +nm_platform_link_get_lnk_vlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VLAN, out_link); +} + +const NMPlatformLnkVrf * +nm_platform_link_get_lnk_vrf(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VRF, out_link); +} + +const NMPlatformLnkVxlan * +nm_platform_link_get_lnk_vxlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VXLAN, out_link); +} + +const NMPlatformLnkWireGuard * +nm_platform_link_get_lnk_wireguard(NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk(self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link); +} + +/*****************************************************************************/ + +static NM_UTILS_FLAGS2STR_DEFINE( + _wireguard_change_flags_to_string, + NMPlatformWireGuardChangeFlags, + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS, "replace-peers"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT, "has-listen-port"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK, "has-fwmark"), ); + +static NM_UTILS_FLAGS2STR_DEFINE( + _wireguard_change_peer_flags_to_string, + NMPlatformWireGuardChangePeerFlags, + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE, "none"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME, "remove"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY, "psk"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL, "ka"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT, "ep"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS, "aips"), + NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS, "remove-aips"), ); + +int +nm_platform_link_wireguard_change(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + nm_assert(klass->link_wireguard_change); + + if (_LOGD_ENABLED()) { + char buf_lnk[256]; + char buf_peers[512]; + char buf_change_flags[100]; + + buf_peers[0] = '\0'; + if (peers_len > 0) { + char *b = buf_peers; + gsize len = sizeof(buf_peers); + guint i; + + nm_utils_strbuf_append_str(&b, &len, " { "); + for (i = 0; i < peers_len; i++) { + nm_utils_strbuf_append_str(&b, &len, " { "); + nm_platform_wireguard_peer_to_string(&peers[i], b, len); + nm_utils_strbuf_seek_end(&b, &len); + if (peer_flags) { + nm_utils_strbuf_append( + &b, + &len, + " (%s)", + _wireguard_change_peer_flags_to_string(peer_flags[i], + buf_change_flags, + sizeof(buf_change_flags))); + } + nm_utils_strbuf_append_str(&b, &len, " } "); + } + nm_utils_strbuf_append_str(&b, &len, "}"); + } + + _LOG3D("link: change wireguard ifindex %d, %s, (%s), %u peers%s", + ifindex, + nm_platform_lnk_wireguard_to_string(lnk_wireguard, buf_lnk, sizeof(buf_lnk)), + _wireguard_change_flags_to_string(change_flags, + buf_change_flags, + sizeof(buf_change_flags)), + peers_len, + buf_peers); + } + + return klass->link_wireguard_change(self, + ifindex, + lnk_wireguard, + peers, + peer_flags, + peers_len, + change_flags); +} + +/*****************************************************************************/ + +/** + * nm_platform_link_tun_add: + * @self: platform instance + * @name: new interface name + * @tap: whether the interface is a TAP + * @owner: interface owner or -1 + * @group: interface group or -1 + * @pi: whether to clear the IFF_NO_PI flag + * @vnet_hdr: whether to set the IFF_VNET_HDR flag + * @multi_queue: whether to set the IFF_MULTI_QUEUE flag + * @out_link: on success, the link object + * @out_fd: (allow-none): if give, return the file descriptor for the + * created device. Note that when creating a non-persistent device, + * this argument is mandatory, otherwise it makes no sense + * to create such an interface. + * The caller is responsible for closing this file descriptor. + * + * Create a TUN or TAP interface. + */ +int +nm_platform_link_tun_add(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd) +{ + char b[255]; + int r; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(name, -NME_BUG); + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(NM_IN_SET(props->type, IFF_TUN, IFF_TAP), -NME_BUG); + + /* creating a non-persistent device requires that the caller handles + * the file descriptor. */ + g_return_val_if_fail(props->persist || out_fd, -NME_BUG); + + NM_SET_OUT(out_fd, -1); + + r = _link_add_check_existing(self, name, NM_LINK_TYPE_TUN, out_link); + if (r < 0) + return r; + + _LOG2D("link: adding link %s", nm_platform_lnk_tun_to_string(props, b, sizeof(b))); + + if (!klass->link_tun_add(self, name, props, out_link, out_fd)) + return -NME_UNSPEC; + return 0; +} + +gboolean +nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent) +{ + const NMPlatformLink *plink; + + plink = nm_platform_link_get(self, ifindex); + if (!plink) + return FALSE; + + if (plink->type != NM_LINK_TYPE_6LOWPAN) + return FALSE; + + if (plink->parent != 0) { + NM_SET_OUT(out_parent, plink->parent); + return TRUE; + } + + /* As of 4.16 kernel does not expose the peer_ifindex as IFA_LINK. + * Find the WPAN device with the same MAC address. */ + if (out_parent) { + const NMPlatformLink *parent_plink; + + parent_plink = nm_platform_link_get_by_address(self, + NM_LINK_TYPE_WPAN, + plink->l_address.data, + plink->l_address.len); + NM_SET_OUT(out_parent, parent_plink ? parent_plink->ifindex : -1); + } + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +link_set_option(NMPlatform *self, + int ifindex, + const char *category, + const char *option, + const char *value) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const char * path; + + if (!category || !option) + return FALSE; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return FALSE; + + path = + nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option); + return nm_platform_sysctl_set(self, + NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path), + value); +} + +static char * +link_get_option(NMPlatform *self, int ifindex, const char *category, const char *option) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const char * path; + + if (!category || !option) + return NULL; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return NULL; + + path = + nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option); + return nm_platform_sysctl_get(self, + NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path)); +} + +static const char * +master_category(NMPlatform *self, int master) +{ + switch (nm_platform_link_get_type(self, master)) { + case NM_LINK_TYPE_BRIDGE: + return "bridge"; + case NM_LINK_TYPE_BOND: + return "bonding"; + default: + return NULL; + } +} + +static const char * +slave_category(NMPlatform *self, int slave) +{ + int master = nm_platform_link_get_master(self, slave); + + if (master <= 0) + return NULL; + + switch (nm_platform_link_get_type(self, master)) { + case NM_LINK_TYPE_BRIDGE: + return "brport"; + default: + return NULL; + } +} + +gboolean +nm_platform_sysctl_master_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + g_return_val_if_fail(value, FALSE); + + return link_set_option(self, ifindex, master_category(self, ifindex), option, value); +} + +char * +nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + + return link_get_option(self, ifindex, master_category(self, ifindex), option); +} + +gboolean +nm_platform_sysctl_slave_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + g_return_val_if_fail(value, FALSE); + + return link_set_option(self, ifindex, slave_category(self, ifindex), option, value); +} + +char * +nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option) +{ + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(option, FALSE); + + return link_get_option(self, ifindex, slave_category(self, ifindex), option); +} + +/*****************************************************************************/ + +gboolean +nm_platform_link_vlan_change(NMPlatform * self, + int ifindex, + _NMVlanFlags flags_mask, + _NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map) +{ + _CHECK_SELF(self, klass, FALSE); + + nm_assert(klass->link_vlan_change); + + g_return_val_if_fail(!n_ingress_map || ingress_map, FALSE); + g_return_val_if_fail(!n_egress_map || egress_map, FALSE); + + flags_set &= flags_mask; + + if (_LOGD_ENABLED()) { + char buf[512]; + char *b = buf; + gsize len, i; + + b[0] = '\0'; + len = sizeof(buf); + + if (flags_mask) + nm_utils_strbuf_append(&b, + &len, + " flags 0x%x/0x%x", + (unsigned) flags_set, + (unsigned) flags_mask); + + if (ingress_reset_all || n_ingress_map) { + nm_utils_strbuf_append_str(&b, &len, " ingress-qos-map"); + nm_platform_vlan_qos_mapping_to_string("", ingress_map, n_ingress_map, b, len); + i = strlen(b); + b += i; + len -= i; + if (ingress_reset_all) + nm_utils_strbuf_append_str(&b, &len, " (reset-all)"); + } + + if (egress_reset_all || n_egress_map) { + nm_utils_strbuf_append_str(&b, &len, " egress-qos-map"); + nm_platform_vlan_qos_mapping_to_string("", egress_map, n_egress_map, b, len); + i = strlen(b); + b += i; + len -= i; + if (egress_reset_all) + nm_utils_strbuf_append_str(&b, &len, " (reset-all)"); + } + + _LOG3D("link: change vlan %s", buf); + } + return klass->link_vlan_change(self, + ifindex, + flags_mask, + flags_set, + ingress_reset_all, + ingress_map, + n_ingress_map, + egress_reset_all, + egress_map, + n_egress_map); +} + +gboolean +nm_platform_link_vlan_set_ingress_map(NMPlatform *self, int ifindex, int from, int to) +{ + NMVlanQosMapping map = { + .from = from, + .to = to, + }; + + return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, &map, 1, FALSE, NULL, 0); +} + +gboolean +nm_platform_link_vlan_set_egress_map(NMPlatform *self, int ifindex, int from, int to) +{ + NMVlanQosMapping map = { + .from = from, + .to = to, + }; + + return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1); +} + +static int +_infiniband_add_add_or_delete(NMPlatform * self, + int ifindex, + int p_key, + gboolean add, + const NMPlatformLink **out_link) +{ + char name[IFNAMSIZ]; + const NMPlatformLink *parent_link; + int r; + + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(ifindex >= 0, -NME_BUG); + g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, -NME_BUG); + + /* the special keys 0x0000 and 0x8000 are not allowed. */ + if (NM_IN_SET(p_key, 0, 0x8000)) + return -NME_UNSPEC; + + parent_link = nm_platform_link_get(self, ifindex); + if (!parent_link) + return -NME_PL_NOT_FOUND; + + if (parent_link->type != NM_LINK_TYPE_INFINIBAND) + return -NME_PL_WRONG_TYPE; + + nmp_utils_new_infiniband_name(name, parent_link->name, p_key); + + if (add) { + r = _link_add_check_existing(self, name, NM_LINK_TYPE_INFINIBAND, out_link); + if (r < 0) + return r; + + _LOG3D("link: adding infiniband partition %s, key %d", name, p_key); + if (!klass->infiniband_partition_add(self, ifindex, p_key, out_link)) + return -NME_UNSPEC; + } else { + _LOG3D("link: deleting infiniband partition %s, key %d", name, p_key); + + if (!klass->infiniband_partition_delete(self, ifindex, p_key)) + return -NME_UNSPEC; + } + + return 0; +} + +int +nm_platform_link_infiniband_add(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link) +{ + return _infiniband_add_add_or_delete(self, parent, p_key, TRUE, out_link); +} + +int +nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key) +{ + return _infiniband_add_add_or_delete(self, parent, p_key, FALSE, NULL); +} + +gboolean +nm_platform_link_infiniband_get_properties(NMPlatform * self, + int ifindex, + int * out_parent, + int * out_p_key, + const char **out_mode) +{ + nm_auto_close int dirfd = -1; + char ifname_verified[IFNAMSIZ]; + const NMPlatformLnkInfiniband *plnk; + const NMPlatformLink * plink; + char * contents; + const char * mode; + int p_key = 0; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + plnk = nm_platform_link_get_lnk_infiniband(self, ifindex, &plink); + + if (!plink || plink->type != NM_LINK_TYPE_INFINIBAND) + return FALSE; + + if (plnk) { + NM_SET_OUT(out_parent, plink->parent); + NM_SET_OUT(out_p_key, plnk->p_key); + NM_SET_OUT(out_mode, plnk->mode); + return TRUE; + } + + /* Could not get the link information via netlink. To support older kernels, + * fallback to reading sysfs. */ + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified); + if (dirfd < 0) + return FALSE; + + contents = + nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode")); + if (!contents) + return FALSE; + if (strstr(contents, "datagram")) + mode = "datagram"; + else if (strstr(contents, "connected")) + mode = "connected"; + else + mode = NULL; + g_free(contents); + + p_key = + nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "pkey"), + 16, + 0, + 0xFFFF, + -1); + if (p_key < 0) + return FALSE; + + NM_SET_OUT(out_parent, plink->parent); + NM_SET_OUT(out_p_key, p_key); + NM_SET_OUT(out_mode, mode); + return TRUE; +} + +gboolean +nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex) +{ + const NMPlatformLink *plink; + int peer_ifindex; + + plink = nm_platform_link_get(self, ifindex); + if (!plink) + return FALSE; + + if (plink->type != NM_LINK_TYPE_VETH) + return FALSE; + + if (plink->parent != 0) { + NM_SET_OUT(out_peer_ifindex, plink->parent); + return TRUE; + } + + /* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */ + if (out_peer_ifindex) { + nm_auto_pop_netns NMPNetns *netns = NULL; + + if (!nm_platform_netns_push(self, &netns)) + return FALSE; + peer_ifindex = nmp_utils_ethtool_get_peer_ifindex(plink->ifindex); + if (peer_ifindex <= 0) + return FALSE; + + *out_peer_ifindex = peer_ifindex; + } + return TRUE; +} + +/** + * nm_platform_link_tun_get_properties: + * @self: the #NMPlatform instance + * @ifindex: the ifindex to look up + * @out_properties: (out) (allow-none): return the read properties + * + * Only recent versions of kernel export tun properties via netlink. + * So, if that's the case, then we have the NMPlatformLnkTun instance + * in the platform cache ready to return. Otherwise, this function + * falls back reading sysctl to obtain the tun properties. That + * is racy, because querying sysctl means that the object might + * be already removed from cache (while NM didn't yet process the + * netlink message). + * + * Hence, to lookup the tun properties, you always need to use this + * function, and use it with care knowing that it might obtain its + * data by reading sysctl. Note that we don't want to add this workaround + * to the platform cache itself, because the cache should (mainly) + * contain data from netlink. To access the sysctl side channel, the + * user needs to do explicitly. + * + * Returns: #TRUE, if the properties could be read. */ +gboolean +nm_platform_link_tun_get_properties(NMPlatform *self, int ifindex, NMPlatformLnkTun *out_properties) +{ + const NMPObject *plobj; + const NMPObject *pllnk; + char ifname[IFNAMSIZ]; + gint64 owner; + gint64 group; + gint64 flags; + + /* we consider also invisible links (those that are not yet in udev). */ + plobj = nm_platform_link_get_obj(self, ifindex, FALSE); + if (!plobj) + return FALSE; + + if (NMP_OBJECT_CAST_LINK(plobj)->type != NM_LINK_TYPE_TUN) + return FALSE; + + pllnk = plobj->_link.netlink.lnk; + if (pllnk) { + nm_assert(NMP_OBJECT_GET_TYPE(pllnk) == NMP_OBJECT_TYPE_LNK_TUN); + nm_assert(NMP_OBJECT_GET_CLASS(pllnk)->lnk_link_type == NM_LINK_TYPE_TUN); + + /* recent kernels expose tun properties via netlink and thus we have them + * in the platform cache. */ + NM_SET_OUT(out_properties, pllnk->lnk_tun); + return TRUE; + } + + /* fallback to reading sysctl. */ + { + nm_auto_close int dirfd = -1; + + dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname); + if (dirfd < 0) + return FALSE; + + owner = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "owner"), + 10, + -1, + G_MAXUINT32, + -2); + if (owner == -2) + return FALSE; + + group = nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "group"), + 10, + -1, + G_MAXUINT32, + -2); + if (group == -2) + return FALSE; + + flags = + nm_platform_sysctl_get_int_checked(self, + NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "tun_flags"), + 16, + 0, + G_MAXINT64, + -1); + if (flags == -1) + return FALSE; + } + + if (out_properties) { + memset(out_properties, 0, sizeof(*out_properties)); + if (owner != -1) { + out_properties->owner_valid = TRUE; + out_properties->owner = owner; + } + if (group != -1) { + out_properties->group_valid = TRUE; + out_properties->group = group; + } + out_properties->type = (flags & TUN_TYPE_MASK); + out_properties->pi = !(flags & IFF_NO_PI); + out_properties->vnet_hdr = !!(flags & IFF_VNET_HDR); + out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE); + out_properties->persist = !!(flags & IFF_PERSIST); + } + return TRUE; +} + +gboolean +nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, _NMDeviceWifiCapabilities *caps) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_capabilities(self, ifindex, caps); +} + +guint32 +nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + + return klass->wifi_get_frequency(self, ifindex); +} + +gboolean +nm_platform_wifi_get_station(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_station(self, ifindex, out_bssid, out_quality, out_rate); +} + +_NM80211Mode +nm_platform_wifi_get_mode(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, _NM_802_11_MODE_UNKNOWN); + + g_return_val_if_fail(ifindex > 0, _NM_802_11_MODE_UNKNOWN); + + return klass->wifi_get_mode(self, ifindex); +} + +void +nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, _NM80211Mode mode) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_set_mode(self, ifindex, mode); +} + +static void +wifi_set_powersave(NMPlatform *p, int ifindex, guint32 powersave) +{ + /* empty */ +} + +void +nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_set_powersave(self, ifindex, powersave); +} + +guint32 +nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + g_return_val_if_fail(freqs != NULL, 0); + + return klass->wifi_find_frequency(self, ifindex, freqs); +} + +void +nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running) +{ + _CHECK_SELF_VOID(self, klass); + + g_return_if_fail(ifindex > 0); + + klass->wifi_indicate_addressing_running(self, ifindex, running); +} + +_NMSettingWirelessWakeOnWLan +nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_get_wake_on_wlan(self, ifindex); +} + +gboolean +nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, _NMSettingWirelessWakeOnWLan wowl) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wifi_set_wake_on_wlan(self, ifindex, wowl); +} + +guint32 +nm_platform_mesh_get_channel(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, 0); + + g_return_val_if_fail(ifindex > 0, 0); + + return klass->mesh_get_channel(self, ifindex); +} + +gboolean +nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->mesh_set_channel(self, ifindex, channel); +} + +gboolean +nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ssid != NULL, FALSE); + + return klass->mesh_set_ssid(self, ifindex, ssid, len); +} + +guint16 +nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_get_pan_id(self, ifindex); +} + +gboolean +nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_pan_id(self, ifindex, pan_id); +} + +guint16 +nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_get_short_addr(self, ifindex); +} + +gboolean +nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_short_addr(self, ifindex, short_addr); +} + +gboolean +nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return klass->wpan_set_channel(self, ifindex, page, channel); +} + +#define TO_STRING_DEV_BUF_SIZE (5 + 15 + 1) +static const char * +_to_string_dev(NMPlatform *self, int ifindex, char *buf, size_t size) +{ + g_assert(buf && size >= TO_STRING_DEV_BUF_SIZE); + + if (ifindex) { + const char *name = ifindex > 0 && self ? nm_platform_link_get_name(self, ifindex) : NULL; + char * buf2; + + strcpy(buf, " dev "); + buf2 = buf + 5; + size -= 5; + + if (name) + g_strlcpy(buf2, name, size); + else + g_snprintf(buf2, size, "%d", ifindex); + } else + buf[0] = 0; + + return buf; +} + +#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; +} + +/*****************************************************************************/ + +gboolean +nm_platform_ethtool_set_wake_on_lan(NMPlatform * self, + int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_wake_on_lan(ifindex, wol, wol_password); +} + +gboolean +nm_platform_ethtool_set_link_settings(NMPlatform * self, + int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_link_settings(ifindex, autoneg, speed, duplex); +} + +gboolean +nm_platform_ethtool_get_link_settings(NMPlatform * self, + int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex); +} + +/*****************************************************************************/ + +NMEthtoolFeatureStates * +nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex) +{ + _CHECK_SELF_NETNS(self, klass, netns, NULL); + + g_return_val_if_fail(ifindex > 0, NULL); + + return nmp_utils_ethtool_get_features(ifindex); +} + +gboolean +nm_platform_ethtool_set_features( + NMPlatform * self, + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_features(ifindex, features, requested, do_set); +} + +gboolean +nm_platform_ethtool_get_link_coalesce(NMPlatform * self, + int ifindex, + NMEthtoolCoalesceState *coalesce) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(coalesce, FALSE); + + return nmp_utils_ethtool_get_coalesce(ifindex, coalesce); +} + +gboolean +nm_platform_ethtool_set_coalesce(NMPlatform * self, + int ifindex, + const NMEthtoolCoalesceState *coalesce) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_coalesce(ifindex, coalesce); +} + +gboolean +nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(ring, FALSE); + + return nmp_utils_ethtool_get_ring(ifindex, ring); +} + +gboolean +nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_ring(ifindex, ring); +} + +gboolean +nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(pause, FALSE); + + return nmp_utils_ethtool_get_pause(ifindex, pause); +} + +gboolean +nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause) +{ + _CHECK_SELF_NETNS(self, klass, netns, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + + return nmp_utils_ethtool_set_pause(ifindex, pause); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nm_platform_lookup_all(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nmp_cache_lookup_all(nm_platform_get_cache(self), cache_id_type, obj); +} + +const NMDedupMultiEntry * +nm_platform_lookup_entry(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nmp_cache_lookup_entry_with_idx_type(nm_platform_get_cache(self), cache_id_type, obj); +} + +const NMDedupMultiHeadEntry * +nm_platform_lookup(NMPlatform *self, const NMPLookup *lookup) +{ + return nmp_cache_lookup(nm_platform_get_cache(self), lookup); +} + +gboolean +nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data) +{ + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + return nm_platform_route_table_is_main( + nm_platform_ip_route_get_effective_table(&obj->ip_route)); +} + +gboolean +nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj, + gpointer user_data) +{ + nm_assert( + NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + return nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&obj->ip_route)) + && obj->ip_route.rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL; +} + +/** + * nm_platform_lookup_clone: + * @self: + * @lookup: + * @predicate: if given, only objects for which @predicate returns %TRUE are included + * in the result. + * @user_data: user data for @predicate + * + * Returns the result of lookup in a GPtrArray. The result array contains + * references objects from the cache, its destroy function will unref them. + * + * The user must unref the GPtrArray, which will also unref the NMPObject + * elements. + * + * The elements in the array *must* not be modified. + * + * Returns: the result of the lookup. + */ +GPtrArray * +nm_platform_lookup_clone(NMPlatform * self, + const NMPLookup * lookup, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + return nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, lookup), + (NMDedupMultiFcnSelectPredicate) predicate, + user_data); +} + +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; +} + +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_utils_ip4_address_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; +} + +gboolean +nm_platform_ip4_address_add(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 32, FALSE); + g_return_val_if_fail(lifetime > 0, FALSE); + g_return_val_if_fail(preferred <= lifetime, FALSE); + g_return_val_if_fail(!label || strlen(label) < sizeof(((NMPlatformIP4Address *) NULL)->label), + FALSE); + + if (_LOGD_ENABLED()) { + NMPlatformIP4Address addr; + + addr = (NMPlatformIP4Address){ + .ifindex = ifindex, + .address = address, + .peer_address = peer_address, + .plen = plen, + .timestamp = 0, /* set it at zero, which to_string will treat as *now* */ + .lifetime = lifetime, + .preferred = preferred, + .n_ifa_flags = flags, + .broadcast_address = broadcast_address, + .use_ip4_broadcast_address = TRUE, + }; + if (label) + g_strlcpy(addr.label, label, sizeof(addr.label)); + + _LOG3D("address: adding or updating IPv4 address: %s", + nm_platform_ip4_address_to_string(&addr, NULL, 0)); + } + return klass->ip4_address_add(self, + ifindex, + address, + plen, + peer_address, + broadcast_address, + lifetime, + preferred, + flags, + label); +} + +gboolean +nm_platform_ip6_address_add(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 128, FALSE); + g_return_val_if_fail(lifetime > 0, FALSE); + g_return_val_if_fail(preferred <= lifetime, FALSE); + + if (_LOGD_ENABLED()) { + NMPlatformIP6Address addr = {0}; + + addr.ifindex = ifindex; + addr.address = address; + addr.peer_address = peer_address; + addr.plen = plen; + addr.timestamp = 0; /* set it to zero, which to_string will treat as *now* */ + addr.lifetime = lifetime; + addr.preferred = preferred; + addr.n_ifa_flags = flags; + + _LOG3D("address: adding or updating IPv6 address: %s", + nm_platform_ip6_address_to_string(&addr, NULL, 0)); + } + return klass + ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags); +} + +gboolean +nm_platform_ip4_address_delete(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char str_peer[INET_ADDRSTRLEN + 50]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 32, FALSE); + + _LOG3D("address: deleting IPv4 address %s/%d, %s%s", + _nm_utils_inet4_ntop(address, b1), + plen, + peer_address != address + ? nm_sprintf_buf(str_peer, "peer %s, ", _nm_utils_inet4_ntop(peer_address, b2)) + : "", + _to_string_dev(self, ifindex, str_dev, sizeof(str_dev))); + return klass->ip4_address_delete(self, ifindex, address, plen, peer_address); +} + +gboolean +nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(plen <= 128, FALSE); + + _LOG3D("address: deleting IPv6 address %s/%d, %s", + _nm_utils_inet6_ntop(&address, sbuf), + plen, + _to_string_dev(self, ifindex, str_dev, sizeof(str_dev))); + return klass->ip6_address_delete(self, ifindex, address, plen); +} + +const NMPlatformIP4Address * +nm_platform_ip4_address_get(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address) +{ + NMPObject obj_id; + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + g_return_val_if_fail(plen <= 32, NULL); + + nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, address, plen, peer_address); + obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id); + nm_assert(!obj || nmp_object_is_visible(obj)); + return NMP_OBJECT_CAST_IP4_ADDRESS(obj); +} + +const NMPlatformIP6Address * +nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address) +{ + NMPObject obj_id; + const NMPObject *obj; + + _CHECK_SELF(self, klass, NULL); + + nm_assert(address); + + nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, address); + obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id); + nm_assert(!obj || nmp_object_is_visible(obj)); + return NMP_OBJECT_CAST_IP6_ADDRESS(obj); +} + +static gboolean +_addr_array_clean_expired(int addr_family, + int ifindex, + GPtrArray * array, + guint32 now, + GHashTable **idx) +{ + guint i; + gboolean any_addrs = FALSE; + + nm_assert_addr_family(addr_family); + nm_assert(ifindex > 0); + nm_assert(now > 0); + + if (!array) + return FALSE; + + /* remove all addresses that are already expired. */ + for (i = 0; i < array->len; i++) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(array->pdata[i]); + +#if NM_MORE_ASSERTS > 10 + nm_assert(a); + nm_assert(a->ifindex == ifindex); + { + const NMPObject *o = NMP_OBJECT_UP_CAST(a); + guint j; + + nm_assert(NMP_OBJECT_GET_CLASS(o)->addr_family == addr_family); + for (j = i + 1; j < array->len; j++) { + const NMPObject *o2 = array->pdata[j]; + + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_GET_TYPE(o2)); + nm_assert(!nmp_object_id_equal(o, o2)); + } + } +#endif + + if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) { + /* temporary addresses are never added explicitly by NetworkManager but + * kernel adds them via mngtempaddr flag. + * + * We drop them from this list. */ + goto clear_and_next; + } + + if (!nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, now, NULL)) + goto clear_and_next; + + if (idx) { + if (G_UNLIKELY(!*idx)) { + *idx = g_hash_table_new((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + if (!g_hash_table_add(*idx, (gpointer) NMP_OBJECT_UP_CAST(a))) + nm_assert_not_reached(); + } + any_addrs = TRUE; + continue; + +clear_and_next: + nmp_object_unref(g_steal_pointer(&array->pdata[i])); + } + + return any_addrs; +} + +static gboolean +ip4_addr_subnets_is_plain_address(const GPtrArray *addresses, gconstpointer needle) +{ + return needle >= (gconstpointer) &addresses->pdata[0] + && needle < (gconstpointer) &addresses->pdata[addresses->len]; +} + +static const NMPObject ** +ip4_addr_subnets_addr_list_get(const GPtrArray *addr_list, guint idx) +{ + nm_assert(addr_list); + nm_assert(addr_list->len > 1); + nm_assert(idx < addr_list->len); + nm_assert(addr_list->pdata[idx]); + nm_assert(!(*((gpointer *) addr_list->pdata[idx])) + || NMP_OBJECT_CAST_IP4_ADDRESS(*((gpointer *) addr_list->pdata[idx]))); + nm_assert(idx == 0 || ip4_addr_subnets_addr_list_get(addr_list, idx - 1)); + return addr_list->pdata[idx]; +} + +static void +ip4_addr_subnets_destroy_index(GHashTable *subnets, const GPtrArray *addresses) +{ + GHashTableIter iter; + gpointer p; + + if (!subnets) + return; + + g_hash_table_iter_init(&iter, subnets); + while (g_hash_table_iter_next(&iter, NULL, &p)) { + if (!ip4_addr_subnets_is_plain_address(addresses, p)) + g_ptr_array_free((GPtrArray *) p, TRUE); + } + + g_hash_table_unref(subnets); +} + +static GHashTable * +ip4_addr_subnets_build_index(const GPtrArray *addresses, + gboolean consider_flags, + gboolean full_index) +{ + GHashTable *subnets; + guint i; + + nm_assert(addresses && addresses->len); + + subnets = g_hash_table_new(nm_direct_hash, NULL); + + /* Build a hash table of all addresses per subnet */ + for (i = 0; i < addresses->len; i++) { + const NMPlatformIP4Address *address; + gpointer p_address; + GPtrArray * addr_list; + guint32 net; + int position; + gpointer p; + + if (!addresses->pdata[i]) + continue; + + p_address = &addresses->pdata[i]; + address = NMP_OBJECT_CAST_IP4_ADDRESS(addresses->pdata[i]); + + net = address->address & _nm_utils_ip4_prefix_to_netmask(address->plen); + if (!g_hash_table_lookup_extended(subnets, GUINT_TO_POINTER(net), NULL, &p)) { + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address); + continue; + } + nm_assert(p); + + if (full_index) { + if (ip4_addr_subnets_is_plain_address(addresses, p)) { + addr_list = g_ptr_array_new(); + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), addr_list); + g_ptr_array_add(addr_list, p); + } else + addr_list = p; + + if (!consider_flags || NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY)) + position = -1; /* append */ + else + position = 0; /* prepend */ + g_ptr_array_insert(addr_list, position, p_address); + } else { + /* we only care about the primary. No need to track the secondaries + * as a GPtrArray. */ + nm_assert(ip4_addr_subnets_is_plain_address(addresses, p)); + if (consider_flags && !NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY)) { + g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address); + } + } + } + + return subnets; +} + +/** + * ip4_addr_subnets_is_secondary: + * @address: an address + * @subnets: the hash table mapping subnets to addresses + * @addresses: array of addresses in the hash table + * @out_addr_list: array of addresses belonging to the same subnet + * + * Checks whether @address is secondary and returns in @out_addr_list the list of addresses + * belonging to the same subnet, if it contains other elements. + * + * Returns: %TRUE if the address is secondary, %FALSE otherwise + */ +static gboolean +ip4_addr_subnets_is_secondary(const NMPObject * address, + GHashTable * subnets, + const GPtrArray * addresses, + const GPtrArray **out_addr_list) +{ + const NMPlatformIP4Address *a; + const GPtrArray * addr_list; + gconstpointer p; + guint32 net; + const NMPObject ** o; + + a = NMP_OBJECT_CAST_IP4_ADDRESS(address); + + net = a->address & _nm_utils_ip4_prefix_to_netmask(a->plen); + p = g_hash_table_lookup(subnets, GUINT_TO_POINTER(net)); + nm_assert(p); + if (!ip4_addr_subnets_is_plain_address(addresses, p)) { + addr_list = p; + nm_assert(addr_list->len > 1); + NM_SET_OUT(out_addr_list, addr_list); + o = ip4_addr_subnets_addr_list_get(addr_list, 0); + nm_assert(o && *o); + if (*o != address) + return TRUE; + } else { + NM_SET_OUT(out_addr_list, NULL); + return address != *((gconstpointer *) p); + } + return FALSE; +} + +typedef enum { + IP6_ADDR_SCOPE_LOOPBACK, + IP6_ADDR_SCOPE_LINKLOCAL, + IP6_ADDR_SCOPE_SITELOCAL, + IP6_ADDR_SCOPE_OTHER, +} IP6AddrScope; + +static IP6AddrScope +ip6_address_scope(const NMPlatformIP6Address *a) +{ + if (IN6_IS_ADDR_LOOPBACK(&a->address)) + return IP6_ADDR_SCOPE_LOOPBACK; + if (IN6_IS_ADDR_LINKLOCAL(&a->address)) + return IP6_ADDR_SCOPE_LINKLOCAL; + if (IN6_IS_ADDR_SITELOCAL(&a->address)) + return IP6_ADDR_SCOPE_SITELOCAL; + return IP6_ADDR_SCOPE_OTHER; +} + +static int +ip6_address_scope_cmp(gconstpointer p_a, gconstpointer p_b, gpointer increasing) +{ + const NMPlatformIP6Address *a; + const NMPlatformIP6Address *b; + + if (!increasing) + NM_SWAP(&p_a, &p_b); + + a = NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_a); + b = NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_b); + + NM_CMP_DIRECT(ip6_address_scope(a), ip6_address_scope(b)); + return 0; +} + +/** + * nm_platform_ip_address_sync: + * @self: platform instance + * @addr_family: the address family AF_INET or AF_INET6. + * @ifindex: Interface index + * @known_addresses: List of addresses. The list will be modified and only + * addresses that were successfully added will be kept in the list. + * That means, expired addresses and addresses that could not be added + * will be dropped. + * Hence, the input argument @known_addresses is also an output argument + * telling which addresses were successfully added. + * Addresses are removed by unrefing the instance via nmp_object_unref() + * and leaving a NULL tombstone. + * @addresses_prune: (allow-none): the list of addresses to delete. + * If platform has such an address configured, it will be deleted + * at the beginning of the sync. Note that the array will be modified + * by the function. + * Note that the addresses must be properly sorted, by their priority. + * Create this list with nm_platform_ip_address_get_prune_list() which + * gets the sorting right. + * + * A convenience function to synchronize addresses for a specific interface + * with the least possible disturbance. It simply removes addresses that are + * not listed and adds addresses that are. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + GPtrArray * addresses_prune) +{ + const gint32 now = nm_utils_get_monotonic_timestamp_sec(); + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_hashtable GHashTable *known_addresses_idx = NULL; + GPtrArray * plat_addresses; + GHashTable * known_subnets = NULL; + guint32 ifa_flags; + guint i_plat; + guint i_know; + guint i; + guint j; + + _CHECK_SELF(self, klass, FALSE); + + /* The order we want to enforce is only among addresses with the same + * scope, as the kernel keeps addresses sorted by scope. Therefore, + * apply the same sorting to known addresses, so that we don't try to + * unnecessary change the order of addresses with different scopes. */ + if (!IS_IPv4) { + if (known_addresses) + g_ptr_array_sort_with_data(known_addresses, + ip6_address_scope_cmp, + GINT_TO_POINTER(TRUE)); + } + + if (!_addr_array_clean_expired(addr_family, + ifindex, + known_addresses, + now, + &known_addresses_idx)) + known_addresses = NULL; + + /* @plat_addresses must be sorted in decreasing priority order (highest priority addresses first), contrary to + * @known_addresses which is in increasing priority order (lowest priority addresses first). */ + plat_addresses = addresses_prune; + + if (nm_g_ptr_array_len(plat_addresses) > 0) { + /* Delete unknown addresses */ + if (IS_IPv4) { + GHashTable *plat_subnets; + + plat_subnets = ip4_addr_subnets_build_index(plat_addresses, TRUE, TRUE); + + for (i = 0; i < plat_addresses->len; i++) { + const NMPObject * plat_obj; + const NMPlatformIP4Address *plat_address; + const GPtrArray * addr_list; + + plat_obj = plat_addresses->pdata[i]; + if (!plat_obj) { + /* Already deleted */ + continue; + } + + plat_address = NMP_OBJECT_CAST_IP4_ADDRESS(plat_obj); + + if (known_addresses) { + const NMPObject *o; + + o = g_hash_table_lookup(known_addresses_idx, plat_obj); + if (o) { + gboolean secondary; + + if (!known_subnets) + known_subnets = + ip4_addr_subnets_build_index(known_addresses, FALSE, FALSE); + + secondary = + ip4_addr_subnets_is_secondary(o, known_subnets, known_addresses, NULL); + if (secondary == NM_FLAGS_HAS(plat_address->n_ifa_flags, IFA_F_SECONDARY)) { + /* if we have an existing known-address, with matching secondary role, + * do not delete the platform-address. */ + continue; + } + } + } + + nm_platform_ip4_address_delete(self, + ifindex, + plat_address->address, + plat_address->plen, + plat_address->peer_address); + + if (!ip4_addr_subnets_is_secondary(plat_obj, + plat_subnets, + plat_addresses, + &addr_list) + && addr_list) { + /* If we just deleted a primary addresses and there were + * secondary ones the kernel can do two things, depending on + * version and sysctl setting: delete also secondary addresses + * or promote a secondary to primary. Ensure that secondary + * addresses are deleted, so that we can start with a clean + * slate and add addresses in the right order. */ + for (j = 1; j < addr_list->len; j++) { + const NMPObject **o; + + o = ip4_addr_subnets_addr_list_get(addr_list, j); + nm_assert(o); + + if (*o) { + const NMPlatformIP4Address *a; + + a = NMP_OBJECT_CAST_IP4_ADDRESS(*o); + nm_platform_ip4_address_delete(self, + ifindex, + a->address, + a->plen, + a->peer_address); + nmp_object_unref(*o); + *o = NULL; + } + } + } + } + ip4_addr_subnets_destroy_index(plat_subnets, plat_addresses); + } else { + guint known_addresses_len; + IP6AddrScope cur_scope; + gboolean delete_remaining_addrs; + + g_ptr_array_sort_with_data(plat_addresses, + ip6_address_scope_cmp, + GINT_TO_POINTER(FALSE)); + + known_addresses_len = known_addresses ? known_addresses->len : 0; + + /* First, compare every address whether it is still a "known address", that is, whether + * to keep it or to delete it. + * + * If we don't find a matching valid address in @known_addresses, we will delete + * plat_addr. + * + * Certain addresses, like temporary addresses, are ignored by this function + * if not run with full_sync. These addresses are usually not managed by NetworkManager + * directly, or at least, they are not managed via nm_platform_ip6_address_sync(). + * Only in full_sync mode, we really want to get rid of them (usually, when we take + * the interface down). + * + * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */ + for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) { + const NMPObject * plat_obj = plat_addresses->pdata[i_plat]; + const NMPObject * know_obj; + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj); + + if (known_addresses_idx) { + know_obj = g_hash_table_lookup(known_addresses_idx, plat_obj); + if (know_obj + && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS(know_obj)->plen) { + /* technically, plen is not part of the ID for IPv6 addresses and thus + * @plat_addr is essentially the same address as @know_addr (regrading + * its identity, not its other attributes). + * However, we cannot modify an existing addresses' plen without + * removing and readding it. Thus, only keep plat_addr, if the plen + * matches. + * + * keep this one, and continue */ + continue; + } + } + + nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen); + nmp_object_unref(g_steal_pointer(&plat_addresses->pdata[i_plat])); + } + + /* Next, we must preserve the priority of the routes. That is, source address + * selection will choose addresses in the order as they are reported by kernel. + * Note that the order in @plat_addresses of the remaining matches is highest + * priority first. + * We need to compare this to the order of addresses with same scope in + * @known_addresses (which has lowest priority first). + * + * If we find a first discrepancy, we need to delete all remaining addresses + * with same scope from that point on, because below we must re-add all the + * addresses in the right order to get their priority right. */ + cur_scope = IP6_ADDR_SCOPE_LOOPBACK; + delete_remaining_addrs = FALSE; + i_plat = plat_addresses->len; + i_know = 0; + while (i_plat > 0) { + const NMPlatformIP6Address *plat_addr = + NMP_OBJECT_CAST_IP6_ADDRESS(plat_addresses->pdata[--i_plat]); + IP6AddrScope plat_scope; + + if (!plat_addr) + continue; + + plat_scope = ip6_address_scope(plat_addr); + if (cur_scope != plat_scope) { + nm_assert(cur_scope < plat_scope); + delete_remaining_addrs = FALSE; + cur_scope = plat_scope; + } + + if (!delete_remaining_addrs) { + delete_remaining_addrs = TRUE; + for (; i_know < known_addresses_len; i_know++) { + const NMPlatformIP6Address *know_addr = + NMP_OBJECT_CAST_IP6_ADDRESS(known_addresses->pdata[i_know]); + IP6AddrScope know_scope; + + if (!know_addr) + continue; + + know_scope = ip6_address_scope(know_addr); + if (know_scope < plat_scope) + continue; + + if (IN6_ARE_ADDR_EQUAL(&plat_addr->address, &know_addr->address)) { + /* we have a match. Mark address as handled. */ + i_know++; + delete_remaining_addrs = FALSE; + goto next_plat; + } + + /* plat_address has no match. Now delete_remaining_addrs is TRUE and we will + * delete all the remaining addresses with cur_scope. */ + break; + } + } + + nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen); +next_plat:; + } + } + } + + if (!known_addresses) + return TRUE; + + if (IS_IPv4) + ip4_addr_subnets_destroy_index(known_subnets, known_addresses); + + ifa_flags = nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) + ? IFA_F_NOPREFIXROUTE + : 0; + + /* Add missing addresses. New addresses are added by kernel with top + * priority. + */ + for (i_know = 0; i_know < known_addresses->len; i_know++) { + const NMPlatformIPXAddress *known_address; + const NMPObject * o; + guint32 lifetime; + guint32 preferred; + + o = known_addresses->pdata[i_know]; + if (!o) + continue; + + nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)); + + known_address = NMP_OBJECT_CAST_IPX_ADDRESS(o); + + lifetime = nmp_utils_lifetime_get(known_address->ax.timestamp, + known_address->ax.lifetime, + known_address->ax.preferred, + now, + &preferred); + nm_assert(lifetime > 0); + + if (IS_IPv4) { + if (!nm_platform_ip4_address_add( + self, + ifindex, + known_address->a4.address, + known_address->a4.plen, + known_address->a4.peer_address, + nm_platform_ip4_broadcast_address_from_addr(&known_address->a4), + lifetime, + preferred, + ifa_flags, + known_address->a4.label)) { + /* ignore error, for unclear reasons. */ + } + } else { + if (!nm_platform_ip6_address_add(self, + ifindex, + known_address->a6.address, + known_address->a6.plen, + known_address->a6.peer_address, + lifetime, + preferred, + ifa_flags | known_address->a6.n_ifa_flags)) + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex) +{ + gboolean success = TRUE; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) + success &= nm_platform_ip4_address_sync(self, ifindex, NULL); + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) + success &= nm_platform_ip6_address_sync(self, ifindex, NULL, TRUE); + return success; +} + +/*****************************************************************************/ + +static gboolean +_err_inval_due_to_ipv6_tentative_pref_src(NMPlatform *self, const NMPObject *obj) +{ + const NMPlatformIP6Route * r; + const NMPlatformIP6Address *a; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + /* trying to add an IPv6 route with pref-src fails, if the address is + * still tentative (rh#1452684). We need to hack around that. + * + * Detect it, by guessing whether that's the case. */ + + if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_ROUTE) + return FALSE; + + r = NMP_OBJECT_CAST_IP6_ROUTE(obj); + + /* we only allow this workaround for routes added manually by the user. */ + if (r->rt_source != NM_IP_CONFIG_SOURCE_USER) + return FALSE; + + if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src)) + return FALSE; + + a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src); + if (!a) + return FALSE; + if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE) + || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED)) + return FALSE; + + return TRUE; +} + +GPtrArray * +nm_platform_ip_address_get_prune_list(NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + GPtrArray * result; + CList * iter; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family)), ifindex); + + head_entry = nm_platform_lookup(self, &lookup); + + if (!head_entry) + return NULL; + + result = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nmp_object_unref); + + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj; + + if (!IS_IPv4) { + if (exclude_ipv6_temporary_addrs + && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_TEMPORARY)) + continue; + } + + g_ptr_array_add(result, (gpointer) nmp_object_ref(obj)); + } + + if (result->len == 0) { + g_ptr_array_unref(result); + return NULL; + } + return result; +} + +GPtrArray * +nm_platform_ip_route_get_prune_list(NMPlatform * self, + int addr_family, + int ifindex, + NMIPRouteTableSyncMode route_table_sync) +{ + NMPLookup lookup; + GPtrArray * routes_prune; + const NMDedupMultiHeadEntry *head_entry; + CList * iter; + NMPlatformIP4Route rt_local4; + NMPlatformIP6Route rt_local6; + NMPlatformIP6Route rt_mcast6; + const NMPlatformLink * pllink; + const NMPlatformLnkVrf * lnk_vrf; + guint32 local_table; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + nm_assert(NM_IN_SET(route_table_sync, + NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, + NM_IP_ROUTE_TABLE_SYNC_MODE_FULL, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE)); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), ifindex); + head_entry = nm_platform_lookup(self, &lookup); + if (!head_entry) + return NULL; + + lnk_vrf = nm_platform_link_get_lnk_vrf(self, ifindex, &pllink); + if (!lnk_vrf && pllink && pllink->master > 0) + lnk_vrf = nm_platform_link_get_lnk_vrf(self, pllink->master, NULL); + local_table = lnk_vrf ? lnk_vrf->table : RT_TABLE_LOCAL; + + rt_local4.plen = 0; + rt_local6.plen = 0; + rt_mcast6.plen = 0; + + routes_prune = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nm_dedup_multi_obj_unref); + + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMPObject * obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj; + const NMPlatformIPXRoute *rt = NMP_OBJECT_CAST_IPX_ROUTE(obj); + + switch (route_table_sync) { + case NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN: + if (!nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&rt->rx))) + continue; + break; + case NM_IP_ROUTE_TABLE_SYNC_MODE_FULL: + if (nm_platform_ip_route_get_effective_table(&rt->rx) == RT_TABLE_LOCAL) + continue; + break; + case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL: + + /* FIXME: we should better handle routes that are automatically added by kernel. + * + * For now, make a good guess which are those routes and exclude them from + * pruning them. */ + + if (NM_IS_IPv4(addr_family)) { + /* for each IPv4 address kernel adds a route like + * + * local $ADDR dev $IFACE table local proto kernel scope host src $PRIMARY_ADDR + * + * Check whether route could be of that kind. */ + if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table + && rt->rx.plen == 32 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL + && rt->rx.metric == 0 + && rt->r4.scope_inv == nm_platform_route_scope_inv(RT_SCOPE_HOST) + && rt->r4.gateway == INADDR_ANY) { + if (rt_local4.plen == 0) { + rt_local4 = (NMPlatformIP4Route){ + .ifindex = ifindex, + .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL), + .plen = 32, + .rt_source = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL, + .metric = 0, + .table_coerced = nm_platform_route_table_coerce(local_table), + .scope_inv = nm_platform_route_scope_inv(RT_SCOPE_HOST), + .gateway = INADDR_ANY, + }; + } + + /* the possible "network" depends on the addresses we have. We don't check that + * carefully. If the other parameters match, we assume that this route is the one + * generated by kernel. */ + rt_local4.network = rt->r4.network; + rt_local4.pref_src = rt->r4.pref_src; + + /* to be more confident about comparing the value, use our nm_platform_ip4_route_cmp() + * implementation. That will also consider parameters that we leave unspecified here. */ + if (nm_platform_ip4_route_cmp(&rt->r4, + &rt_local4, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + == 0) + continue; + } + } else { + /* for each IPv6 address (that is no longer tentative) kernel adds a route like + * + * local $ADDR dev $IFACE table local proto kernel metric 0 pref medium + * + * Same as for the IPv4 case. */ + if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table + && rt->rx.plen == 128 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL + && rt->rx.metric == 0 && rt->r6.rt_pref == NM_ICMPV6_ROUTER_PREF_MEDIUM + && IN6_IS_ADDR_UNSPECIFIED(&rt->r6.gateway)) { + if (rt_local6.plen == 0) { + rt_local6 = (NMPlatformIP6Route){ + .ifindex = ifindex, + .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL), + .plen = 128, + .rt_source = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL, + .metric = 0, + .table_coerced = nm_platform_route_table_coerce(local_table), + .rt_pref = NM_ICMPV6_ROUTER_PREF_MEDIUM, + .gateway = IN6ADDR_ANY_INIT, + }; + } + + rt_local6.network = rt->r6.network; + + if (nm_platform_ip6_route_cmp(&rt->r6, + &rt_local6, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + == 0) + continue; + } + + /* Kernels < 5.11 add a route like: + * + * unicast ff00::/8 dev $IFACE proto boot scope global metric 256 pref medium + * + * to allow sending and receiving IPv6 multicast traffic. Don't remove it. + * Since kernel 5.11 the route looks like: + * + * multicast ff00::/8 dev $IFACE proto kernel metric 256 pref medium + * + * As NM ignores routes with rtm_type multicast, there is no need for the code + * below on newer kernels. + */ + if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table + && rt->rx.plen == 8 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_BOOT + && rt->rx.metric == 256 && rt->r6.rt_pref == NM_ICMPV6_ROUTER_PREF_MEDIUM + && IN6_IS_ADDR_UNSPECIFIED(&rt->r6.gateway)) { + if (rt_mcast6.plen == 0) { + rt_mcast6 = (NMPlatformIP6Route){ + .ifindex = ifindex, + .type_coerced = nm_platform_route_type_coerce(RTN_UNICAST), + .plen = 8, + .rt_source = NM_IP_CONFIG_SOURCE_RTPROT_BOOT, + .metric = 256, + .table_coerced = nm_platform_route_table_coerce(local_table), + .rt_pref = NM_ICMPV6_ROUTER_PREF_MEDIUM, + .gateway = IN6ADDR_ANY_INIT, + .network = {{{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + }; + } + + if (nm_platform_ip6_route_cmp(&rt->r6, + &rt_mcast6, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + == 0) + continue; + } + } + break; + + case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE: + break; + + default: + nm_assert_not_reached(); + break; + } + + g_ptr_array_add(routes_prune, (gpointer) nmp_object_ref(obj)); + } + + if (routes_prune->len == 0) { + g_ptr_array_unref(routes_prune); + return NULL; + } + return routes_prune; +} + +/** + * nm_platform_ip_route_sync: + * @self: the #NMPlatform instance. + * @addr_family: AF_INET or AF_INET6. + * @ifindex: the @ifindex for which the routes are to be added. + * @routes: (allow-none): a list of routes to configure. Must contain + * NMPObject instances of routes, according to @addr_family. + * @routes_prune: (allow-none): the list of routes to delete. + * If platform has such a route configured, it will be deleted + * at the end of the operation. Note that if @routes contains + * the same route, then it will not be deleted. @routes overrules + * @routes_prune list. + * @out_temporary_not_available: (allow-none) (out): routes that could + * currently not be synced. The caller shall keep them and try later again. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_ip_route_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * routes, + GPtrArray * routes_prune, + GPtrArray **out_temporary_not_available) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPlatformVTableRoute *vt; + gs_unref_hashtable GHashTable *routes_idx = NULL; + const NMPObject * conf_o; + const NMDedupMultiEntry * plat_entry; + guint i; + int i_type; + gboolean success = TRUE; + char sbuf1[sizeof(_nm_utils_to_string_buffer)]; + char sbuf2[sizeof(_nm_utils_to_string_buffer)]; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + vt = &nm_platform_vtable_route.vx[IS_IPv4]; + + for (i_type = 0; routes && i_type < 2; i_type++) { + for (i = 0; i < routes->len; i++) { + int r, r2; + gboolean gateway_route_added = FALSE; + + conf_o = routes->pdata[i]; + + if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->is_external) { + /* This route is added externally. We don't have our own agenda to + * add it, so skip. */ + continue; + } + + /* User space cannot add IPv6 routes with metric 0. However, kernel can, and we might track such + * routes in @route as they are present external. As we already skipped external routes above, + * we don't expect a user's choice to add such a route (it won't work anyway). */ + nm_assert( + IS_IPv4 + || nm_platform_ip6_route_get_effective_metric(NMP_OBJECT_CAST_IP6_ROUTE(conf_o)) + != 0); + +#define VTABLE_IS_DEVICE_ROUTE(vt, o) \ + (vt->is_ip4 ? (NMP_OBJECT_CAST_IP4_ROUTE(o)->gateway == 0) \ + : IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(o)->gateway)) + + if ((i_type == 0 && !VTABLE_IS_DEVICE_ROUTE(vt, conf_o)) + || (i_type == 1 && VTABLE_IS_DEVICE_ROUTE(vt, conf_o))) { + /* we add routes in two runs over @i_type. + * + * First device routes, then gateway routes. */ + continue; + } + + if (!routes_idx) { + routes_idx = g_hash_table_new((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + if (!g_hash_table_add(routes_idx, (gpointer) conf_o)) { + _LOG3D("route-sync: skip adding duplicate route %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + continue; + } + + plat_entry = nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); + if (plat_entry) { + const NMPObject *plat_o; + + plat_o = plat_entry->obj; + + if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), + NMP_OBJECT_CAST_IPX_ROUTE(plat_o), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + == 0) + continue; + + /* we need to replace the existing route with a (slightly) different + * one. Delete it first. */ + if (!nm_platform_object_delete(self, plat_o)) { + /* ignore error. */ + } + } + +sync_route_add: + r = nm_platform_ip_route_add(self, + NMP_NLM_FLAG_APPEND + | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, + conf_o); + if (r < 0) { + if (r == -EEXIST) { + /* Don't fail for EEXIST. It's not clear that the existing route + * is identical to the one that we were about to add. However, + * above we should have deleted conflicting (non-identical) routes. */ + if (_LOGD_ENABLED()) { + plat_entry = + nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); + if (!plat_entry) { + _LOG3D("route-sync: adding route %s failed with EEXIST, however we " + "cannot find such a route", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), + NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + != 0) { + _LOG3D("route-sync: adding route %s failed due to existing " + "(different!) route %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nmp_object_to_string(plat_entry->obj, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf2, + sizeof(sbuf2))); + } + } + } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { + _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + } else if (r == -EINVAL && out_temporary_not_available + && _err_inval_due_to_ipv6_tentative_pref_src(self, conf_o)) { + _LOG3D("route-sync: ignore failure to add IPv6 route with tentative IPv6 " + "pref-src: %s: %s", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + if (!*out_temporary_not_available) + *out_temporary_not_available = + g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref); + g_ptr_array_add(*out_temporary_not_available, + (gpointer) nmp_object_ref(conf_o)); + } else if (!gateway_route_added + && ((r == -ENETUNREACH && vt->is_ip4 + && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway) + || (r == -EHOSTUNREACH && !vt->is_ip4 + && !IN6_IS_ADDR_UNSPECIFIED( + &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) { + NMPObject oo; + + if (vt->is_ip4) { + const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o); + + nmp_object_stackinit( + &oo, + NMP_OBJECT_TYPE_IP4_ROUTE, + &((NMPlatformIP4Route){ + .ifindex = rt->ifindex, + .network = rt->gateway, + .plen = 32, + .metric = nm_platform_ip4_route_get_effective_metric(rt), + .rt_source = rt->rt_source, + .table_coerced = nm_platform_ip_route_get_effective_table( + NM_PLATFORM_IP_ROUTE_CAST(rt)), + })); + } else { + const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o); + + nmp_object_stackinit( + &oo, + NMP_OBJECT_TYPE_IP6_ROUTE, + &((NMPlatformIP6Route){ + .ifindex = rt->ifindex, + .network = rt->gateway, + .plen = 128, + .metric = nm_platform_ip6_route_get_effective_metric(rt), + .rt_source = rt->rt_source, + .table_coerced = nm_platform_ip_route_get_effective_table( + NM_PLATFORM_IP_ROUTE_CAST(rt)), + })); + } + + _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct " + "route to gateway %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r), + nmp_object_to_string(&oo, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf2, + sizeof(sbuf2))); + + r2 = nm_platform_ip_route_add(self, + NMP_NLM_FLAG_APPEND + | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, + &oo); + + if (r2 < 0) { + _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r2)); + } + + gateway_route_added = TRUE; + goto sync_route_add; + } else { + _LOG3W("route-sync: failure to add IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1)), + nm_strerror(r)); + success = FALSE; + } + } + } + } + + if (routes_prune) { + if (routes) { + for (i = 0; i < routes->len; i++) { + conf_o = routes->pdata[i]; + + if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->is_external) { + /* this is only to catch the case where an external route is + * both in @routes and @routes_prune list. In that case, + * @routes should win and we should not remove the address. */ + if (!routes_idx) { + routes_idx = g_hash_table_new((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + g_hash_table_add(routes_idx, (gpointer) conf_o); + continue; + } + } + } + + for (i = 0; i < routes_prune->len; i++) { + const NMPObject *prune_o; + + prune_o = routes_prune->pdata[i]; + + nm_assert((NM_IS_IPv4(addr_family) + && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP4_ROUTE) + || (!NM_IS_IPv4(addr_family) + && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP6_ROUTE)); + + if (nm_g_hash_table_lookup(routes_idx, prune_o)) + continue; + + if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, prune_o)) + continue; + + if (!nm_platform_object_delete(self, prune_o)) { + /* ignore error... */ + } + } + } + + return success; +} + +gboolean +nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex) +{ + gboolean success = TRUE; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + + routes_prune = nm_platform_ip_route_get_prune_list(self, + AF_INET, + ifindex, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE); + success &= nm_platform_ip_route_sync(self, AF_INET, ifindex, NULL, routes_prune, NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + + routes_prune = nm_platform_ip_route_get_prune_list(self, + AF_INET6, + ifindex, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE); + success &= nm_platform_ip_route_sync(self, AF_INET6, ifindex, NULL, routes_prune, NULL); + } + return success; +} + +/*****************************************************************************/ + +static guint8 +_ip_route_scope_inv_get_normalized(const NMPlatformIP4Route *route) +{ + /* in kernel, you cannot set scope to RT_SCOPE_NOWHERE (255). + * That means, in NM, we treat RT_SCOPE_NOWHERE as unset, and detect + * it based on the presence of the gateway. In other words, when adding + * a route with scope RT_SCOPE_NOWHERE (in NetworkManager) to kernel, + * the resulting scope will be either "link" or "universe" (depending + * on the gateway). + * + * Note that internally, we track @scope_inv is the inverse of scope, + * so that the default equals zero (~(RT_SCOPE_NOWHERE)). + **/ + if (route->scope_inv == 0) { + if (route->type_coerced == nm_platform_route_type_coerce(RTN_LOCAL)) + return nm_platform_route_scope_inv(RT_SCOPE_HOST); + else { + return nm_platform_route_scope_inv(!route->gateway ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE); + } + } + return route->scope_inv; +} + +static guint8 +_route_pref_normalize(guint8 pref) +{ + /* for kernel (and ICMPv6) pref can only have one of 3 values. Normalize. */ + return NM_IN_SET(pref, NM_ICMPV6_ROUTER_PREF_LOW, NM_ICMPV6_ROUTER_PREF_HIGH) + ? pref + : NM_ICMPV6_ROUTER_PREF_MEDIUM; +} + +/** + * nm_platform_ip_route_normalize: + * @addr_family: AF_INET or AF_INET6 + * @route: an NMPlatformIP4Route or NMPlatformIP6Route instance, depending on @addr_family. + * + * Adding a route to kernel via nm_platform_ip_route_add() will normalize/coerce some + * properties of the route. This function modifies (normalizes) the route like it + * would be done by adding the route in kernel. + * + * Note that this function is related to NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY + * in that if two routes compare semantically equal, after normalizing they also shall + * compare equal with NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL. + */ +void +nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route) +{ + NMPlatformIP4Route *r4; + NMPlatformIP6Route *r6; + + route->table_coerced = + nm_platform_route_table_coerce(nm_platform_ip_route_get_effective_table(route)); + route->table_any = FALSE; + + route->rt_source = nmp_utils_ip_config_source_round_trip_rtprot(route->rt_source); + + switch (addr_family) { + case AF_INET: + r4 = (NMPlatformIP4Route *) route; + route->metric = nm_platform_ip4_route_get_effective_metric(r4); + route->metric_any = FALSE; + r4->network = nm_utils_ip4_address_clear_host_address(r4->network, r4->plen); + r4->scope_inv = _ip_route_scope_inv_get_normalized(r4); + break; + case AF_INET6: + r6 = (NMPlatformIP6Route *) route; + route->metric = nm_platform_ip6_route_get_effective_metric(r6); + route->metric_any = FALSE; + nm_utils_ip6_address_clear_host_address(&r6->network, &r6->network, r6->plen); + nm_utils_ip6_address_clear_host_address(&r6->src, &r6->src, r6->src_plen); + break; + default: + nm_assert_not_reached(); + break; + } +} + +static int +_ip_route_add(NMPlatform *self, NMPNlmFlags flags, int addr_family, gconstpointer route) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + int ifindex; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(route); + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + ifindex = ((const NMPlatformIPRoute *) route)->ifindex; + _LOG3D("route: %-10s IPv%c route: %s", + _nmp_nlm_flag_to_string(flags & NMP_NLM_FLAG_FMASK), + nm_utils_addr_family_to_char(addr_family), + NM_IS_IPv4(addr_family) ? nm_platform_ip4_route_to_string(route, sbuf, sizeof(sbuf)) + : nm_platform_ip6_route_to_string(route, sbuf, sizeof(sbuf))); + + return klass->ip_route_add(self, flags, addr_family, route); +} + +int +nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route) +{ + int addr_family; + + switch (NMP_OBJECT_GET_TYPE(route)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + addr_family = AF_INET; + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + addr_family = AF_INET6; + break; + default: + g_return_val_if_reached(FALSE); + } + + return _ip_route_add(self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE(route)); +} + +int +nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route) +{ + return _ip_route_add(self, flags, AF_INET, route); +} + +int +nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route) +{ + return _ip_route_add(self, flags, AF_INET6, route); +} + +gboolean +nm_platform_object_delete(NMPlatform *self, const NMPObject *obj) +{ + int ifindex; + + _CHECK_SELF(self, klass, FALSE); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_ROUTING_RULE: + _LOGD("%s: delete %s", + NMP_OBJECT_GET_CLASS(obj)->obj_type_name, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + break; + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj)->ifindex; + _LOG3D("%s: delete %s", + NMP_OBJECT_GET_CLASS(obj)->obj_type_name, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + break; + default: + g_return_val_if_reached(FALSE); + } + + return klass->object_delete(self, obj); +} + +/*****************************************************************************/ + +int +nm_platform_ip_route_get(NMPlatform * self, + int addr_family, + gconstpointer address /* in_addr_t or struct in6_addr */, + int oif_ifindex, + NMPObject ** out_route) +{ + nm_auto_nmpobj NMPObject *route = NULL; + int result; + char buf[NM_UTILS_INET_ADDRSTRLEN]; + char buf_oif[64]; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(address, -NME_BUG); + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), -NME_BUG); + + _LOGT("route: get IPv%c route for: %s%s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + oif_ifindex > 0 ? nm_sprintf_buf(buf_oif, " oif %d", oif_ifindex) : ""); + + if (!klass->ip_route_get) + result = -NME_PL_OPNOTSUPP; + else { + result = klass->ip_route_get(self, addr_family, address, oif_ifindex, &route); + } + + if (result < 0) { + nm_assert(!route); + _LOGW("route: get IPv%c route for: %s failed with %s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + nm_strerror(result)); + } else { + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(route), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(route)); + nm_assert(route->parent._ref_count == 1); + _LOGD("route: get IPv%c route for: %s succeeded: %s", + nm_utils_addr_family_to_char(addr_family), + inet_ntop(addr_family, address, buf, sizeof(buf)), + nmp_object_to_string(route, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + NM_SET_OUT(out_route, g_steal_pointer(&route)); + } + return result; +} + +/*****************************************************************************/ + +#define IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS ((int) 1500) +#define IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S \ + ((int) (((IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS + 999) * 3) / 1000)) + +static gint64 +_ip4_dev_route_blacklist_timeout_ms_get(gint64 timeout_msec) +{ + return timeout_msec >> 1; +} + +static gint64 +_ip4_dev_route_blacklist_timeout_ms_marked(gint64 timeout_msec) +{ + return !!(timeout_msec & ((gint64) 1)); +} + +static gboolean +_ip4_dev_route_blacklist_check_cb(gpointer user_data) +{ + NMPlatform * self = user_data; + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + GHashTableIter iter; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + priv->ip4_dev_route_blacklist_check_id = 0; + +again: + if (!priv->ip4_dev_route_blacklist_hash) + goto out; + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (!_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms)) + continue; + + /* unmark because we checked it. */ + *p_timeout_ms = *p_timeout_ms & ~((gint64) 1); + + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) + continue; + + if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, p_obj)) + continue; + + _LOGT("ip4-dev-route: delete %s", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + nm_platform_object_delete(self, p_obj); + goto again; + } + +out: + return G_SOURCE_REMOVE; +} + +static void +_ip4_dev_route_blacklist_check_schedule(NMPlatform *self) +{ + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + if (!priv->ip4_dev_route_blacklist_check_id) { + priv->ip4_dev_route_blacklist_check_id = + g_idle_add_full(G_PRIORITY_HIGH, _ip4_dev_route_blacklist_check_cb, self, NULL); + } +} + +static void +_ip4_dev_route_blacklist_notify_route(NMPlatform *self, const NMPObject *obj) +{ + NMPlatformPrivate *priv; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE); + + priv = NM_PLATFORM_GET_PRIVATE(self); + + nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id); + + if (!g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash, + obj, + (gpointer *) &p_obj, + (gpointer *) &p_timeout_ms)) + return; + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) { + /* already expired. Wait for gc. */ + return; + } + + if (_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms)) { + nm_assert(priv->ip4_dev_route_blacklist_check_id); + return; + } + + /* We cannot delete it right away because we are in the process of receiving netlink messages. + * It may be possible to do so, but complicated and error prone. + * + * Instead, we mark the entry and schedule an idle action (with high priority). */ + *p_timeout_ms = (*p_timeout_ms) | ((gint64) 1); + _ip4_dev_route_blacklist_check_schedule(self); +} + +static gboolean +_ip4_dev_route_blacklist_gc_timeout_handle(gpointer user_data) +{ + NMPlatform * self = user_data; + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + GHashTableIter iter; + const NMPObject * p_obj; + gint64 * p_timeout_ms; + gint64 now_ms; + + nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id); + + now_ms = nm_utils_get_monotonic_timestamp_msec(); + + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) { + _LOGT("ip4-dev-route: cleanup %s", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + g_hash_table_iter_remove(&iter); + } + } + + _ip4_dev_route_blacklist_schedule(self); + return G_SOURCE_CONTINUE; +} + +static void +_ip4_dev_route_blacklist_schedule(NMPlatform *self) +{ + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + if (!priv->ip4_dev_route_blacklist_hash + || g_hash_table_size(priv->ip4_dev_route_blacklist_hash) == 0) { + nm_clear_pointer(&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref); + nm_clear_g_source(&priv->ip4_dev_route_blacklist_gc_timeout_id); + } else { + if (!priv->ip4_dev_route_blacklist_gc_timeout_id) { + /* this timeout is only to garbage collect the expired entries from priv->ip4_dev_route_blacklist_hash. + * It can run infrequently, and it doesn't hurt if expired entries linger around a bit + * longer then necessary. */ + priv->ip4_dev_route_blacklist_gc_timeout_id = + g_timeout_add_seconds(IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S, + _ip4_dev_route_blacklist_gc_timeout_handle, + self); + } + } +} + +/** + * nm_platform_ip4_dev_route_blacklist_set: + * @self: + * @ifindex: + * @ip4_dev_route_blacklist: + * + * When adding an IP address, kernel automatically adds a device route. + * This can be suppressed via the IFA_F_NOPREFIXROUTE address flag. For proper + * IPv6 support, we require kernel support for IFA_F_NOPREFIXROUTE and always + * add the device route manually. + * + * For IPv4, this flag is rather new and we don't rely on it yet. We want to use + * it (but currently still don't). So, for IPv4, kernel possibly adds a device + * route, however it has a wrong metric of zero. We add our own device route (with + * proper metric), but need to delete the route that kernel adds. + * + * The problem is, that kernel does not immediately add the route, when adding + * the address. It only shows up some time later. So, we register here a list + * of blacklisted routes, and when they show up within a time out, we assume it's + * the kernel generated one, and we delete it. + * + * Eventually, we want to get rid of this and use IFA_F_NOPREFIXROUTE for IPv4 + * routes as well. + */ +void +nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self, + int ifindex, + GPtrArray * ip4_dev_route_blacklist) +{ + NMPlatformPrivate *priv; + GHashTableIter iter; + const NMPObject * p_obj; + guint i; + gint64 timeout_msec; + gint64 timeout_msec_val; + gint64 * p_timeout_ms; + gboolean needs_check = FALSE; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + /* TODO: the blacklist should be maintained by NML3Cfg. */ + + priv = NM_PLATFORM_GET_PRIVATE(self); + + /* first, expire all for current ifindex... */ + if (priv->ip4_dev_route_blacklist_hash) { + g_hash_table_iter_init(&iter, priv->ip4_dev_route_blacklist_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) { + if (NMP_OBJECT_CAST_IP4_ROUTE(p_obj)->ifindex == ifindex) { + /* we could g_hash_table_iter_remove(&iter) the current entry. + * Instead, just expire it and let _ip4_dev_route_blacklist_gc_timeout_handle() + * handle it. + * + * The assumption is, that ip4_dev_route_blacklist contains the very same entry + * again, with a new timeout. So, we can un-expire it below. */ + *p_timeout_ms = 0; + } + } + } + + if (ip4_dev_route_blacklist && ip4_dev_route_blacklist->len > 0) { + if (!priv->ip4_dev_route_blacklist_hash) { + priv->ip4_dev_route_blacklist_hash = + g_hash_table_new_full((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + nm_g_slice_free_fcn_gint64); + } + + timeout_msec = nm_utils_get_monotonic_timestamp_msec() + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS; + timeout_msec_val = (timeout_msec << 1) | ((gint64) 1); + for (i = 0; i < ip4_dev_route_blacklist->len; i++) { + const NMPObject *o; + + needs_check = TRUE; + o = ip4_dev_route_blacklist->pdata[i]; + if (g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash, + o, + (gpointer *) &p_obj, + (gpointer *) &p_timeout_ms)) { + if (nmp_object_equal(p_obj, o)) { + /* un-expire and reuse the entry. */ + _LOGT("ip4-dev-route: register %s (update)", + nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + *p_timeout_ms = timeout_msec_val; + continue; + } + } + + _LOGT("ip4-dev-route: register %s", + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + p_timeout_ms = g_slice_new(gint64); + *p_timeout_ms = timeout_msec_val; + g_hash_table_replace(priv->ip4_dev_route_blacklist_hash, + (gpointer) nmp_object_ref(o), + p_timeout_ms); + } + } + + _ip4_dev_route_blacklist_schedule(self); + + if (needs_check) + _ip4_dev_route_blacklist_check_schedule(self); +} + +/*****************************************************************************/ + +int +nm_platform_routing_rule_add(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule) +{ + _CHECK_SELF(self, klass, -NME_BUG); + + g_return_val_if_fail(routing_rule, -NME_BUG); + + _LOGD("routing-rule: adding or updating: %s", + nm_platform_routing_rule_to_string(routing_rule, NULL, 0)); + return klass->routing_rule_add(self, flags, routing_rule); +} + +/*****************************************************************************/ + +int +nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc) +{ + int ifindex = qdisc->ifindex; + _CHECK_SELF(self, klass, -NME_BUG); + + /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind + * is undefined. */ + + _LOG3D("adding or updating a qdisc: %s", nm_platform_qdisc_to_string(qdisc, NULL, 0)); + return klass->qdisc_add(self, flags, qdisc); +} + +/** + * nm_platform_qdisc_sync: + * @self: the #NMPlatform instance + * @ifindex: the ifindex where to configure the qdiscs. + * @known_qdiscs: the list of qdiscs (#NMPObject). + * + * The function promises not to take any reference to the qdisc + * instances from @known_qdiscs, nor to keep them around after + * the function returns. This is important, because it allows the + * caller to pass NMPlatformQdisc instances which "kind" string + * have a limited lifetime. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs) +{ + gs_unref_ptrarray GPtrArray *plat_qdiscs = NULL; + NMPLookup lookup; + guint i; + gboolean success = TRUE; + gs_unref_hashtable GHashTable *known_qdiscs_idx = NULL; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + known_qdiscs_idx = + g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal); + if (known_qdiscs) { + for (i = 0; i < known_qdiscs->len; i++) { + const NMPObject *q = g_ptr_array_index(known_qdiscs, i); + + if (!g_hash_table_insert(known_qdiscs_idx, (gpointer) q, (gpointer) q)) { + _LOGW("duplicate qdisc %s", nm_platform_qdisc_to_string(&q->qdisc, NULL, 0)); + return FALSE; + } + } + } + + plat_qdiscs = + nm_platform_lookup_clone(self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex), + NULL, + NULL); + if (plat_qdiscs) { + for (i = 0; i < plat_qdiscs->len; i++) { + const NMPObject *p = g_ptr_array_index(plat_qdiscs, i); + const NMPObject *k; + + /* look up known qdisc with same parent */ + k = g_hash_table_lookup(known_qdiscs_idx, p); + + if (k) { + const NMPlatformQdisc *qdisc_k = NMP_OBJECT_CAST_QDISC(k); + const NMPlatformQdisc *qdisc_p = NMP_OBJECT_CAST_QDISC(p); + + /* check other fields */ + if (nm_platform_qdisc_cmp_full(qdisc_k, qdisc_p, FALSE) != 0 + || (qdisc_k->handle != qdisc_p->handle && qdisc_k != 0)) { + k = NULL; + } + } + + if (k) { + g_hash_table_remove(known_qdiscs_idx, k); + } else { + /* can't delete qdisc with zero handle */ + if (TC_H_MAJ(p->qdisc.handle) != 0) { + success &= nm_platform_object_delete(self, p); + } + } + } + } + + if (known_qdiscs) { + for (i = 0; i < known_qdiscs->len; i++) { + const NMPObject *q = g_ptr_array_index(known_qdiscs, i); + + if (g_hash_table_contains(known_qdiscs_idx, q)) { + success &= + (nm_platform_qdisc_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_QDISC(q)) >= 0); + } + } + } + + return success; +} + +/*****************************************************************************/ + +int +nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter) +{ + int ifindex = tfilter->ifindex; + _CHECK_SELF(self, klass, -NME_BUG); + + /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind + * and tfilter.action.kind is undefined. */ + + _LOG3D("adding or updating a tfilter: %s", nm_platform_tfilter_to_string(tfilter, NULL, 0)); + return klass->tfilter_add(self, flags, tfilter); +} + +/** + * nm_platform_qdisc_sync: + * @self: the #NMPlatform instance + * @ifindex: the ifindex where to configure the qdiscs. + * @known_tfilters: the list of tfilters (#NMPObject). + * + * The function promises not to take any reference to the tfilter + * instances from @known_tfilters, nor to keep them around after + * the function returns. This is important, because it allows the + * caller to pass NMPlatformTfilter instances which "kind" string + * have a limited lifetime. + * + * Returns: %TRUE on success. + */ +gboolean +nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters) +{ + gs_unref_ptrarray GPtrArray *plat_tfilters = NULL; + NMPLookup lookup; + guint i; + gboolean success = TRUE; + gs_unref_hashtable GHashTable *known_tfilters_idx = NULL; + + nm_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex > 0); + + known_tfilters_idx = + g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal); + + if (known_tfilters) { + for (i = 0; i < known_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(known_tfilters, i); + + g_hash_table_insert(known_tfilters_idx, (gpointer) q, (gpointer) q); + } + } + + plat_tfilters = + nm_platform_lookup_clone(self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_TFILTER, ifindex), + NULL, + NULL); + + if (plat_tfilters) { + for (i = 0; i < plat_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(plat_tfilters, i); + + if (!g_hash_table_lookup(known_tfilters_idx, q)) + success &= nm_platform_object_delete(self, q); + } + } + + if (known_tfilters) { + for (i = 0; i < known_tfilters->len; i++) { + const NMPObject *q = g_ptr_array_index(known_tfilters, i); + + success &= + (nm_platform_tfilter_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_TFILTER(q)) >= 0); + } + } + + return success; +} + +/*****************************************************************************/ + +const char * +nm_platform_vlan_qos_mapping_to_string(const char * name, + const NMVlanQosMapping *map, + gsize n_map, + char * buf, + gsize len) +{ + gsize i; + char *b; + + nm_utils_to_string_buffer_init(&buf, &len); + + if (!n_map) { + nm_utils_strbuf_append_str(&buf, &len, ""); + return buf; + } + + if (!map) + g_return_val_if_reached(""); + + b = buf; + + if (name) { + nm_utils_strbuf_append_str(&b, &len, name); + nm_utils_strbuf_append_str(&b, &len, " {"); + } else + nm_utils_strbuf_append_c(&b, &len, '{'); + + for (i = 0; i < n_map; i++) + nm_utils_strbuf_append(&b, &len, " %u:%u", map[i].from, map[i].to); + nm_utils_strbuf_append_str(&b, &len, " }"); + return buf; +} + +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; +} + +/** + * nm_platform_link_to_string: + * @route: pointer to NMPlatformLink 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 link struct into a string representation. + * + * Returns: a string representation of the link. + */ +const char * +nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len) +{ + char master[20]; + char parent[20]; + char str_flags[1 + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 1]; + char str_highlighted_flags[50]; + char * s; + gsize l; + char str_addrmode[30]; + char str_address[_NM_UTILS_HWADDR_LEN_MAX * 3]; + char str_broadcast[_NM_UTILS_HWADDR_LEN_MAX * 3]; + char str_inet6_token[NM_UTILS_INET_ADDRSTRLEN]; + const char *str_link_type; + + if (!nm_utils_to_string_buffer_init_null(link, &buf, &len)) + return buf; + + s = str_highlighted_flags; + l = sizeof(str_highlighted_flags); + if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_NOARP)) + nm_utils_strbuf_append_str(&s, &l, "NOARP,"); + if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_UP)) + nm_utils_strbuf_append_str(&s, &l, "UP"); + else + nm_utils_strbuf_append_str(&s, &l, "DOWN"); + if (link->connected) + nm_utils_strbuf_append_str(&s, &l, ",LOWER_UP"); + nm_assert(s > str_highlighted_flags && l > 0); + + if (link->n_ifi_flags) { + str_flags[0] = ';'; + nm_platform_link_flags2str(link->n_ifi_flags, &str_flags[1], sizeof(str_flags) - 1); + } else + str_flags[0] = '\0'; + + if (link->master) + g_snprintf(master, sizeof(master), " master %d", link->master); + else + master[0] = 0; + + if (link->parent > 0) + g_snprintf(parent, sizeof(parent), "@%d", link->parent); + else if (link->parent == NM_PLATFORM_LINK_OTHER_NETNS) + g_strlcpy(parent, "@other-netns", sizeof(parent)); + else + parent[0] = 0; + + _nmp_link_address_to_string(&link->l_address, str_address); + _nmp_link_address_to_string(&link->l_broadcast, str_broadcast); + + str_link_type = nm_link_type_to_string(link->type); + + g_snprintf( + buf, + len, + "%d: " /* ifindex */ + "%s" /* name */ + "%s" /* parent */ + " <%s%s>" /* flags */ + " mtu %d" + "%s" /* master */ + " arp %u" /* arptype */ + " %s" /* link->type */ + "%s%s" /* kind */ + "%s" /* is-in-udev */ + "%s%s" /* addr-gen-mode */ + "%s%s" /* l_address */ + "%s%s" /* l_broadcast */ + "%s%s" /* inet6_token */ + "%s%s" /* driver */ + " rx:%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT " tx:%" G_GUINT64_FORMAT + ",%" G_GUINT64_FORMAT, + link->ifindex, + link->name, + parent, + str_highlighted_flags, + str_flags, + link->mtu, + master, + link->arptype, + str_link_type ?: "???", + link->kind ? (g_strcmp0(str_link_type, link->kind) ? "/" : "*") : "?", + link->kind && g_strcmp0(str_link_type, link->kind) ? link->kind : "", + link->initialized ? " init" : " not-init", + link->inet6_addr_gen_mode_inv ? " addrgenmode " : "", + link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str( + _nm_platform_uint8_inv(link->inet6_addr_gen_mode_inv), + str_addrmode, + sizeof(str_addrmode)) + : "", + str_address[0] ? " addr " : "", + str_address[0] ? str_address : "", + str_broadcast[0] ? " brd " : "", + str_broadcast[0] ? str_broadcast : "", + link->inet6_token.id ? " inet6token " : "", + link->inet6_token.id + ? nm_utils_inet6_interface_identifier_to_token(link->inet6_token, str_inet6_token) + : "", + link->driver ? " driver " : "", + link->driver ?: "", + link->rx_packets, + link->rx_bytes, + link->tx_packets, + link->tx_bytes); + return buf; +} + +const NMPlatformLnkBridge nm_platform_lnk_bridge_default = { + .forward_delay = NM_BRIDGE_FORWARD_DELAY_DEF_SYS, + .hello_time = NM_BRIDGE_HELLO_TIME_DEF_SYS, + .max_age = NM_BRIDGE_MAX_AGE_DEF_SYS, + .ageing_time = NM_BRIDGE_AGEING_TIME_DEF_SYS, + .stp_state = FALSE, + .priority = NM_BRIDGE_PRIORITY_DEF, + .vlan_protocol = 0x8100, + .vlan_stats_enabled = NM_BRIDGE_VLAN_STATS_ENABLED_DEF, + .group_fwd_mask = 0, + .group_addr = NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN), + .mcast_snooping = NM_BRIDGE_MULTICAST_SNOOPING_DEF, + .mcast_router = 1, + .mcast_query_use_ifaddr = NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF, + .mcast_querier = NM_BRIDGE_MULTICAST_QUERIER_DEF, + .mcast_hash_max = NM_BRIDGE_MULTICAST_HASH_MAX_DEF, + .mcast_last_member_count = NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF, + .mcast_startup_query_count = NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF, + .mcast_last_member_interval = NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF, + .mcast_membership_interval = NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF, + .mcast_querier_interval = NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF, + .mcast_query_interval = NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF, + .mcast_query_response_interval = NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF, + .mcast_startup_query_interval = NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF, +}; + +const char * +nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "forward_delay %u" + " hello_time %u" + " max_age %u" + " ageing_time %u" + " stp_state %d" + " priority %u" + " vlan_protocol %u" + " vlan_stats_enabled %d" + " group_fwd_mask %#x" + " group_address " NM_ETHER_ADDR_FORMAT_STR " mcast_snooping %d" + " mcast_router %u" + " mcast_query_use_ifaddr %d" + " mcast_querier %d" + " mcast_hash_max %u" + " mcast_last_member_count %u" + " mcast_startup_query_count %u" + " mcast_last_member_interval %" G_GUINT64_FORMAT + " mcast_membership_interval %" G_GUINT64_FORMAT + " mcast_querier_interval %" G_GUINT64_FORMAT + " mcast_query_interval %" G_GUINT64_FORMAT + " mcast_query_response_interval %" G_GUINT64_FORMAT + " mcast_startup_query_interval %" G_GUINT64_FORMAT "", + lnk->forward_delay, + lnk->hello_time, + lnk->max_age, + lnk->ageing_time, + (int) lnk->stp_state, + lnk->priority, + lnk->vlan_protocol, + (int) lnk->vlan_stats_enabled, + lnk->group_fwd_mask, + NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr), + (int) lnk->mcast_snooping, + lnk->mcast_router, + (int) lnk->mcast_query_use_ifaddr, + (int) lnk->mcast_querier, + lnk->mcast_hash_max, + lnk->mcast_last_member_count, + lnk->mcast_startup_query_count, + lnk->mcast_last_member_interval, + lnk->mcast_membership_interval, + lnk->mcast_querier_interval, + lnk->mcast_query_interval, + lnk->mcast_query_response_interval, + lnk->mcast_startup_query_interval); + return buf; +} + +const char * +nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_parent_ifindex[30]; + char str_input_flags[30]; + char str_output_flags[30]; + char str_input_key[30]; + char str_input_key1[NM_UTILS_INET_ADDRSTRLEN]; + char str_output_key[30]; + char str_output_key1[NM_UTILS_INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "gre%s" /* is_tap */ + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "%s" /* iflags */ + "%s" /* oflags */ + "%s" /* ikey */ + "%s" /* okey */ + "", + lnk->is_tap ? "tap" : "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc", + lnk->input_flags ? nm_sprintf_buf(str_input_flags, " iflags 0x%x", lnk->input_flags) : "", + lnk->output_flags ? nm_sprintf_buf(str_output_flags, " oflags 0x%x", lnk->output_flags) + : "", + NM_FLAGS_HAS(lnk->input_flags, GRE_KEY) || lnk->input_key + ? nm_sprintf_buf(str_input_key, + " ikey %s", + _nm_utils_inet4_ntop(lnk->input_key, str_input_key1)) + : "", + NM_FLAGS_HAS(lnk->output_flags, GRE_KEY) || lnk->output_key + ? nm_sprintf_buf(str_output_key, + " okey %s", + _nm_utils_inet4_ntop(lnk->output_key, str_output_key1)) + : ""); + return buf; +} + +const char * +nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len) +{ + char str_p_key[64]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "infiniband" + "%s" /* p_key */ + "%s%s" /* mode */ + "", + lnk->p_key ? nm_sprintf_buf(str_p_key, " pkey %d", lnk->p_key) : "", + lnk->mode ? " mode " : "", + lnk->mode ?: ""); + return buf; +} + +const char * +nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tclass[30]; + char str_flow[30]; + char str_encap[30]; + char str_proto[30]; + char str_parent_ifindex[30]; + char *str_type; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->is_gre) + str_type = lnk->is_tap ? "ip6gretap" : "ip6gre"; + else + str_type = "ip6tnl"; + + g_snprintf( + buf, + len, + "%s" /* type */ + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tclass */ + "%s" /* encap limit */ + "%s" /* flow label */ + "%s" /* proto */ + " flags 0x%x" + "", + str_type, + nm_sprintf_buf(str_remote, " remote %s", _nm_utils_inet6_ntop(&lnk->remote, str_remote1)), + nm_sprintf_buf(str_local, " local %s", _nm_utils_inet6_ntop(&lnk->local, str_local1)), + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tclass == 1 ? " tclass inherit" + : nm_sprintf_buf(str_tclass, " tclass 0x%x", lnk->tclass), + nm_sprintf_buf(str_encap, " encap-limit %u", lnk->encap_limit), + nm_sprintf_buf(str_flow, " flow-label 0x05%x", lnk->flow_label), + nm_sprintf_buf(str_proto, " proto %u", lnk->proto), + (guint) lnk->flags); + return buf; +} + +const char * +nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_parent_ifindex[30]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "ipip" + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc"); + return buf; +} + +const char * +nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "macsec " + "sci %016llx " + "protect %s " + "cipher %016llx " + "icvlen %u " + "encodingsa %u " + "validate %u " + "encrypt %s " + "send_sci %s " + "end_station %s " + "scb %s " + "replay %s", + (unsigned long long) lnk->sci, + lnk->protect ? "on" : "off", + (unsigned long long) lnk->cipher_suite, + lnk->icv_length, + lnk->encoding_sa, + lnk->validation, + lnk->encrypt ? "on" : "off", + lnk->include_sci ? "on" : "off", + lnk->es ? "on" : "off", + lnk->scb ? "on" : "off", + lnk->replay_protect ? "on" : "off"); + return buf; +} + +const char * +nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len) +{ + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "%s mode %u %s", + lnk->tap ? "macvtap" : "macvlan", + lnk->mode, + lnk->no_promisc ? "not-promisc" : "promisc"); + return buf; +} + +const char * +nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len) +{ + char str_local[30]; + char str_local1[NM_UTILS_INET_ADDRSTRLEN]; + char str_remote[30]; + char str_remote1[NM_UTILS_INET_ADDRSTRLEN]; + char str_ttl[30]; + char str_tos[30]; + char str_flags[30]; + char str_proto[30]; + char str_parent_ifindex[30]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + g_snprintf( + buf, + len, + "sit" + "%s" /* remote */ + "%s" /* local */ + "%s" /* parent_ifindex */ + "%s" /* ttl */ + "%s" /* tos */ + "%s" /* path_mtu_discovery */ + "%s" /* flags */ + "%s" /* proto */ + "", + lnk->remote ? nm_sprintf_buf(str_remote, + " remote %s", + _nm_utils_inet4_ntop(lnk->remote, str_remote1)) + : "", + lnk->local + ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1)) + : "", + lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex) + : "", + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : " ttl inherit", + lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos 0x%x", lnk->tos)) + : "", + lnk->path_mtu_discovery ? "" : " nopmtudisc", + lnk->flags ? nm_sprintf_buf(str_flags, " flags 0x%x", lnk->flags) : "", + lnk->proto ? nm_sprintf_buf(str_proto, " proto 0x%x", lnk->proto) : ""); + return buf; +} + +const char * +nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len) +{ + char str_owner[50]; + char str_group[50]; + char str_type[50]; + const char *type; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->type == IFF_TUN) + type = "tun"; + else if (lnk->type == IFF_TAP) + type = "tap"; + else + type = nm_sprintf_buf(str_type, "tun type %u", (guint) lnk->type); + + g_snprintf(buf, + len, + "%s" /* type */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + "%s" /* persist */ + "%s" /* owner */ + "%s" /* group */ + "", + type, + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + lnk->persist ? " persist" : "", + lnk->owner_valid ? nm_sprintf_buf(str_owner, " owner %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_buf(str_group, " group %u", (guint) lnk->group) : ""); + return buf; +} + +const char * +nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *lnk, char *buf, gsize len) +{ + char *b; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + b = buf; + + nm_utils_strbuf_append(&b, &len, "vlan %u", lnk->id); + if (lnk->flags) + nm_utils_strbuf_append(&b, &len, " flags 0x%x", lnk->flags); + return buf; +} + +const char * +nm_platform_lnk_vrf_to_string(const NMPlatformLnkVrf *lnk, char *buf, gsize len) +{ + char *b; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + b = buf; + + nm_utils_strbuf_append(&b, &len, "table %u", lnk->table); + return buf; +} + +const char * +nm_platform_lnk_vxlan_to_string(const NMPlatformLnkVxlan *lnk, char *buf, gsize len) +{ + char str_group[100]; + char str_group6[100]; + char str_local[100]; + char str_local6[100]; + char str_dev[25]; + char str_limit[25]; + char str_src_port[35]; + char str_dst_port[25]; + char str_tos[25]; + char str_ttl[25]; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (lnk->group == 0) + str_group[0] = '\0'; + else { + g_snprintf(str_group, + sizeof(str_group), + " %s %s", + IN_MULTICAST(ntohl(lnk->group)) ? "group" : "remote", + _nm_utils_inet4_ntop(lnk->group, sbuf)); + } + if (IN6_IS_ADDR_UNSPECIFIED(&lnk->group6)) + str_group6[0] = '\0'; + else { + g_snprintf(str_group6, + sizeof(str_group6), + " %s%s %s", + IN6_IS_ADDR_MULTICAST(&lnk->group6) ? "group" : "remote", + str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */ + _nm_utils_inet6_ntop(&lnk->group6, sbuf)); + } + + if (lnk->local == 0) + str_local[0] = '\0'; + else { + g_snprintf(str_local, + sizeof(str_local), + " local %s", + _nm_utils_inet4_ntop(lnk->local, sbuf)); + } + if (IN6_IS_ADDR_UNSPECIFIED(&lnk->local6)) + str_local6[0] = '\0'; + else { + g_snprintf(str_local6, + sizeof(str_local6), + " local%s %s", + str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */ + _nm_utils_inet6_ntop(&lnk->local6, sbuf)); + } + + g_snprintf( + buf, + len, + "vxlan" + " id %u" /* id */ + "%s%s" /* group/group6 */ + "%s%s" /* local/local6 */ + "%s" /* dev */ + "%s" /* src_port_min/src_port_max */ + "%s" /* dst_port */ + "%s" /* learning */ + "%s" /* proxy */ + "%s" /* rsc */ + "%s" /* l2miss */ + "%s" /* l3miss */ + "%s" /* tos */ + "%s" /* ttl */ + " ageing %u" /* ageing */ + "%s" /* limit */ + "", + (guint) lnk->id, + str_group, + str_group6, + str_local, + str_local6, + lnk->parent_ifindex ? nm_sprintf_buf(str_dev, " dev %d", lnk->parent_ifindex) : "", + lnk->src_port_min || lnk->src_port_max + ? nm_sprintf_buf(str_src_port, " srcport %u %u", lnk->src_port_min, lnk->src_port_max) + : "", + lnk->dst_port ? nm_sprintf_buf(str_dst_port, " dstport %u", lnk->dst_port) : "", + !lnk->learning ? " nolearning" : "", + lnk->proxy ? " proxy" : "", + lnk->rsc ? " rsc" : "", + lnk->l2miss ? " l2miss" : "", + lnk->l3miss ? " l3miss" : "", + lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos %#x", lnk->tos), + lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : "", + lnk->ageing, + lnk->limit ? nm_sprintf_buf(str_limit, " maxaddr %u", lnk->limit) : ""); + return buf; +} + +const char * +nm_platform_wireguard_peer_to_string(const NMPWireGuardPeer *peer, char *buf, gsize len) +{ + char * buf0 = buf; + gs_free char *public_key_b64 = NULL; + char s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100]; + char s_endpoint[20 + sizeof(s_sockaddr)]; + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + char s_keepalive[100]; + guint i; + + nm_utils_to_string_buffer_init(&buf, &len); + + public_key_b64 = g_base64_encode(peer->public_key, sizeof(peer->public_key)); + + if (peer->endpoint.sa.sa_family != AF_UNSPEC) { + nm_sprintf_buf( + s_endpoint, + " endpoint %s", + nm_sock_addr_union_to_string(&peer->endpoint, s_sockaddr, sizeof(s_sockaddr))); + } else + s_endpoint[0] = '\0'; + + nm_utils_strbuf_append( + &buf, + &len, + "public-key %s" + "%s" /* preshared-key */ + "%s" /* endpoint */ + " rx %" G_GUINT64_FORMAT " tx %" G_GUINT64_FORMAT "%s" /* persistent-keepalive */ + "%s", /* allowed-ips */ + public_key_b64, + nm_utils_memeqzero_secret(peer->preshared_key, sizeof(peer->preshared_key)) + ? "" + : " preshared-key (hidden)", + s_endpoint, + peer->rx_bytes, + peer->tx_bytes, + peer->persistent_keepalive_interval > 0 + ? nm_sprintf_buf(s_keepalive, + " keepalive %u", + (guint) peer->persistent_keepalive_interval) + : "", + peer->allowed_ips_len > 0 ? " allowed-ips" : ""); + + for (i = 0; i < peer->allowed_ips_len; i++) { + const NMPWireGuardAllowedIP *allowed_ip = &peer->allowed_ips[i]; + + nm_utils_strbuf_append(&buf, + &len, + " %s/%u", + nm_utils_inet_ntop(allowed_ip->family, &allowed_ip->addr, s_addr), + allowed_ip->mask); + } + + return buf0; +} + +const char * +nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len) +{ + gs_free char *public_b64 = NULL; + + if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len)) + return buf; + + if (!nm_utils_memeqzero(lnk->public_key, sizeof(lnk->public_key))) + public_b64 = g_base64_encode(lnk->public_key, sizeof(lnk->public_key)); + + g_snprintf(buf, + len, + "wireguard" + "%s%s" /* public-key */ + "%s" /* private-key */ + " listen-port %u" + " fwmark 0x%x", + public_b64 ? " public-key " : "", + public_b64 ?: "", + nm_utils_memeqzero_secret(lnk->private_key, sizeof(lnk->private_key)) + ? "" + : " private-key (hidden)", + lnk->listen_port, + lnk->fwmark); + + return buf; +} + +/** + * 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[TO_STRING_DEV_BUF_SIZE]; + char str_label[32]; + char str_lft[30], str_pref[30], str_time[50], 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); + } + + _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev)); + + 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" /* external */ + "%s" /* ip4acd_not_ready */ + "", + 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_utils_inet4_ntop(broadcast_address, str_broadcast) + : "", + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + str_dev, + _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->external ? " ext" : "", + address->ip4acd_not_ready ? " ip4acd-not-ready" : ""); + g_free(str_peer); + return buf; +} + +NM_UTILS_FLAGS2STR_DEFINE(nm_platform_link_flags2str, + unsigned, + NM_UTILS_FLAGS2STR(IFF_LOOPBACK, "loopback"), + NM_UTILS_FLAGS2STR(IFF_BROADCAST, "broadcast"), + NM_UTILS_FLAGS2STR(IFF_POINTOPOINT, "pointopoint"), + NM_UTILS_FLAGS2STR(IFF_MULTICAST, "multicast"), + NM_UTILS_FLAGS2STR(IFF_NOARP, "noarp"), + NM_UTILS_FLAGS2STR(IFF_ALLMULTI, "allmulti"), + NM_UTILS_FLAGS2STR(IFF_PROMISC, "promisc"), + NM_UTILS_FLAGS2STR(IFF_MASTER, "master"), + NM_UTILS_FLAGS2STR(IFF_SLAVE, "slave"), + NM_UTILS_FLAGS2STR(IFF_DEBUG, "debug"), + NM_UTILS_FLAGS2STR(IFF_DYNAMIC, "dynamic"), + NM_UTILS_FLAGS2STR(IFF_AUTOMEDIA, "automedia"), + NM_UTILS_FLAGS2STR(IFF_PORTSEL, "portsel"), + NM_UTILS_FLAGS2STR(IFF_NOTRAILERS, "notrailers"), + NM_UTILS_FLAGS2STR(IFF_UP, "up"), + NM_UTILS_FLAGS2STR(IFF_RUNNING, "running"), + NM_UTILS_FLAGS2STR(IFF_LOWER_UP, "lowerup"), + NM_UTILS_FLAGS2STR(IFF_DORMANT, "dormant"), + NM_UTILS_FLAGS2STR(IFF_ECHO, "echo"), ); + +NM_UTILS_ENUM2STR_DEFINE(nm_platform_link_inet6_addrgenmode2str, + guint8, + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_NONE, "none"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_EUI64, "eui64"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"), + NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_RANDOM, "random"), ); + +NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str, + unsigned, + NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"), + NM_UTILS_FLAGS2STR(IFA_F_NODAD, "nodad"), + NM_UTILS_FLAGS2STR(IFA_F_OPTIMISTIC, "optimistic"), + NM_UTILS_FLAGS2STR(IFA_F_HOMEADDRESS, "homeaddress"), + NM_UTILS_FLAGS2STR(IFA_F_DEPRECATED, "deprecated"), + NM_UTILS_FLAGS2STR(IFA_F_PERMANENT, "permanent"), + NM_UTILS_FLAGS2STR(IFA_F_MANAGETEMPADDR, "mngtmpaddr"), + NM_UTILS_FLAGS2STR(IFA_F_NOPREFIXROUTE, "noprefixroute"), + NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), ); + +NM_UTILS_ENUM2STR_DEFINE(nm_platform_route_scope2str, + int, + NM_UTILS_ENUM2STR(RT_SCOPE_NOWHERE, "nowhere"), + NM_UTILS_ENUM2STR(RT_SCOPE_HOST, "host"), + NM_UTILS_ENUM2STR(RT_SCOPE_LINK, "link"), + NM_UTILS_ENUM2STR(RT_SCOPE_SITE, "site"), + NM_UTILS_ENUM2STR(RT_SCOPE_UNIVERSE, "global"), ); + +/** + * 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], str_pref[30], str_time[50], s_source[50]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + 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); + } + + _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev)); + + 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", + s_address, + address->plen, + str_lft_p, + str_pref_p, + str_time_p, + str_peer ?: "", + str_dev, + _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->external ? " ext" : ""); + g_free(str_peer); + return buf; +} + +static NM_UTILS_FLAGS2STR_DEFINE(_rtm_flags_to_string, + unsigned, + NM_UTILS_FLAGS2STR(RTNH_F_DEAD, "dead"), + NM_UTILS_FLAGS2STR(RTNH_F_PERVASIVE, "pervasive"), + NM_UTILS_FLAGS2STR(RTNH_F_ONLINK, "onlink"), + NM_UTILS_FLAGS2STR(8 /*RTNH_F_OFFLOAD*/, "offload"), + NM_UTILS_FLAGS2STR(16 /*RTNH_F_LINKDOWN*/, "linkdown"), + NM_UTILS_FLAGS2STR(32 /*RTNH_F_UNRESOLVED*/, "unresolved"), + + NM_UTILS_FLAGS2STR(RTM_F_NOTIFY, "notify"), + NM_UTILS_FLAGS2STR(RTM_F_CLONED, "cloned"), + NM_UTILS_FLAGS2STR(RTM_F_EQUALIZE, "equalize"), + NM_UTILS_FLAGS2STR(RTM_F_PREFIX, "prefix"), + NM_UTILS_FLAGS2STR(0x1000 /*RTM_F_LOOKUP_TABLE*/, "lookup-table"), + NM_UTILS_FLAGS2STR(0x2000 /*RTM_F_FIB_MATCH*/, "fib-match"), ); + +#define _RTM_FLAGS_TO_STRING_MAXLEN 200 + +static const char * +_rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags) +{ + const char *buf0 = buf; + + nm_assert(buf_size >= _RTM_FLAGS_TO_STRING_MAXLEN); + + if (!rtm_flags) + return ""; + + nm_utils_strbuf_append_str(&buf, &buf_size, " rtm_flags "); + _rtm_flags_to_string(rtm_flags, buf, buf_size); + nm_assert(strlen(buf) < buf_size); + return buf0; +} + +/** + * nm_platform_ip4_route_to_string: + * @route: pointer to NMPlatformIP4Route route 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 a route struct into a string representation. + * + * Example output: "192.168.1.0/24 via 0.0.0.0 dev em1 metric 0 mss 0" + * + * Returns: a string representation of the route. + */ +const char * +nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len) +{ + char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN]; + char s_pref_src[INET_ADDRSTRLEN]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char str_table[30]; + char str_scope[30], s_source[50]; + char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32]; + char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN]; + char str_type[30]; + char str_metric[30]; + + if (!nm_utils_to_string_buffer_init_null(route, &buf, &len)) + return buf; + + inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network)); + inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway)); + + _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev)); + + g_snprintf( + buf, + len, + "type %s " /* type */ + "%s" /* table */ + "%s/%d" + " via %s" + "%s" + " metric %s" + " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ + "%s" /* rtm_flags */ + "%s%s" /* scope */ + "%s%s" /* pref-src */ + "%s" /* tos */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "%s" /* is_external */ + "", + nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), + str_type), + route->table_any + ? "table ?? " + : (route->table_coerced + ? nm_sprintf_buf(str_table, + "table %u ", + nm_platform_route_table_uncoerce(route->table_coerced, FALSE)) + : ""), + s_network, + route->plen, + s_gateway, + str_dev, + route->metric_any + ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??") + : nm_sprintf_buf(str_metric, "%u", route->metric), + route->mss, + nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)), + _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags), + route->scope_inv ? " scope " : "", + route->scope_inv + ? (nm_platform_route_scope2str(nm_platform_route_scope_inv(route->scope_inv), + str_scope, + sizeof(str_scope))) + : "", + route->pref_src ? " pref-src " : "", + route->pref_src ? inet_ntop(AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "", + route->tos ? nm_sprintf_buf(str_tos, " tos 0x%x", (unsigned) route->tos) : "", + route->window || route->lock_window ? nm_sprintf_buf(str_window, + " window %s%" G_GUINT32_FORMAT, + route->lock_window ? "lock " : "", + route->window) + : "", + route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd, + " cwnd %s%" G_GUINT32_FORMAT, + route->lock_cwnd ? "lock " : "", + route->cwnd) + : "", + route->initcwnd || route->lock_initcwnd + ? nm_sprintf_buf(str_initcwnd, + " initcwnd %s%" G_GUINT32_FORMAT, + route->lock_initcwnd ? "lock " : "", + route->initcwnd) + : "", + route->initrwnd || route->lock_initrwnd + ? nm_sprintf_buf(str_initrwnd, + " initrwnd %s%" G_GUINT32_FORMAT, + route->lock_initrwnd ? "lock " : "", + route->initrwnd) + : "", + route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu, + " mtu %s%" G_GUINT32_FORMAT, + route->lock_mtu ? "lock " : "", + route->mtu) + : "", + route->is_external ? " (E)" : ""); + return buf; +} + +/** + * nm_platform_ip6_route_to_string: + * @route: pointer to NMPlatformIP6Route route 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 a route struct into a string representation. + * + * Example output: "ff02::fb/128 via :: dev em1 metric 0" + * + * Returns: a string representation of the route. + */ +const char * +nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len) +{ + char s_network[INET6_ADDRSTRLEN]; + char s_gateway[INET6_ADDRSTRLEN]; + char s_pref_src[INET6_ADDRSTRLEN]; + char s_src_all[INET6_ADDRSTRLEN + 40]; + char s_src[INET6_ADDRSTRLEN]; + char str_type[30]; + char str_table[30]; + char str_pref[40]; + char str_pref2[30]; + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char s_source[50]; + char str_window[32]; + char str_cwnd[32]; + char str_initcwnd[32]; + char str_initrwnd[32]; + char str_mtu[32]; + char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN]; + char str_metric[30]; + + if (!nm_utils_to_string_buffer_init_null(route, &buf, &len)) + return buf; + + inet_ntop(AF_INET6, &route->network, s_network, sizeof(s_network)); + inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway)); + + if (IN6_IS_ADDR_UNSPECIFIED(&route->pref_src)) + s_pref_src[0] = 0; + else + inet_ntop(AF_INET6, &route->pref_src, s_pref_src, sizeof(s_pref_src)); + + _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev)); + + g_snprintf( + buf, + len, + "type %s " /* type */ + "%s" /* table */ + "%s/%d" + " via %s" + "%s" + " metric %s" + " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */ + "%s" /* source */ + "%s" /* rtm_flags */ + "%s%s" /* pref-src */ + "%s" /* window */ + "%s" /* cwnd */ + "%s" /* initcwnd */ + "%s" /* initrwnd */ + "%s" /* mtu */ + "%s" /* pref */ + "%s" /* is_external */ + "", + nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), + str_type), + route->table_any + ? "table ?? " + : (route->table_coerced + ? nm_sprintf_buf(str_table, + "table %u ", + nm_platform_route_table_uncoerce(route->table_coerced, FALSE)) + : ""), + s_network, + route->plen, + s_gateway, + str_dev, + route->metric_any + ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??") + : nm_sprintf_buf(str_metric, "%u", route->metric), + route->mss, + nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)), + route->src_plen || !IN6_IS_ADDR_UNSPECIFIED(&route->src) + ? nm_sprintf_buf(s_src_all, + " src %s/%u", + _nm_utils_inet6_ntop(&route->src, s_src), + (unsigned) route->src_plen) + : "", + _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags), + s_pref_src[0] ? " pref-src " : "", + s_pref_src[0] ? s_pref_src : "", + route->window || route->lock_window ? nm_sprintf_buf(str_window, + " window %s%" G_GUINT32_FORMAT, + route->lock_window ? "lock " : "", + route->window) + : "", + route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd, + " cwnd %s%" G_GUINT32_FORMAT, + route->lock_cwnd ? "lock " : "", + route->cwnd) + : "", + route->initcwnd || route->lock_initcwnd + ? nm_sprintf_buf(str_initcwnd, + " initcwnd %s%" G_GUINT32_FORMAT, + route->lock_initcwnd ? "lock " : "", + route->initcwnd) + : "", + route->initrwnd || route->lock_initrwnd + ? nm_sprintf_buf(str_initrwnd, + " initrwnd %s%" G_GUINT32_FORMAT, + route->lock_initrwnd ? "lock " : "", + route->initrwnd) + : "", + route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu, + " mtu %s%" G_GUINT32_FORMAT, + route->lock_mtu ? "lock " : "", + route->mtu) + : "", + route->rt_pref ? nm_sprintf_buf( + str_pref, + " pref %s", + nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2))) + : "", + route->is_external ? " (E)" : ""); + + return buf; +} + +static void +_routing_rule_addr_to_string(char ** buf, + gsize * len, + int addr_family, + const NMIPAddr *addr, + guint8 plen, + gboolean is_src) +{ + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + gboolean is_zero; + gsize addr_size; + + nm_assert_addr_family(addr_family); + nm_assert(addr); + + addr_size = nm_utils_addr_family_to_size(addr_family); + + is_zero = nm_utils_memeqzero(addr, addr_size); + + if (plen == 0 && is_zero) { + if (is_src) + nm_utils_strbuf_append_str(buf, len, " from all"); + else + nm_utils_strbuf_append_str(buf, len, ""); + return; + } + + nm_utils_strbuf_append_str(buf, len, is_src ? " from " : " to "); + + nm_utils_strbuf_append_str(buf, len, nm_utils_inet_ntop(addr_family, addr, s_addr)); + + if (plen != (addr_size * 8)) + nm_utils_strbuf_append(buf, len, "/%u", plen); +} + +static void +_routing_rule_port_range_to_string(char ** buf, + gsize * len, + const NMFibRulePortRange *port_range, + const char * name) +{ + if (port_range->start == 0 && port_range->end == 0) + nm_utils_strbuf_append_str(buf, len, ""); + else { + nm_utils_strbuf_append(buf, len, " %s %u", name, port_range->start); + if (port_range->start != port_range->end) + nm_utils_strbuf_append(buf, len, "-%u", port_range->end); + } +} + +const char * +nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len) +{ + const char *buf0; + guint32 rr_flags; + + if (!nm_utils_to_string_buffer_init_null(routing_rule, &buf, &len)) + return buf; + + if (!NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6)) { + /* invalid addr-family. The other fields are undefined. */ + if (routing_rule->addr_family == AF_UNSPEC) + g_snprintf(buf, len, "[routing-rule]"); + else + g_snprintf(buf, len, "[routing-rule family:%u]", routing_rule->addr_family); + return buf; + } + + buf0 = buf; + + rr_flags = routing_rule->flags; + + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_INVERT); + nm_utils_strbuf_append(&buf, + &len, + "[%c] " /* addr-family */ + "%u:" /* priority */ + "%s", /* not/FIB_RULE_INVERT */ + nm_utils_addr_family_to_char(routing_rule->addr_family), + routing_rule->priority, + (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_INVERT) ? " not" : "")); + + _routing_rule_addr_to_string(&buf, + &len, + routing_rule->addr_family, + &routing_rule->src, + routing_rule->src_len, + TRUE); + + _routing_rule_addr_to_string(&buf, + &len, + routing_rule->addr_family, + &routing_rule->dst, + routing_rule->dst_len, + FALSE); + + if (routing_rule->tos) + nm_utils_strbuf_append(&buf, &len, " tos 0x%02x", routing_rule->tos); + + if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) { + nm_utils_strbuf_append(&buf, &len, " fwmark %#x", (unsigned) routing_rule->fwmark); + if (routing_rule->fwmark != 0xFFFFFFFFu) + nm_utils_strbuf_append(&buf, &len, "/%#x", (unsigned) routing_rule->fwmask); + } + + if (routing_rule->iifname[0]) { + nm_utils_strbuf_append(&buf, &len, " iif %s", routing_rule->iifname); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_IIF_DETACHED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_IIF_DETACHED)) + nm_utils_strbuf_append_str(&buf, &len, " [detached]"); + } + + if (routing_rule->oifname[0]) { + nm_utils_strbuf_append(&buf, &len, " oif %s", routing_rule->oifname); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_OIF_DETACHED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_OIF_DETACHED)) + nm_utils_strbuf_append_str(&buf, &len, " [detached]"); + } + + if (routing_rule->l3mdev != 0) { + if (routing_rule->l3mdev == 1) + nm_utils_strbuf_append_str(&buf, &len, " lookup [l3mdev-table]"); + else { + nm_utils_strbuf_append(&buf, + &len, + " lookup [l3mdev-table/%u]", + (unsigned) routing_rule->l3mdev); + } + } + + if (routing_rule->uid_range_has || routing_rule->uid_range.start + || routing_rule->uid_range.end) { + nm_utils_strbuf_append(&buf, + &len, + " uidrange %u-%u%s", + routing_rule->uid_range.start, + routing_rule->uid_range.end, + routing_rule->uid_range_has ? "" : "(?)"); + } + + if (routing_rule->ip_proto != 0) { + /* we don't call getprotobynumber(), just print the numeric value. + * This differs from what ip-rule prints. */ + nm_utils_strbuf_append(&buf, &len, " ipproto %u", routing_rule->ip_proto); + } + + _routing_rule_port_range_to_string(&buf, &len, &routing_rule->sport_range, "sport"); + + _routing_rule_port_range_to_string(&buf, &len, &routing_rule->dport_range, "dport"); + + if (routing_rule->tun_id != 0) { + nm_utils_strbuf_append(&buf, &len, " tun_id %" G_GUINT64_FORMAT, routing_rule->tun_id); + } + + if (routing_rule->table != 0) { + nm_utils_strbuf_append(&buf, &len, " lookup %u", routing_rule->table); + } + + if (routing_rule->suppress_prefixlen_inverse != 0) { + nm_utils_strbuf_append(&buf, + &len, + " suppress_prefixlen %d", + (int) (~routing_rule->suppress_prefixlen_inverse)); + } + + if (routing_rule->suppress_ifgroup_inverse != 0) { + nm_utils_strbuf_append(&buf, + &len, + " suppress_ifgroup %d", + (int) (~routing_rule->suppress_ifgroup_inverse)); + } + + if (routing_rule->flow) { + /* FRA_FLOW is only for IPv4, but we want to print the value for all address-families, + * to see when it is set. In practice, this should not be set except for IPv4. + * + * We don't follow the style how ip-rule prints flow/realms. It's confusing. Just + * print the value hex. */ + nm_utils_strbuf_append(&buf, &len, " realms 0x%08x", routing_rule->flow); + } + + if (routing_rule->action == RTN_NAT) { + G_STATIC_ASSERT_EXPR(RTN_NAT == 10); + + /* NAT is deprecated for many years. We don't support RTA_GATEWAY/FRA_UNUSED2 + * for the gateway, and so do recent kernels ignore that parameter. */ + nm_utils_strbuf_append_str(&buf, &len, " masquerade"); + } else if (routing_rule->action == FR_ACT_GOTO) { + if (routing_rule->goto_target != 0) + nm_utils_strbuf_append(&buf, &len, " goto %u", routing_rule->goto_target); + else + nm_utils_strbuf_append_str(&buf, &len, " goto none"); + rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_UNRESOLVED); + if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_UNRESOLVED)) + nm_utils_strbuf_append_str(&buf, &len, " unresolved"); + } else if (routing_rule->action != FR_ACT_TO_TBL) { + char ss_buf[60]; + + nm_utils_strbuf_append(&buf, + &len, + " %s", + nm_net_aux_rtnl_rtntype_n2a(routing_rule->action) + ?: nm_sprintf_buf(ss_buf, "action-%u", routing_rule->action)); + } + + if (routing_rule->protocol != RTPROT_UNSPEC) + nm_utils_strbuf_append(&buf, &len, " protocol %u", routing_rule->protocol); + + if (routing_rule->goto_target != 0 && routing_rule->action != FR_ACT_GOTO) { + /* a trailing target is set for an unexpected action. Print it. */ + nm_utils_strbuf_append(&buf, &len, " goto-target %u", routing_rule->goto_target); + } + + if (rr_flags != 0) { + /* we have some flags we didn't print about yet. */ + nm_utils_strbuf_append(&buf, &len, " remaining-flags %x", rr_flags); + } + + return buf0; +} + +const char * +nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + const char *buf0; + + if (!nm_utils_to_string_buffer_init_null(qdisc, &buf, &len)) + return buf; + + buf0 = buf; + + nm_utils_strbuf_append(&buf, + &len, + "%s%s family %u handle %x parent %x info %x", + qdisc->kind, + _to_string_dev(NULL, qdisc->ifindex, str_dev, sizeof(str_dev)), + qdisc->addr_family, + qdisc->handle, + qdisc->parent, + qdisc->info); + + if (nm_streq0(qdisc->kind, "fq_codel")) { + if (qdisc->fq_codel.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->fq_codel.limit); + if (qdisc->fq_codel.flows) + nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->fq_codel.flows); + if (qdisc->fq_codel.target) + nm_utils_strbuf_append(&buf, &len, " target %u", qdisc->fq_codel.target); + if (qdisc->fq_codel.interval) + nm_utils_strbuf_append(&buf, &len, " interval %u", qdisc->fq_codel.interval); + if (qdisc->fq_codel.quantum) + nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->fq_codel.quantum); + if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) + nm_utils_strbuf_append(&buf, &len, " ce_threshold %u", qdisc->fq_codel.ce_threshold); + if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET) + nm_utils_strbuf_append(&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit); + if (qdisc->fq_codel.ecn) + nm_utils_strbuf_append(&buf, &len, " ecn"); + } else if (nm_streq0(qdisc->kind, "sfq")) { + if (qdisc->sfq.quantum) + nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->sfq.quantum); + if (qdisc->sfq.perturb_period) + nm_utils_strbuf_append(&buf, &len, " perturb %d", qdisc->sfq.perturb_period); + if (qdisc->sfq.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", (guint) qdisc->sfq.limit); + if (qdisc->sfq.divisor) + nm_utils_strbuf_append(&buf, &len, " divisor %u", qdisc->sfq.divisor); + if (qdisc->sfq.flows) + nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->sfq.flows); + if (qdisc->sfq.depth) + nm_utils_strbuf_append(&buf, &len, " depth %u", qdisc->sfq.depth); + } else if (nm_streq0(qdisc->kind, "tbf")) { + nm_utils_strbuf_append(&buf, &len, " rate %" G_GUINT64_FORMAT, qdisc->tbf.rate); + nm_utils_strbuf_append(&buf, &len, " burst %u", qdisc->tbf.burst); + if (qdisc->tbf.limit) + nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->tbf.limit); + if (qdisc->tbf.latency) + nm_utils_strbuf_append(&buf, &len, " latency %uns", qdisc->tbf.latency); + } + + return buf0; +} + +void +nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h) +{ + nm_hash_update_str0(h, obj->kind); + nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info); + if (nm_streq0(obj->kind, "fq_codel")) { + nm_hash_update_vals(h, + obj->fq_codel.limit, + obj->fq_codel.flows, + obj->fq_codel.target, + obj->fq_codel.interval, + obj->fq_codel.quantum, + obj->fq_codel.ce_threshold, + obj->fq_codel.memory_limit, + NM_HASH_COMBINE_BOOLS(guint8, obj->fq_codel.ecn)); + } else if (nm_streq0(obj->kind, "sfq")) { + nm_hash_update_vals(h, + obj->sfq.quantum, + obj->sfq.perturb_period, + obj->sfq.limit, + obj->sfq.divisor, + obj->sfq.flows, + obj->sfq.depth); + } else if (nm_streq0(obj->kind, "tbf")) { + nm_hash_update_vals(h, obj->tbf.rate, obj->tbf.burst, obj->tbf.limit, obj->tbf.latency); + } +} + +int +nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a, + const NMPlatformQdisc *b, + gboolean compare_handle) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD(a, b, addr_family); + if (compare_handle) + NM_CMP_FIELD(a, b, handle); + NM_CMP_FIELD(a, b, info); + + if (nm_streq0(a->kind, "fq_codel")) { + NM_CMP_FIELD(a, b, fq_codel.limit); + NM_CMP_FIELD(a, b, fq_codel.flows); + NM_CMP_FIELD(a, b, fq_codel.target); + NM_CMP_FIELD(a, b, fq_codel.interval); + NM_CMP_FIELD(a, b, fq_codel.quantum); + NM_CMP_FIELD(a, b, fq_codel.ce_threshold); + NM_CMP_FIELD(a, b, fq_codel.memory_limit); + NM_CMP_FIELD_UNSAFE(a, b, fq_codel.ecn); + } else if (nm_streq0(a->kind, "sfq")) { + NM_CMP_FIELD(a, b, sfq.quantum); + NM_CMP_FIELD(a, b, sfq.perturb_period); + NM_CMP_FIELD(a, b, sfq.limit); + NM_CMP_FIELD(a, b, sfq.flows); + NM_CMP_FIELD(a, b, sfq.divisor); + NM_CMP_FIELD(a, b, sfq.depth); + } else if (nm_streq0(a->kind, "tbf")) { + NM_CMP_FIELD(a, b, tbf.rate); + NM_CMP_FIELD(a, b, tbf.burst); + NM_CMP_FIELD(a, b, tbf.limit); + NM_CMP_FIELD(a, b, tbf.latency); + } + + return 0; +} + +int +nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b) +{ + return nm_platform_qdisc_cmp_full(a, b, TRUE); +} + +const char * +nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len) +{ + char str_dev[TO_STRING_DEV_BUF_SIZE]; + char act_buf[300]; + char *p; + gsize l; + + if (!nm_utils_to_string_buffer_init_null(tfilter, &buf, &len)) + return buf; + + if (tfilter->action.kind) { + p = act_buf; + l = sizeof(act_buf); + + nm_utils_strbuf_append(&p, &l, " \"%s\"", tfilter->action.kind); + if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + gs_free char *t = NULL; + + nm_utils_strbuf_append( + &p, + &l, + " (\"%s\")", + nm_utils_str_utf8safe_escape(tfilter->action.kind, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL + | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII, + &t)); + } else if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + nm_utils_strbuf_append(&p, + &l, + "%s%s%s%s dev %d", + tfilter->action.mirred.ingress ? " ingress" : "", + tfilter->action.mirred.egress ? " egress" : "", + tfilter->action.mirred.mirror ? " mirror" : "", + tfilter->action.mirred.redirect ? " redirect" : "", + tfilter->action.mirred.ifindex); + } + } else + act_buf[0] = '\0'; + + g_snprintf(buf, + len, + "%s%s family %u handle %x parent %x info %x%s", + tfilter->kind, + _to_string_dev(NULL, tfilter->ifindex, str_dev, sizeof(str_dev)), + tfilter->addr_family, + tfilter->handle, + tfilter->parent, + tfilter->info, + act_buf); + + return buf; +} + +void +nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h) +{ + nm_hash_update_str0(h, obj->kind); + nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info); + if (obj->action.kind) { + nm_hash_update_str(h, obj->action.kind); + if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + nm_hash_update_strarr(h, obj->action.simple.sdata); + } else if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + nm_hash_update_vals(h, + obj->action.mirred.ifindex, + NM_HASH_COMBINE_BOOLS(guint8, + obj->action.mirred.ingress, + obj->action.mirred.egress, + obj->action.mirred.mirror, + obj->action.mirred.redirect)); + } + } +} + +int +nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD(a, b, addr_family); + NM_CMP_FIELD(a, b, handle); + NM_CMP_FIELD(a, b, info); + + NM_CMP_FIELD_STR_INTERNED(a, b, action.kind); + if (a->action.kind) { + if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) { + NM_CMP_FIELD_STR(a, b, action.simple.sdata); + } else if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) { + NM_CMP_FIELD(a, b, action.mirred.ifindex); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.ingress); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.egress); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.mirror); + NM_CMP_FIELD_UNSAFE(a, b, action.mirred.redirect); + } + } + + return 0; +} + +const char * +nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len) +{ + char str_mac[128], mac[128]; + char str_spoof_check[64]; + char str_trust[64]; + char str_min_tx_rate[64]; + char str_max_tx_rate[64]; + nm_auto_free_gstring GString *gstr_vlans = NULL; + guint i; + + if (!nm_utils_to_string_buffer_init_null(vf, &buf, &len)) + return buf; + + if (vf->mac.len) { + _nm_utils_hwaddr_ntoa(vf->mac.data, vf->mac.len, TRUE, mac, sizeof(mac)); + nm_sprintf_buf(str_mac, " mac %s", mac); + } else + str_mac[0] = '\0'; + + if (vf->num_vlans) { + gstr_vlans = g_string_new(""); + for (i = 0; i < vf->num_vlans; i++) { + g_string_append_printf(gstr_vlans, " vlan %u", (unsigned) vf->vlans[i].id); + if (vf->vlans[i].qos) + g_string_append_printf(gstr_vlans, " qos %u", (unsigned) vf->vlans[i].qos); + if (vf->vlans[i].proto_ad) + g_string_append(gstr_vlans, " proto 802.1ad"); + } + } + + g_snprintf(buf, + len, + "%u" /* index */ + "%s" /* MAC */ + "%s" /* spoof check */ + "%s" /* trust */ + "%s" /* min tx rate */ + "%s" /* max tx rate */ + "%s", /* VLANs */ + vf->index, + str_mac, + vf->spoofchk >= 0 ? nm_sprintf_buf(str_spoof_check, " spoofchk %d", vf->spoofchk) + : "", + vf->trust >= 0 ? nm_sprintf_buf(str_trust, " trust %d", vf->trust) : "", + vf->min_tx_rate + ? nm_sprintf_buf(str_min_tx_rate, " min_tx_rate %u", (unsigned) vf->min_tx_rate) + : "", + vf->max_tx_rate + ? nm_sprintf_buf(str_max_tx_rate, " max_tx_rate %u", (unsigned) vf->max_tx_rate) + : "", + gstr_vlans ? gstr_vlans->str : ""); + + return buf; +} + +const char * +nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len) +{ + char str_vid_end[64]; + + if (!nm_utils_to_string_buffer_init_null(vlan, &buf, &len)) + return buf; + + g_snprintf(buf, + len, + "%u" + "%s" + "%s" + "%s", + vlan->vid_start, + vlan->vid_start != vlan->vid_end ? nm_sprintf_buf(str_vid_end, "-%u", vlan->vid_end) + : "", + vlan->pvid ? " PVID" : "", + vlan->untagged ? " untagged" : ""); + + return buf; +} + +void +nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->ifindex, + obj->master, + obj->parent, + obj->n_ifi_flags, + obj->mtu, + obj->type, + obj->arptype, + obj->inet6_addr_gen_mode_inv, + obj->inet6_token, + obj->rx_packets, + obj->rx_bytes, + obj->tx_packets, + obj->tx_bytes, + NM_HASH_COMBINE_BOOLS(guint8, obj->connected, obj->initialized)); + nm_hash_update_strarr(h, obj->name); + nm_hash_update_str0(h, obj->kind); + nm_hash_update_str0(h, obj->driver); + /* nm_hash_update_mem() also hashes the length obj->addr.len */ + nm_hash_update_mem(h, + obj->l_address.data, + NM_MIN(obj->l_address.len, sizeof(obj->l_address.data))); + nm_hash_update_mem(h, + obj->l_broadcast.data, + NM_MIN(obj->l_broadcast.len, sizeof(obj->l_broadcast.data))); +} + +int +nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type); + NM_CMP_FIELD_STR(a, b, name); + NM_CMP_FIELD(a, b, master); + NM_CMP_FIELD(a, b, parent); + NM_CMP_FIELD(a, b, n_ifi_flags); + NM_CMP_FIELD_UNSAFE(a, b, connected); + NM_CMP_FIELD(a, b, mtu); + NM_CMP_FIELD_BOOL(a, b, initialized); + NM_CMP_FIELD(a, b, arptype); + NM_CMP_FIELD(a, b, l_address.len); + NM_CMP_FIELD(a, b, l_broadcast.len); + NM_CMP_FIELD(a, b, inet6_addr_gen_mode_inv); + NM_CMP_FIELD_STR_INTERNED(a, b, kind); + NM_CMP_FIELD_STR_INTERNED(a, b, driver); + if (a->l_address.len) + NM_CMP_FIELD_MEMCMP_LEN(a, b, l_address.data, a->l_address.len); + if (a->l_broadcast.len) + NM_CMP_FIELD_MEMCMP_LEN(a, b, l_broadcast.data, a->l_broadcast.len); + NM_CMP_FIELD_MEMCMP(a, b, inet6_token); + NM_CMP_FIELD(a, b, rx_packets); + NM_CMP_FIELD(a, b, rx_bytes); + NM_CMP_FIELD(a, b, tx_packets); + NM_CMP_FIELD(a, b, tx_bytes); + return 0; +} + +void +nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->forward_delay, + obj->hello_time, + obj->max_age, + obj->ageing_time, + obj->priority, + obj->vlan_protocol, + obj->group_fwd_mask, + obj->group_addr, + obj->mcast_hash_max, + obj->mcast_last_member_count, + obj->mcast_startup_query_count, + obj->mcast_last_member_interval, + obj->mcast_membership_interval, + obj->mcast_querier_interval, + obj->mcast_query_interval, + obj->mcast_router, + obj->mcast_query_response_interval, + obj->mcast_startup_query_interval, + NM_HASH_COMBINE_BOOLS(guint8, + obj->stp_state, + obj->mcast_querier, + obj->mcast_query_use_ifaddr, + obj->mcast_snooping, + obj->vlan_stats_enabled)); +} + +int +nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, forward_delay); + NM_CMP_FIELD(a, b, hello_time); + NM_CMP_FIELD(a, b, max_age); + NM_CMP_FIELD(a, b, ageing_time); + NM_CMP_FIELD_BOOL(a, b, stp_state); + NM_CMP_FIELD(a, b, priority); + NM_CMP_FIELD(a, b, vlan_protocol); + NM_CMP_FIELD_BOOL(a, b, vlan_stats_enabled); + NM_CMP_FIELD(a, b, group_fwd_mask); + NM_CMP_FIELD_MEMCMP(a, b, group_addr); + NM_CMP_FIELD_BOOL(a, b, mcast_snooping); + NM_CMP_FIELD(a, b, mcast_router); + NM_CMP_FIELD_BOOL(a, b, mcast_query_use_ifaddr); + NM_CMP_FIELD_BOOL(a, b, mcast_querier); + NM_CMP_FIELD(a, b, mcast_hash_max); + NM_CMP_FIELD(a, b, mcast_last_member_count); + NM_CMP_FIELD(a, b, mcast_startup_query_count); + NM_CMP_FIELD(a, b, mcast_last_member_interval); + NM_CMP_FIELD(a, b, mcast_membership_interval); + NM_CMP_FIELD(a, b, mcast_querier_interval); + NM_CMP_FIELD(a, b, mcast_query_interval); + NM_CMP_FIELD(a, b, mcast_query_response_interval); + NM_CMP_FIELD(a, b, mcast_startup_query_interval); + + return 0; +} + +void +nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->input_flags, + obj->output_flags, + obj->input_key, + obj->output_key, + obj->ttl, + obj->tos, + (bool) obj->path_mtu_discovery, + (bool) obj->is_tap); +} + +int +nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, input_flags); + NM_CMP_FIELD(a, b, output_flags); + NM_CMP_FIELD(a, b, input_key); + NM_CMP_FIELD(a, b, output_key); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + NM_CMP_FIELD_BOOL(a, b, is_tap); + return 0; +} + +void +nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h) +{ + nm_hash_update_val(h, obj->p_key); + nm_hash_update_str0(h, obj->mode); +} + +int +nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, p_key); + NM_CMP_FIELD_STR_INTERNED(a, b, mode); + return 0; +} + +void +nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->ttl, + obj->tclass, + obj->encap_limit, + obj->proto, + obj->flow_label, + obj->flags, + obj->input_flags, + obj->output_flags, + obj->input_key, + obj->output_key, + (bool) obj->is_gre, + (bool) obj->is_tap); +} + +int +nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD_MEMCMP(a, b, local); + NM_CMP_FIELD_MEMCMP(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tclass); + NM_CMP_FIELD(a, b, encap_limit); + NM_CMP_FIELD(a, b, flow_label); + NM_CMP_FIELD(a, b, proto); + NM_CMP_FIELD(a, b, flags); + NM_CMP_FIELD(a, b, input_flags); + NM_CMP_FIELD(a, b, output_flags); + NM_CMP_FIELD(a, b, input_key); + NM_CMP_FIELD(a, b, output_key); + NM_CMP_FIELD_BOOL(a, b, is_gre); + NM_CMP_FIELD_BOOL(a, b, is_tap); + return 0; +} + +void +nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->ttl, + obj->tos, + (bool) obj->path_mtu_discovery); +} + +int +nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + return 0; +} + +void +nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->parent_ifindex, + obj->sci, + obj->cipher_suite, + obj->window, + obj->icv_length, + obj->encoding_sa, + obj->validation, + NM_HASH_COMBINE_BOOLS(guint8, + obj->encrypt, + obj->protect, + obj->include_sci, + obj->es, + obj->scb, + obj->replay_protect)); +} + +int +nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, sci); + NM_CMP_FIELD(a, b, icv_length); + NM_CMP_FIELD(a, b, cipher_suite); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, encoding_sa); + NM_CMP_FIELD(a, b, validation); + NM_CMP_FIELD_UNSAFE(a, b, encrypt); + NM_CMP_FIELD_UNSAFE(a, b, protect); + NM_CMP_FIELD_UNSAFE(a, b, include_sci); + NM_CMP_FIELD_UNSAFE(a, b, es); + NM_CMP_FIELD_UNSAFE(a, b, scb); + NM_CMP_FIELD_UNSAFE(a, b, replay_protect); + return 0; +} + +void +nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->mode, NM_HASH_COMBINE_BOOLS(guint8, obj->no_promisc, obj->tap)); +} + +int +nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, mode); + NM_CMP_FIELD_UNSAFE(a, b, no_promisc); + NM_CMP_FIELD_UNSAFE(a, b, tap); + return 0; +} + +void +nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->local, + obj->remote, + obj->parent_ifindex, + obj->flags, + obj->ttl, + obj->tos, + obj->proto, + (bool) obj->path_mtu_discovery); +} + +int +nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD(a, b, remote); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery); + NM_CMP_FIELD(a, b, flags); + NM_CMP_FIELD(a, b, proto); + return 0; +} + +void +nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->type, + obj->owner, + obj->group, + NM_HASH_COMBINE_BOOLS(guint8, + obj->owner_valid, + obj->group_valid, + obj->pi, + obj->vnet_hdr, + obj->multi_queue, + obj->persist)); +} + +int +nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, type); + NM_CMP_FIELD(a, b, owner); + NM_CMP_FIELD(a, b, group); + NM_CMP_FIELD_BOOL(a, b, owner_valid); + NM_CMP_FIELD_BOOL(a, b, group_valid); + NM_CMP_FIELD_BOOL(a, b, pi); + NM_CMP_FIELD_BOOL(a, b, vnet_hdr); + NM_CMP_FIELD_BOOL(a, b, multi_queue); + NM_CMP_FIELD_BOOL(a, b, persist); + return 0; +} + +void +nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->id, obj->flags); +} + +int +nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, id); + NM_CMP_FIELD(a, b, flags); + return 0; +} + +void +nm_platform_lnk_vrf_hash_update(const NMPlatformLnkVrf *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->table); +} + +int +nm_platform_lnk_vrf_cmp(const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, table); + return 0; +} + +void +nm_platform_lnk_vxlan_hash_update(const NMPlatformLnkVxlan *obj, NMHashState *h) +{ + nm_hash_update_vals(h, + obj->group6, + obj->local6, + obj->group, + obj->local, + obj->parent_ifindex, + obj->id, + obj->ageing, + obj->limit, + obj->dst_port, + obj->src_port_min, + obj->src_port_max, + obj->tos, + obj->ttl, + NM_HASH_COMBINE_BOOLS(guint8, + obj->learning, + obj->proxy, + obj->rsc, + obj->l2miss, + obj->l3miss)); +} + +int +nm_platform_lnk_vxlan_cmp(const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, parent_ifindex); + NM_CMP_FIELD(a, b, id); + NM_CMP_FIELD(a, b, group); + NM_CMP_FIELD(a, b, local); + NM_CMP_FIELD_MEMCMP(a, b, group6); + NM_CMP_FIELD_MEMCMP(a, b, local6); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD(a, b, ttl); + NM_CMP_FIELD_BOOL(a, b, learning); + NM_CMP_FIELD(a, b, ageing); + NM_CMP_FIELD(a, b, limit); + NM_CMP_FIELD(a, b, dst_port); + NM_CMP_FIELD(a, b, src_port_min); + NM_CMP_FIELD(a, b, src_port_max); + NM_CMP_FIELD_BOOL(a, b, proxy); + NM_CMP_FIELD_BOOL(a, b, rsc); + NM_CMP_FIELD_BOOL(a, b, l2miss); + NM_CMP_FIELD_BOOL(a, b, l3miss); + return 0; +} + +void +nm_platform_lnk_wireguard_hash_update(const NMPlatformLnkWireGuard *obj, NMHashState *h) +{ + nm_hash_update_vals(h, obj->listen_port, obj->fwmark); + nm_hash_update(h, obj->private_key, sizeof(obj->private_key)); + nm_hash_update(h, obj->public_key, sizeof(obj->public_key)); +} + +int +nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, listen_port); + NM_CMP_FIELD(a, b, fwmark); + NM_CMP_FIELD_MEMCMP(a, b, private_key); + NM_CMP_FIELD_MEMCMP(a, b, public_key); + return 0; +} + +static int +_address_pretty_sort_get_prio_4(in_addr_t addr) +{ + if (nm_utils_ip4_address_is_link_local(addr)) + return 0; + return 1; +} + +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 = a1->address & _nm_utils_ip4_prefix_to_netmask(a1->plen); + n2 = a2->address & _nm_utils_ip4_prefix_to_netmask(a2->plen); + NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32)); + return 0; +} + +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_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_TEMPORARY); + ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY); + if (ipv6_privacy1 || ipv6_privacy2) { + gboolean public1 = TRUE; + gboolean public2 = TRUE; + + if (ipv6_privacy1) { + if (a1->n_ifa_flags & IFA_F_TEMPORARY) + public1 = prefer_temp; + else + public1 = !prefer_temp; + } + if (ipv6_privacy2) { + if (a2->n_ifa_flags & IFA_F_TEMPORARY) + 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_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->external, + obj->use_ip4_broadcast_address, + obj->ip4acd_not_ready)); + nm_hash_update_strarr(h, obj->label); +} + +int +nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, address); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD(a, b, peer_address); + 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(a, b, addr_source); + 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_STR(a, b, label); + NM_CMP_FIELD_UNSAFE(a, b, external); + NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready); + return 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->external)); +} + +int +nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b) +{ + const struct in6_addr *p_a, *p_b; + + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD_MEMCMP(a, b, address); + 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)); + NM_CMP_FIELD(a, b, addr_source); + 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_UNSAFE(a, b, external); + return 0; +} + +void +nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h) +{ + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + nm_hash_update_vals( + h, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->tos, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->tos, + /* on top of WEAK_ID: */ + obj->ifindex, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + _ip_route_scope_inv_get_normalized(obj), + obj->gateway, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags & RTNH_F_ONLINK, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + obj->ifindex, + nm_utils_ip4_address_clear_host_address(obj->network, obj->plen), + obj->plen, + obj->metric, + obj->gateway, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + _ip_route_scope_inv_get_normalized(obj), + obj->tos, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK), + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + nm_hash_update_vals(h, + obj->type_coerced, + obj->table_coerced, + obj->ifindex, + obj->network, + obj->plen, + obj->metric, + obj->gateway, + obj->rt_source, + obj->scope_inv, + obj->tos, + obj->mss, + obj->pref_src, + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->r_rtm_flags, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu, + obj->is_external)); + break; + } +} + +int +nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, + const NMPlatformIP4Route *b, + NMPlatformIPRouteCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + NM_CMP_FIELD_UNSAFE(a, b, table_any); + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen)); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD(a, b, tos); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a), + _ip_route_scope_inv_get_normalized(b)); + NM_CMP_FIELD(a, b, gateway); + NM_CMP_FIELD(a, b, mss); + NM_CMP_FIELD(a, b, pref_src); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + NM_CMP_DIRECT(a->r_rtm_flags & RTNH_F_ONLINK, b->r_rtm_flags & RTNH_F_ONLINK); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + } + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_UNSAFE(a, b, table_any); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + } else + NM_CMP_FIELD(a, b, table_coerced); + NM_CMP_FIELD(a, b, ifindex); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen)); + else + NM_CMP_FIELD(a, b, network); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD(a, b, gateway); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a), + _ip_route_scope_inv_get_normalized(b)); + } else { + NM_CMP_FIELD(a, b, rt_source); + NM_CMP_FIELD(a, b, scope_inv); + } + NM_CMP_FIELD(a, b, mss); + NM_CMP_FIELD(a, b, pref_src); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(a->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK), + b->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK)); + } else + NM_CMP_FIELD(a, b, r_rtm_flags); + NM_CMP_FIELD(a, b, tos); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) + NM_CMP_FIELD_UNSAFE(a, b, is_external); + break; + } + return 0; +} + +void +nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h) +{ + struct in6_addr a1, a2; + + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + nm_hash_update_vals( + h, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any), + /* on top of WEAK_ID: */ + obj->ifindex, + obj->gateway); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals( + h, + obj->type_coerced, + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), + obj->ifindex, + *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen), + obj->plen, + obj->metric, + obj->gateway, + obj->pref_src, + *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen), + obj->src_plen, + nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source), + obj->mss, + obj->r_rtm_flags & RTM_F_CLONED, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu), + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + _route_pref_normalize(obj->rt_pref)); + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + nm_hash_update_vals(h, + obj->type_coerced, + obj->table_coerced, + obj->ifindex, + obj->network, + obj->metric, + obj->gateway, + obj->pref_src, + obj->src, + obj->src_plen, + obj->rt_source, + obj->mss, + obj->r_rtm_flags, + NM_HASH_COMBINE_BOOLS(guint8, + obj->metric_any, + obj->table_any, + obj->lock_window, + obj->lock_cwnd, + obj->lock_initcwnd, + obj->lock_initrwnd, + obj->lock_mtu, + obj->is_external), + obj->window, + obj->cwnd, + obj->initcwnd, + obj->initrwnd, + obj->mtu, + obj->rt_pref); + break; + } +} + +int +nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, + const NMPlatformIP6Route *b, + NMPlatformIPRouteCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + switch (cmp_type) { + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID: + NM_CMP_FIELD_UNSAFE(a, b, table_any); + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen)); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen)); + NM_CMP_FIELD(a, b, src_plen); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + } + break; + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: + case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, type_coerced); + NM_CMP_FIELD_UNSAFE(a, b, table_any); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)), + nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); + } else + NM_CMP_FIELD(a, b, table_coerced); + NM_CMP_FIELD(a, b, ifindex); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen)); + else + NM_CMP_FIELD_IN6ADDR(a, b, network); + NM_CMP_FIELD(a, b, plen); + NM_CMP_FIELD_UNSAFE(a, b, metric_any); + NM_CMP_FIELD(a, b, metric); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + NM_CMP_FIELD_IN6ADDR(a, b, pref_src); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen)); + NM_CMP_FIELD(a, b, src_plen); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source)); + } else { + NM_CMP_FIELD_IN6ADDR(a, b, src); + NM_CMP_FIELD(a, b, src_plen); + NM_CMP_FIELD(a, b, rt_source); + } + NM_CMP_FIELD(a, b, mss); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { + NM_CMP_DIRECT(a->r_rtm_flags & RTM_F_CLONED, b->r_rtm_flags & RTM_F_CLONED); + } else + NM_CMP_FIELD(a, b, r_rtm_flags); + NM_CMP_FIELD_UNSAFE(a, b, lock_window); + NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd); + NM_CMP_FIELD_UNSAFE(a, b, lock_mtu); + NM_CMP_FIELD(a, b, window); + NM_CMP_FIELD(a, b, cwnd); + NM_CMP_FIELD(a, b, initcwnd); + NM_CMP_FIELD(a, b, initrwnd); + NM_CMP_FIELD(a, b, mtu); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref)); + else + NM_CMP_FIELD(a, b, rt_pref); + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) + NM_CMP_FIELD_UNSAFE(a, b, is_external); + break; + } + return 0; +} + +#define _ROUTING_RULE_FLAGS_IGNORE \ + (FIB_RULE_UNRESOLVED | FIB_RULE_IIF_DETACHED | FIB_RULE_OIF_DETACHED) + +#define _routing_rule_compare(cmp_type, kernel_support_type) \ + ((cmp_type) == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL \ + || nm_platform_kernel_support_get(kernel_support_type)) + +void +nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj, + NMPlatformRoutingRuleCmpType cmp_type, + NMHashState * h) +{ + gboolean cmp_full = TRUE; + gsize addr_size; + guint32 flags_mask = G_MAXUINT32; + + if (G_UNLIKELY(!NM_IN_SET(obj->addr_family, AF_INET, AF_INET6))) { + /* the address family is not one of the supported ones. That means, the + * instance will only compare equal to itself (pointer-equality). */ + nm_hash_update_val(h, (gconstpointer) obj); + return; + } + + switch (cmp_type) { + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID: + + flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY: + + cmp_full = FALSE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL: + + nm_hash_update_vals( + h, + obj->addr_family, + obj->tun_id, + obj->table, + obj->flags & flags_mask, + obj->priority, + obj->fwmark, + obj->fwmask, + ((cmp_full + || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY + && obj->action == FR_ACT_GOTO)) + ? obj->goto_target + : (guint32) 0u), + ((cmp_full || obj->addr_family == AF_INET) ? obj->flow : (guint32) 0u), + NM_HASH_COMBINE_BOOLS( + guint8, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE) + ? obj->uid_range_has + : FALSE)), + obj->suppress_prefixlen_inverse, + obj->suppress_ifgroup_inverse, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV) + ? (cmp_full ? (guint16) obj->l3mdev : (guint16) !!obj->l3mdev) + : G_MAXUINT16), + obj->action, + obj->tos, + obj->src_len, + obj->dst_len, + (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL) + ? (guint16) obj->protocol + : G_MAXUINT16)); + addr_size = nm_utils_addr_family_to_size(obj->addr_family); + if (cmp_full || obj->src_len > 0) + nm_hash_update(h, &obj->src, addr_size); + if (cmp_full || obj->dst_len > 0) + nm_hash_update(h, &obj->dst, addr_size); + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + if (cmp_full || obj->uid_range_has) + nm_hash_update_valp(h, &obj->uid_range); + } + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + nm_hash_update_val(h, obj->ip_proto); + nm_hash_update_valp(h, &obj->sport_range); + nm_hash_update_valp(h, &obj->dport_range); + } + nm_hash_update_str(h, obj->iifname); + nm_hash_update_str(h, obj->oifname); + return; + } + + nm_assert_not_reached(); +} + +int +nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a, + const NMPlatformRoutingRule *b, + NMPlatformRoutingRuleCmpType cmp_type) +{ + gboolean cmp_full = TRUE; + gsize addr_size; + bool valid; + guint32 flags_mask = G_MAXUINT32; + + NM_CMP_SELF(a, b); + + valid = NM_IN_SET(a->addr_family, AF_INET, AF_INET6); + NM_CMP_DIRECT(valid, (bool) NM_IN_SET(b->addr_family, AF_INET, AF_INET6)); + + if (G_UNLIKELY(!valid)) { + /* the address family is not one of the supported ones. That means, the + * instance will only compare equal to itself. */ + NM_CMP_DIRECT((uintptr_t) a, (uintptr_t) b); + nm_assert_not_reached(); + return 0; + } + + switch (cmp_type) { + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID: + + flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY: + + cmp_full = FALSE; + + /* fall-through */ + case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, addr_family); + NM_CMP_FIELD(a, b, action); + NM_CMP_FIELD(a, b, priority); + NM_CMP_FIELD(a, b, tun_id); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) { + if (cmp_full) + NM_CMP_FIELD(a, b, l3mdev); + else + NM_CMP_FIELD_BOOL(a, b, l3mdev); + } + + NM_CMP_FIELD(a, b, table); + + NM_CMP_DIRECT(a->flags & flags_mask, b->flags & flags_mask); + + NM_CMP_FIELD(a, b, fwmark); + NM_CMP_FIELD(a, b, fwmask); + + if (cmp_full + || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY + && a->action == FR_ACT_GOTO)) + NM_CMP_FIELD(a, b, goto_target); + + NM_CMP_FIELD(a, b, suppress_prefixlen_inverse); + NM_CMP_FIELD(a, b, suppress_ifgroup_inverse); + NM_CMP_FIELD(a, b, tos); + + if (cmp_full || a->addr_family == AF_INET) + NM_CMP_FIELD(a, b, flow); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) + NM_CMP_FIELD(a, b, protocol); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) { + NM_CMP_FIELD(a, b, ip_proto); + NM_CMP_FIELD(a, b, sport_range.start); + NM_CMP_FIELD(a, b, sport_range.end); + NM_CMP_FIELD(a, b, dport_range.start); + NM_CMP_FIELD(a, b, dport_range.end); + } + + addr_size = nm_utils_addr_family_to_size(a->addr_family); + + NM_CMP_FIELD(a, b, src_len); + if (cmp_full || a->src_len > 0) + NM_CMP_FIELD_MEMCMP_LEN(a, b, src, addr_size); + + NM_CMP_FIELD(a, b, dst_len); + if (cmp_full || a->dst_len > 0) + NM_CMP_FIELD_MEMCMP_LEN(a, b, dst, addr_size); + + if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) { + NM_CMP_FIELD_UNSAFE(a, b, uid_range_has); + if (cmp_full || a->uid_range_has) { + NM_CMP_FIELD(a, b, uid_range.start); + NM_CMP_FIELD(a, b, uid_range.end); + } + } + + NM_CMP_FIELD_STR(a, b, iifname); + NM_CMP_FIELD_STR(a, b, oifname); + return 0; + } + + nm_assert_not_reached(); + return 0; +} + +/** + * nm_platform_ip_address_cmp_expiry: + * @a: a NMPlatformIPAddress to compare + * @b: the other NMPlatformIPAddress to compare + * + * Compares two addresses and returns which one has a longer remaining lifetime. + * If both addresses have the same lifetime, look at the remaining preferred time. + * + * For comparison, only the timestamp, lifetime and preferred fields are considered. + * If they compare equal (== 0), their other fields were not considered. + * + * Returns: -1, 0, or 1 according to the comparison + **/ +int +nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b) +{ + gint64 ta = 0, tb = 0; + + NM_CMP_SELF(a, b); + + if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0) + ta = G_MAXINT64; + else if (a->timestamp) + ta = ((gint64) a->timestamp) + a->lifetime; + + if (b->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0) + tb = G_MAXINT64; + else if (b->timestamp) + tb = ((gint64) b->timestamp) + b->lifetime; + + if (ta == tb) { + /* if the lifetime is equal, compare the preferred time. */ + ta = tb = 0; + + if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT + || a->lifetime == 0 /* lifetime==0 means permanent! */) + ta = G_MAXINT64; + else if (a->timestamp) + ta = ((gint64) a->timestamp) + a->preferred; + + if (b->preferred == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0) + tb = G_MAXINT64; + else if (b->timestamp) + tb = ((gint64) b->timestamp) + b->preferred; + + if (ta == tb) + return 0; + } + + return ta < tb ? -1 : 1; +} + +/*****************************************************************************/ + +GHashTable * +nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + GHashTable * hash; + + g_return_val_if_fail(NM_IS_PLATFORM(self), NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex); + + head_entry = nmp_cache_lookup(NM_PLATFORM_GET_PRIVATE(self)->cache, &lookup); + + if (!head_entry) + return NULL; + + hash = g_hash_table_new(nm_direct_hash, NULL); + + nmp_cache_iter_for_each (&iter, head_entry, &obj) { + const NMPlatformIP4Address *a = NMP_OBJECT_CAST_IP4_ADDRESS(obj); + + g_hash_table_add(hash, GUINT_TO_POINTER(a->address)); + } + + return hash; +} + +/*****************************************************************************/ + +const char * +nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type) +{ + switch (change_type) { + case NM_PLATFORM_SIGNAL_ADDED: + return "added"; + case NM_PLATFORM_SIGNAL_CHANGED: + return "changed"; + case NM_PLATFORM_SIGNAL_REMOVED: + return "removed"; + default: + g_return_val_if_reached("UNKNOWN"); + } +} + +static void +log_link(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformLink * device, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: link %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_link_to_string(device, NULL, 0)); +} + +static void +log_ip4_address(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Address * address, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: address 4 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip4_address_to_string(address, NULL, 0)); +} + +static void +log_ip6_address(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Address * address, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: address 6 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip6_address_to_string(address, NULL, 0)); +} + +static void +log_ip4_route(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Route * route, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: route 4 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip4_route_to_string(route, NULL, 0)); +} + +static void +log_ip6_route(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Route * route, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: route 6 %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_ip6_route_to_string(route, NULL, 0)); +} + +static void +log_routing_rule(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformRoutingRule * routing_rule, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + /* routing rules don't have an ifindex. We probably should refactor the signals that are emitted for platform changes. */ + _LOG3D("signal: rt-rule %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_routing_rule_to_string(routing_rule, NULL, 0)); +} + +static void +log_qdisc(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformQdisc * qdisc, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: qdisc %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_qdisc_to_string(qdisc, NULL, 0)); +} + +static void +log_tfilter(NMPlatform * self, + NMPObjectType obj_type, + int ifindex, + NMPlatformTfilter * tfilter, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + _LOG3D("signal: tfilter %7s: %s", + nm_platform_signal_change_type_to_string(change_type), + nm_platform_tfilter_to_string(tfilter, NULL, 0)); +} + +/*****************************************************************************/ + +void +nm_platform_cache_update_emit_signal(NMPlatform * self, + NMPCacheOpsType cache_op, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ + gboolean visible_new; + gboolean visible_old; + const NMPObject *o; + const NMPClass * klass; + int ifindex; + + nm_assert(NM_IN_SET((NMPlatformSignalChangeType) cache_op, + NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED)); + + ASSERT_nmp_cache_ops(nm_platform_get_cache(self), cache_op, obj_old, obj_new); + + NMTST_ASSERT_PLATFORM_NETNS_CURRENT(self); + + switch (cache_op) { + case NMP_CACHE_OPS_ADDED: + if (!nmp_object_is_visible(obj_new)) + return; + o = obj_new; + break; + case NMP_CACHE_OPS_UPDATED: + visible_old = nmp_object_is_visible(obj_old); + visible_new = nmp_object_is_visible(obj_new); + if (!visible_old && visible_new) { + o = obj_new; + cache_op = NMP_CACHE_OPS_ADDED; + } else if (visible_old && !visible_new) { + o = obj_old; + cache_op = NMP_CACHE_OPS_REMOVED; + } else if (!visible_new) { + /* it was invisible and stayed invisible. Nothing to do. */ + return; + } else + o = obj_new; + break; + case NMP_CACHE_OPS_REMOVED: + if (!nmp_object_is_visible(obj_old)) + return; + o = obj_old; + break; + default: + nm_assert(cache_op == NMP_CACHE_OPS_UNCHANGED); + return; + } + + klass = NMP_OBJECT_GET_CLASS(o); + + if (klass->obj_type == NMP_OBJECT_TYPE_ROUTING_RULE) + ifindex = 0; + else + ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(o)->ifindex; + + if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE + && NM_PLATFORM_GET_PRIVATE(self)->ip4_dev_route_blacklist_gc_timeout_id + && NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)) + _ip4_dev_route_blacklist_notify_route(self, o); + + _LOG3t("emit signal %s %s: %s", + klass->signal_type, + nm_platform_signal_change_type_to_string((NMPlatformSignalChangeType) cache_op), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + + nmp_object_ref(o); + g_signal_emit(self, + _nm_platform_signal_id_get(klass->signal_type_id), + 0, + (int) klass->obj_type, + ifindex, + &o->object, + (int) cache_op); + nmp_object_unref(o); +} + +/*****************************************************************************/ + +NMPCache * +nm_platform_get_cache(NMPlatform *self) +{ + return NM_PLATFORM_GET_PRIVATE(self)->cache; +} + +NMPNetns * +nm_platform_netns_get(NMPlatform *self) +{ + _CHECK_SELF(self, klass, NULL); + + return self->_netns; +} + +gboolean +nm_platform_netns_push(NMPlatform *self, NMPNetns **netns) +{ + g_return_val_if_fail(NM_IS_PLATFORM(self), FALSE); + + if (self->_netns && !nmp_netns_push(self->_netns)) { + NM_SET_OUT(netns, NULL); + return FALSE; + } + + NM_SET_OUT(netns, self->_netns); + return TRUE; +} + +/*****************************************************************************/ + +const _NMPlatformVTableRouteUnion nm_platform_vtable_route = { + .v4 = + { + .is_ip4 = TRUE, + .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE, + .addr_family = AF_INET, + .sizeof_route = sizeof(NMPlatformIP4Route), + .route_cmp = (int (*)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp, + .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route, + char * buf, + gsize len)) nm_platform_ip4_route_to_string, + }, + .v6 = + { + .is_ip4 = FALSE, + .obj_type = NMP_OBJECT_TYPE_IP6_ROUTE, + .addr_family = AF_INET6, + .sizeof_route = sizeof(NMPlatformIP6Route), + .route_cmp = (int (*)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp, + .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route, + char * buf, + gsize len)) nm_platform_ip6_route_to_string, + }, +}; + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMPlatform * self = NM_PLATFORM(object); + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_NETNS_SUPPORT: + /* construct-only */ + if (g_value_get_boolean(value)) { + NMPNetns *netns; + + netns = nmp_netns_get_current(); + if (netns) + self->_netns = g_object_ref(netns); + } + break; + case PROP_USE_UDEV: + /* construct-only */ + priv->use_udev = g_value_get_boolean(value); + break; + case PROP_LOG_WITH_PTR: + /* construct-only */ + priv->log_with_ptr = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nm_platform_init(NMPlatform *self) +{ + self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_PLATFORM, NMPlatformPrivate); +} + +static GObject * +constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params) +{ + GObject * object; + NMPlatform * self; + NMPlatformPrivate *priv; + + object = G_OBJECT_CLASS(nm_platform_parent_class) + ->constructor(type, n_construct_params, construct_params); + self = NM_PLATFORM(object); + priv = NM_PLATFORM_GET_PRIVATE(self); + + priv->multi_idx = nm_dedup_multi_index_new(); + + priv->cache = nmp_cache_new(priv->multi_idx, priv->use_udev); + + return object; +} + +static void +finalize(GObject *object) +{ + NMPlatform * self = NM_PLATFORM(object); + NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self); + + nm_clear_g_source(&priv->ip4_dev_route_blacklist_check_id); + nm_clear_g_source(&priv->ip4_dev_route_blacklist_gc_timeout_id); + nm_clear_pointer(&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref); + g_clear_object(&self->_netns); + nm_dedup_multi_index_unref(priv->multi_idx); + nmp_cache_free(priv->cache); +} + +static void +nm_platform_class_init(NMPlatformClass *platform_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(platform_class); + + g_type_class_add_private(object_class, sizeof(NMPlatformPrivate)); + + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->finalize = finalize; + + platform_class->wifi_set_powersave = wifi_set_powersave; + + g_object_class_install_property( + object_class, + PROP_NETNS_SUPPORT, + g_param_spec_boolean(NM_PLATFORM_NETNS_SUPPORT, + "", + "", + NM_PLATFORM_NETNS_SUPPORT_DEFAULT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property( + object_class, + PROP_USE_UDEV, + g_param_spec_boolean(NM_PLATFORM_USE_UDEV, + "", + "", + FALSE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property( + object_class, + PROP_LOG_WITH_PTR, + g_param_spec_boolean(NM_PLATFORM_LOG_WITH_PTR, + "", + "", + TRUE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + +#define SIGNAL(signal, signal_id, method) \ + G_STMT_START \ + { \ + signals[signal] = \ + g_signal_new_class_handler("" signal_id "", \ + G_OBJECT_CLASS_TYPE(object_class), \ + G_SIGNAL_RUN_FIRST, \ + G_CALLBACK(method), \ + NULL, \ + NULL, \ + NULL, \ + G_TYPE_NONE, \ + 4, \ + G_TYPE_INT, /* (int) NMPObjectType */ \ + G_TYPE_INT, /* ifindex */ \ + G_TYPE_POINTER /* const NMPObject * */, \ + G_TYPE_INT /* (int) NMPlatformSignalChangeType */ \ + ); \ + } \ + G_STMT_END + + /* Signals */ + SIGNAL(NM_PLATFORM_SIGNAL_ID_LINK, NM_PLATFORM_SIGNAL_LINK_CHANGED, log_link); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + log_ip4_address); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + log_ip6_address); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, log_ip4_route); + SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, log_ip6_route); + SIGNAL(NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, + log_routing_rule); + SIGNAL(NM_PLATFORM_SIGNAL_ID_QDISC, NM_PLATFORM_SIGNAL_QDISC_CHANGED, log_qdisc); + SIGNAL(NM_PLATFORM_SIGNAL_ID_TFILTER, NM_PLATFORM_SIGNAL_TFILTER_CHANGED, log_tfilter); +} diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h new file mode 100644 index 00000000..9d40cbbe --- /dev/null +++ b/src/libnm-platform/nm-platform.h @@ -0,0 +1,2396 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 - 2018 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_PLATFORM_H__ +#define __NETWORKMANAGER_PLATFORM_H__ + +#include "libnm-platform/nmp-base.h" +#include "libnm-base/nm-base.h" + +#define NM_TYPE_PLATFORM (nm_platform_get_type()) +#define NM_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PLATFORM, NMPlatform)) +#define NM_PLATFORM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PLATFORM, NMPlatformClass)) +#define NM_IS_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PLATFORM)) +#define NM_IS_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PLATFORM)) +#define NM_PLATFORM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PLATFORM, NMPlatformClass)) + +#define NM_PLATFORM_NETNS_SUPPORT_DEFAULT FALSE + +/*****************************************************************************/ + +#define NM_PLATFORM_NETNS_SUPPORT "netns-support" +#define NM_PLATFORM_USE_UDEV "use-udev" +#define NM_PLATFORM_LOG_WITH_PTR "log-with-ptr" + +/*****************************************************************************/ + +/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these + * headers conflicted, so we cannot simply include either of them in a header-file.*/ +#define NMP_IFNAMSIZ 16 + +/*****************************************************************************/ + +struct _NMPWireGuardPeer; + +struct udev_device; + +typedef gboolean (*NMPObjectPredicateFunc)(const NMPObject *obj, gpointer user_data); + +/* workaround for older libnl version, that does not define these flags. */ +#ifndef IFA_F_MANAGETEMPADDR + #define IFA_F_MANAGETEMPADDR 0x100 +#endif +#ifndef IFA_F_NOPREFIXROUTE + #define IFA_F_NOPREFIXROUTE 0x200 +#endif + +#define NM_RT_SCOPE_LINK 253 /* RT_SCOPE_LINK */ + +/* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers + * that don't define it. */ +#define NM_IN6_ADDR_GEN_MODE_UNKNOWN 255 /* no corresponding value. */ +#define NM_IN6_ADDR_GEN_MODE_EUI64 0 /* IN6_ADDR_GEN_MODE_EUI64 */ +#define NM_IN6_ADDR_GEN_MODE_NONE 1 /* IN6_ADDR_GEN_MODE_NONE */ +#define NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY 2 /* IN6_ADDR_GEN_MODE_STABLE_PRIVACY */ +#define NM_IN6_ADDR_GEN_MODE_RANDOM 3 /* IN6_ADDR_GEN_MODE_RANDOM */ + +#define NM_IFF_MULTI_QUEUE 0x0100 /* IFF_MULTI_QUEUE */ + +/* Redefine this in host's endianness */ +#define NM_GRE_KEY 0x2000 + +typedef enum { + NMP_NLM_FLAG_F_ECHO = 0x08, /* NLM_F_ECHO, Echo this request */ + + /* use our own platform enum for the nlmsg-flags. Otherwise, we'd have + * to include <linux/netlink.h> */ + NMP_NLM_FLAG_F_REPLACE = 0x100, /* NLM_F_REPLACE, Override existing */ + NMP_NLM_FLAG_F_EXCL = 0x200, /* NLM_F_EXCL, Do not touch, if it exists */ + NMP_NLM_FLAG_F_CREATE = 0x400, /* NLM_F_CREATE, Create, if it does not exist */ + NMP_NLM_FLAG_F_APPEND = 0x800, /* NLM_F_APPEND, Add to end of list */ + + NMP_NLM_FLAG_FMASK = 0xFFFF, /* a mask for all NMP_NLM_FLAG_F_* flags */ + + /* instructs NM to suppress logging an error message for any failures + * received from kernel. + * + * It will still log with debug-level, and it will still log + * other failures aside the kernel response. */ + NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE = 0x10000, + + /* the following aliases correspond to iproute2's `ip route CMD` for + * RTM_NEWROUTE, with CMD being one of add, change, replace, prepend, + * append and test. */ + NMP_NLM_FLAG_ADD = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_EXCL, + NMP_NLM_FLAG_CHANGE = NMP_NLM_FLAG_F_REPLACE, + NMP_NLM_FLAG_REPLACE = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_REPLACE, + NMP_NLM_FLAG_PREPEND = NMP_NLM_FLAG_F_CREATE, + NMP_NLM_FLAG_APPEND = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_APPEND, + NMP_NLM_FLAG_TEST = NMP_NLM_FLAG_F_EXCL, +} NMPNlmFlags; + +typedef enum { + /* compare fields which kernel considers as similar routes. + * It is a looser comparisong then NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID + * and means that `ip route add` would fail to add two routes + * that have the same NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID. + * On the other hand, `ip route append` would allow that, as + * long as NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID differs. */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + + /* compare two routes as kernel would allow to add them with + * `ip route append`. In other words, kernel does not allow you to + * add two routes (at the same time) which compare equal according + * to NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID. + * + * For the ID we can only recognize route fields that we actually implement. + * However, kernel supports more routing options, some of them also part of + * the ID. NetworkManager is oblivious to these options and will wrongly think + * that two routes are identical, while they are not. That can lead to an + * inconsistent platform cache. Not much what we can do about that, except + * implementing all options that kernel supports *sigh*. See rh#1337860. + */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, + + /* compare all fields as they make sense for kernel. For example, + * a route destination 192.168.1.5/24 is not accepted by kernel and + * we treat it identical to 192.168.1.0/24. Semantically these + * routes are identical, but NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL will + * report them as different. + * + * The result shall be identical to call first nm_platform_ip_route_normalize() + * on both routes and then doing a full comparison. */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY, + + /* compare all fields. This should have the same effect as memcmp(), + * except allowing for undefined data in holes between field alignment. + */ + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + +} NMPlatformIPRouteCmpType; + +typedef enum { + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY, + + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, +} NMPlatformRoutingRuleCmpType; + +typedef struct { + union { + guint8 data[20 /* _NM_UTILS_HWADDR_LEN_MAX */]; + NMEtherAddr ether_addr; + }; + guint8 len; +} NMPLinkAddress; + +/* assert that NMEtherAddr does not affect the alignment of NMPLinkAddress struct. */ +G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1); +G_STATIC_ASSERT(_nm_alignof(NMPLinkAddress) == 1); + +gconstpointer nmp_link_address_get(const NMPLinkAddress *addr, size_t *length); +GBytes * nmp_link_address_get_as_bytes(const NMPLinkAddress *addr); + +typedef enum { + + /* match-flags are strictly inclusive. That means, + * by default nothing is matched, but if you enable a particular + * flag, a candidate that matches passes the check. + * + * In other words: adding more flags can only extend the result + * set of matching objects. + * + * Also, the flags form partitions. Like, an address can be either of + * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for + * the ADDRSTATE match types. + */ + NM_PLATFORM_MATCH_WITH_NONE = 0, + + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL = (1LL << 0), + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL = (1LL << 1), + NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY = + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL, + + NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL = (1LL << 2), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE = (1LL << 3), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED = (1LL << 4), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED = (1LL << 5), + NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY = + NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED, +} NMPlatformMatchFlags; + +#define NM_PLATFORM_LINK_OTHER_NETNS (-1) + +struct _NMPlatformObject { + /* the object type has no fields of its own, it is only used to having + * a special pointer type that can be used to indicate "any" type. */ + char _dummy_don_t_use_me; +}; + +#define __NMPlatformObjWithIfindex_COMMON \ + int ifindex; \ + ; + +struct _NMPlatformObjWithIfindex { + __NMPlatformObjWithIfindex_COMMON; +}; + +struct _NMPlatformLink { + __NMPlatformObjWithIfindex_COMMON; + char name[NMP_IFNAMSIZ]; + NMLinkType type; + + /* rtnl_link_get_type(), IFLA_INFO_KIND. */ + /* NMPlatform initializes this field with a static string. */ + const char *kind; + + /* NMPlatform initializes this field with a static string. */ + const char *driver; + + int master; + + /* rtnl_link_get_link(), IFLA_LINK. + * If IFLA_LINK_NETNSID indicates that the parent is in another namespace, + * this field be set to (negative) NM_PLATFORM_LINK_OTHER_NETNS. */ + int parent; + + /* IFF_* flags. Note that the flags in 'struct ifinfomsg' are declared as 'unsigned'. */ + guint n_ifi_flags; + + guint mtu; + + /* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */ + guint32 arptype; + + /* IFLA_ADDRESS */ + NMPLinkAddress l_address; + + /* IFLA_BROADCAST */ + NMPLinkAddress l_broadcast; + + /* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */ + NMUtilsIPv6IfaceId inet6_token; + + /* The bitwise inverse of rtnl_link_inet6_get_addr_gen_mode(). It is inverse + * to have a default of 0 -- meaning: unspecified. That way, a struct + * initialized with memset(0) has and unset value.*/ + guint8 inet6_addr_gen_mode_inv; + + /* Statistics */ + guint64 rx_packets; + guint64 rx_bytes; + guint64 tx_packets; + guint64 tx_bytes; + + /* @connected is mostly identical to (@n_ifi_flags & IFF_UP). Except for bridge/bond masters, + * where we coerce the link as disconnect if it has no slaves. */ + bool connected : 1; + + bool initialized : 1; +}; + +typedef enum { /*< skip >*/ + NM_PLATFORM_SIGNAL_ID_NONE, + NM_PLATFORM_SIGNAL_ID_LINK, + NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, + NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, + NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + NM_PLATFORM_SIGNAL_ID_QDISC, + NM_PLATFORM_SIGNAL_ID_TFILTER, + _NM_PLATFORM_SIGNAL_ID_LAST, +} NMPlatformSignalIdType; + +guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type); + +typedef enum { + NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, +} NMPlatformSignalChangeType; + +#define NM_PLATFORM_IP_ADDRESS_CAST(address) \ + NM_CONSTCAST(NMPlatformIPAddress, \ + (address), \ + NMPlatformIPXAddress, \ + NMPlatformIP4Address, \ + NMPlatformIP6Address) + +#define __NMPlatformIPAddress_COMMON \ + __NMPlatformObjWithIfindex_COMMON; \ + NMIPConfigSource addr_source; \ + \ + /* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*(). + * + * The rules are: + * 1 @lifetime==0: @timestamp and @preferred is irrelevant (but mostly set to 0 too). Such addresses + * are permanent. This rule is so that unset addresses (calloc) are permanent by default. + * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly + * set to 0). Such addresses are permanent. + * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp + * and never returned by nm_utils_get_monotonic_timestamp_sec(). In this case @valid/@preferred + * is anchored at @timestamp. + * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time + * moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add(). + * + * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the + * lease was received. Addresses from kernel might have the @timestamp based on the last modification + * time of the addresses. But don't rely on this behaviour, the @timestamp is only defined for anchoring + * @lifetime and @preferred. + */ \ + guint32 timestamp; \ + guint32 lifetime; /* seconds since timestamp */ \ + guint32 preferred; /* seconds since timestamp */ \ + \ + /* ifa_flags in 'struct ifaddrmsg' from <linux/if_addr.h>, extended to 32 bit by + * IFA_FLAGS attribute. */ \ + guint32 n_ifa_flags; \ + \ + guint8 plen; \ + \ + /* FIXME(l3cfg): the external marker won't be necessary anymore, because we only + * merge addresses we care about, and ignore (don't remove) external addresses. */ \ + bool external : 1; \ + \ + bool use_ip4_broadcast_address : 1; \ + \ + /* Whether the address is ready to be configured. By default, an address is, but this + * flag may indicate that the address is just for tracking purpose only, but the ACD + * state is not yet ready for the address to be configured. */ \ + bool ip4acd_not_ready : 1; \ + ; + +/** + * NMPlatformIPAddress: + * + * Common parts of NMPlatformIP4Address and NMPlatformIP6Address. + **/ +typedef struct { + __NMPlatformIPAddress_COMMON; + union { + guint8 address_ptr[1]; + guint32 __dummy_for_32bit_alignment; + }; +} NMPlatformIPAddress; + +/** + * NMPlatformIP4Address: + * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec() + **/ +struct _NMPlatformIP4Address { + __NMPlatformIPAddress_COMMON; + + /* The local address IFA_LOCAL. */ + in_addr_t address; + + /* The IFA_ADDRESS PTP peer address. This field is rather important, because + * it constitutes the identifier for the IPv4 address (e.g. you can add two + * addresses that only differ by their peer's network-part. + * + * Beware that for most cases, NetworkManager doesn't want to set an explicit + * peer-address. However, that corresponds to setting the peer address to @address + * itself. Leaving peer-address unset/zero, means explicitly setting the peer + * address to 0.0.0.0, which you probably don't want. + * */ + in_addr_t peer_address; /* PTP peer address */ + + /* IFA_BROADCAST. + * + * This parameter is ignored unless use_ip4_broadcast_address is TRUE. + * See nm_platform_ip4_broadcast_address_from_addr(). */ + in_addr_t broadcast_address; + + char label[NMP_IFNAMSIZ]; +}; + +/** + * NMPlatformIP6Address: + * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec() + **/ +struct _NMPlatformIP6Address { + __NMPlatformIPAddress_COMMON; + struct in6_addr address; + struct in6_addr peer_address; +}; + +typedef union { + NMPlatformIPAddress ax; + NMPlatformIP4Address a4; + NMPlatformIP6Address a6; +} NMPlatformIPXAddress; + +#undef __NMPlatformIPAddress_COMMON + +#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address){__VA_ARGS__})) + +#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address){__VA_ARGS__})) + +/* Default value for adding an IPv4 route. This is also what iproute2 does. + * Note that contrary to IPv6, you can add routes with metric 0 and it is even + * the default. + */ +#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 ((guint32) 0u) + +/* Default value for adding an IPv6 route. This is also what iproute2 does. + * Adding an IPv6 route with metric 0, kernel translates to IP6_RT_PRIO_USER (1024). + * + * Note that kernel doesn't allow adding IPv6 routes with metric zero via netlink. + * It however can itself add routes with metric zero. */ +#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 ((guint32) 1024u) + +/* For IPv4, kernel adds a device route (subnet routes) with metric 0 when user + * configures addresses. */ +#define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE ((guint32) 0u) + +#define __NMPlatformIPRoute_COMMON \ + __NMPlatformObjWithIfindex_COMMON; \ + \ + /* The NMIPConfigSource. For routes that we receive from cache this corresponds + * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values). + * When adding a route, the source will be coerced to the protocol using + * nmp_utils_ip_config_source_coerce_to_rtprot(). + * + * rtm_protocol is part of the primary key of an IPv4 route (meaning, you can add + * two IPv4 routes that only differ in their rtm_protocol. For IPv6, that is not + * the case. + * + * When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even + * if it is not part of the primary key for IPv6) -- unless rtm_protocol is set + * to zero, in which case the first matching route (with proto ignored) is deleted. */ \ + NMIPConfigSource rt_source; \ + \ + guint8 plen; \ + \ + /* RTA_METRICS: + * + * For IPv4 routes, these properties are part of their + * ID (meaning: you can add otherwise identical IPv4 routes that + * only differ by the metric property). + * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ + * by an RTA_METRICS property. + * + * When deleting a route, kernel seems to ignore the RTA_METRICS properties. + * That is a problem/bug for IPv4 because you cannot explicitly select which + * route to delete. Kernel just picks the first. See rh#1475642. */ \ + \ + /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */ \ + bool lock_window : 1; \ + bool lock_cwnd : 1; \ + bool lock_initcwnd : 1; \ + bool lock_initrwnd : 1; \ + bool lock_mtu : 1; \ + \ + /* if TRUE, the "metric" field is interpreted as an offset that is added to a default + * metric. For example, form a DHCP lease we don't know the actually used metric, because + * that is determined by upper layers (the configuration). However, we have a default + * metric that should be used. So we set "metric_any" to %TRUE, which means to use + * the default metric. However, we still treat the "metric" field as an offset that + * will be added to the default metric. In most case, you want that "metric" is zero + * when setting "metric_any". */ \ + bool metric_any : 1; \ + \ + /* like "metric_any", the table is determined by other layers of the code. + * This field overrides "table_coerced" field. If "table_any" is true, then + * the "table_coerced" field is ignored (unlike for the metric). */ \ + bool table_any : 1; \ + \ + /* This route is tracked as external route, that is not a route that NetworkManager + * actively wants to add, but a route that was added externally. In some cases, such + * a route should be ignored. + * + * Note that unlike most other fields here, this flag only exists inside NetworkManager + * and is not reflected on netlink. */ \ + bool is_external : 1; \ + \ + /* rtnh_flags + * + * Routes with rtm_flags RTM_F_CLONED are hidden by platform and + * do not exist from the point-of-view of platform users. + * Such a route is not alive, according to nmp_object_is_alive(). + * + * NOTE: currently we ignore all flags except RTM_F_CLONED + * and RTNH_F_ONLINK. + * We also may not properly consider the flags as part of the ID + * in route-cmp. */ \ + unsigned r_rtm_flags; \ + \ + /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \ + guint32 mss; \ + \ + /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */ \ + guint32 window; \ + \ + /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */ \ + guint32 cwnd; \ + \ + /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */ \ + guint32 initcwnd; \ + \ + /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */ \ + guint32 initrwnd; \ + \ + /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */ \ + guint32 mtu; \ + \ + /* RTA_PRIORITY (iproute2: metric) + * If "metric_any" is %TRUE, then this is interpreted as an offset that will be + * added to a default base metric. In such cases, the offset is usually zero. */ \ + guint32 metric; \ + \ + /* rtm_table, RTA_TABLE. + * + * This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and + * zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main + * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \ + guint32 table_coerced; \ + \ + /* rtm_type. + * + * This is not the original type, if type_coerced is 0 then + * it means RTN_UNSPEC otherwise the type value is preserved. + * */ \ + guint8 type_coerced; \ + \ + /*end*/ + +typedef struct { + __NMPlatformIPRoute_COMMON; + union { + guint8 network_ptr[1]; + guint32 __dummy_for_32bit_alignment; + }; +} NMPlatformIPRoute; + +#define NM_PLATFORM_IP_ROUTE_CAST(route) \ + NM_CONSTCAST(NMPlatformIPRoute, \ + (route), \ + NMPlatformIPXRoute, \ + NMPlatformIP4Route, \ + NMPlatformIP6Route) + +#define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) (NM_PLATFORM_IP_ROUTE_CAST(route)->plen <= 0) + +struct _NMPlatformIP4Route { + __NMPlatformIPRoute_COMMON; + in_addr_t network; + + /* RTA_GATEWAY. The gateway is part of the primary key for a route */ + in_addr_t gateway; + + /* RTA_PREFSRC (called "src" by iproute2). + * + * pref_src is part of the ID of an IPv4 route. When deleting a route, + * pref_src must match, unless set to 0.0.0.0 to match any. */ + in_addr_t pref_src; + + /* rtm_tos (iproute2: tos) + * + * For IPv4, tos is part of the weak-id (like metric). + * + * For IPv6, tos is ignored by kernel. */ + guint8 tos; + + /* The bitwise inverse of the route scope rtm_scope. It is inverted so that the + * default value (RT_SCOPE_NOWHERE) is zero. Use nm_platform_route_scope_inv() + * to convert back and forth between the inverse representation and the + * real value. + * + * rtm_scope is part of the primary key for IPv4 routes. When deleting a route, + * the scope must match, unless it is left at RT_SCOPE_NOWHERE, in which case the first + * matching route is deleted. + * + * For IPv6 routes, the scope is ignored and kernel always assumes global scope. + * Hence, this field is only in NMPlatformIP4Route. */ + guint8 scope_inv; +}; + +struct _NMPlatformIP6Route { + __NMPlatformIPRoute_COMMON; + struct in6_addr network; + + /* RTA_GATEWAY. The gateway is part of the primary key for a route */ + struct in6_addr gateway; + + /* RTA_PREFSRC (called "src" by iproute2). + * + * pref_src is not part of the ID for an IPv6 route. You cannot add two + * routes that only differ by pref_src. + * + * When deleting a route, pref_src is ignored by kernel. */ + struct in6_addr pref_src; + + /* RTA_SRC and rtm_src_len (called "from" by iproute2). + * + * Kernel clears the host part of src/src_plen. + * + * src/src_plen is part of the ID of a route just like network/plen. That is, + * Not only `ip route append`, but also `ip route add` allows to add routes that only + * differ in their src/src_plen. + */ + struct in6_addr src; + guint8 src_plen; + + /* RTA_PREF router preference. + * + * The type is guint8 to keep the struct size small. But the values are compatible with + * the NMIcmpv6RouterPref enum. */ + guint8 rt_pref; +}; + +typedef union { + NMPlatformIPRoute rx; + NMPlatformIP4Route r4; + NMPlatformIP6Route r6; +} NMPlatformIPXRoute; + +#undef __NMPlatformIPRoute_COMMON + +typedef struct { + /* struct fib_rule_uid_range */ + guint32 start; + guint32 end; +} NMFibRuleUidRange; + +typedef struct { + /* struct fib_rule_port_range */ + guint16 start; + guint16 end; +} NMFibRulePortRange; + +typedef struct { + NMIPAddr src; /* FRA_SRC */ + NMIPAddr dst; /* FRA_DST */ + guint64 tun_id; /* betoh64(FRA_TUN_ID) */ + guint32 table; /* (struct fib_rule_hdr).table, FRA_TABLE */ + guint32 flags; /* (struct fib_rule_hdr).flags */ + guint32 priority; /* RA_PRIORITY */ + guint32 fwmark; /* FRA_FWMARK */ + guint32 fwmask; /* FRA_FWMASK */ + guint32 goto_target; /* FRA_GOTO */ + guint32 flow; /* FRA_FLOW */ + guint32 suppress_prefixlen_inverse; /* ~(FRA_SUPPRESS_PREFIXLEN) */ + guint32 suppress_ifgroup_inverse; /* ~(FRA_SUPPRESS_IFGROUP) */ + NMFibRuleUidRange uid_range; /* FRA_UID_RANGE */ + NMFibRulePortRange sport_range; /* FRA_SPORT_RANGE */ + NMFibRulePortRange dport_range; /* FRA_DPORT_RANGE */ + char iifname[NMP_IFNAMSIZ]; /* FRA_IIFNAME */ + char oifname[NMP_IFNAMSIZ]; /* FRA_OIFNAME */ + guint8 addr_family; /* (struct fib_rule_hdr).family */ + guint8 action; /* (struct fib_rule_hdr).action */ + guint8 tos; /* (struct fib_rule_hdr).tos */ + guint8 src_len; /* (struct fib_rule_hdr).src_len */ + guint8 dst_len; /* (struct fib_rule_hdr).dst_len */ + guint8 l3mdev; /* FRA_L3MDEV */ + guint8 protocol; /* FRA_PROTOCOL */ + guint8 ip_proto; /* FRA_IP_PROTO */ + + bool uid_range_has : 1; /* has(FRA_UID_RANGE) */ +} NMPlatformRoutingRule; + +#define NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET (~((guint32) 0)) + +#define NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED ((guint32) 0x83126E97u) + +G_STATIC_ASSERT(((((guint64) NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) * 1000u) >> 10) + == (guint64) INT_MAX); + +typedef struct { + guint32 limit; + guint32 flows; + guint32 target; + guint32 interval; + guint32 quantum; + + /* TCA_FQ_CODEL_CE_THRESHOLD: kernel internally stores this value as + * ((val64 * NSEC_PER_USEC) >> CODEL_SHIFT). The default value (in + * the domain with this coercion) is CODEL_DISABLED_THRESHOLD (INT_MAX). + * That means, "disabled" is expressed on RTM_NEWQDISC netlink API by absence of the + * netlink attribute but also as the special value 0x83126E97u + * (NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED). + * Beware: zero is not the default you must always explicitly set this value. */ + guint32 ce_threshold; + + /* TCA_FQ_CODEL_MEMORY_LIMIT: note that only values <= 2^31 are accepted by kernel + * and kernel defaults to 32MB. + * Note that we use the special value NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET + * to indicate that no explicit limit is set (when we send a RTM_NEWQDISC request). + * This will cause kernel to choose the default (32MB). + * Beware: zero is not the default you must always explicitly set this value. */ + guint32 memory_limit; + + bool ecn : 1; +} NMPlatformQdiscFqCodel; + +typedef struct { + unsigned quantum; + int perturb_period; + guint32 limit; + unsigned divisor; + unsigned flows; + unsigned depth; +} NMPlatformQdiscSfq; + +typedef struct { + guint64 rate; + guint32 burst; + guint32 limit; + guint32 latency; +} NMPlatformQdiscTbf; + +typedef struct { + __NMPlatformObjWithIfindex_COMMON; + + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + int addr_family; + guint32 handle; + guint32 parent; + guint32 info; + union { + NMPlatformQdiscFqCodel fq_codel; + NMPlatformQdiscSfq sfq; + NMPlatformQdiscTbf tbf; + }; +} NMPlatformQdisc; + +typedef struct { + char sdata[32]; +} NMPlatformActionSimple; + +typedef struct { + int ifindex; + bool egress : 1; + bool ingress : 1; + bool mirror : 1; + bool redirect : 1; +} NMPlatformActionMirred; + +typedef struct { + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + union { + NMPlatformActionSimple simple; + NMPlatformActionMirred mirred; + }; +} NMPlatformAction; + +#define NM_PLATFORM_ACTION_KIND_SIMPLE "simple" +#define NM_PLATFORM_ACTION_KIND_MIRRED "mirred" + +typedef struct { + __NMPlatformObjWithIfindex_COMMON; + + /* beware, kind is embedded in an NMPObject, hence you must + * take care of the lifetime of the string. */ + const char *kind; + + int addr_family; + guint32 handle; + guint32 parent; + guint32 info; + NMPlatformAction action; +} NMPlatformTfilter; + +#undef __NMPlatformObjWithIfindex_COMMON + +typedef struct { + gboolean is_ip4; + NMPObjectType obj_type; + int addr_family; + gsize sizeof_route; + int (*route_cmp)(const NMPlatformIPXRoute *a, + const NMPlatformIPXRoute *b, + NMPlatformIPRouteCmpType cmp_type); + const char *(*route_to_string)(const NMPlatformIPXRoute *route, char *buf, gsize len); +} NMPlatformVTableRoute; + +typedef union { + struct { + NMPlatformVTableRoute v6; + NMPlatformVTableRoute v4; + }; + NMPlatformVTableRoute vx[2]; +} _NMPlatformVTableRouteUnion; + +extern const _NMPlatformVTableRouteUnion nm_platform_vtable_route; + +typedef struct { + guint16 id; + guint32 qos; + bool proto_ad : 1; +} NMPlatformVFVlan; + +typedef struct { + guint32 index; + guint32 min_tx_rate; + guint32 max_tx_rate; + guint num_vlans; + NMPlatformVFVlan *vlans; + struct { + guint8 data[20]; /* _NM_UTILS_HWADDR_LEN_MAX */ + guint8 len; + } mac; + gint8 spoofchk; + gint8 trust; +} NMPlatformVF; + +typedef struct { + guint16 vid_start; + guint16 vid_end; + bool untagged : 1; + bool pvid : 1; +} NMPlatformBridgeVlan; + +typedef struct { + NMEtherAddr group_addr; + bool mcast_querier : 1; + bool mcast_query_use_ifaddr : 1; + bool mcast_snooping : 1; + bool stp_state : 1; + bool vlan_stats_enabled : 1; + guint16 group_fwd_mask; + guint16 priority; + guint16 vlan_protocol; + guint32 ageing_time; + guint32 forward_delay; + guint32 hello_time; + guint32 max_age; + guint32 mcast_last_member_count; + guint32 mcast_startup_query_count; + guint32 mcast_hash_max; + guint64 mcast_last_member_interval; + guint64 mcast_membership_interval; + guint64 mcast_querier_interval; + guint64 mcast_query_interval; + guint64 mcast_query_response_interval; + guint64 mcast_startup_query_interval; + guint8 mcast_router; +} NMPlatformLnkBridge; + +extern const NMPlatformLnkBridge nm_platform_lnk_bridge_default; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint16 input_flags; + guint16 output_flags; + guint32 input_key; + guint32 output_key; + guint8 ttl; + guint8 tos; + bool path_mtu_discovery : 1; + bool is_tap : 1; +} NMPlatformLnkGre; + +typedef struct { + int p_key; + const char *mode; +} NMPlatformLnkInfiniband; + +typedef struct { + struct in6_addr local; + struct in6_addr remote; + int parent_ifindex; + guint8 ttl; + guint8 tclass; + guint8 encap_limit; + guint8 proto; + guint flow_label; + guint32 flags; + + /* IP6GRE only */ + guint32 input_key; + guint32 output_key; + guint16 input_flags; + guint16 output_flags; + bool is_tap : 1; + bool is_gre : 1; +} NMPlatformLnkIp6Tnl; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint8 ttl; + guint8 tos; + bool path_mtu_discovery : 1; +} NMPlatformLnkIpIp; + +typedef struct { + int parent_ifindex; + guint64 sci; /* host byte order */ + guint64 cipher_suite; + guint32 window; + guint8 icv_length; + guint8 encoding_sa; + guint8 validation; + bool encrypt : 1; + bool protect : 1; + bool include_sci : 1; + bool es : 1; + bool scb : 1; + bool replay_protect : 1; +} NMPlatformLnkMacsec; + +typedef struct { + guint mode; + bool no_promisc : 1; + bool tap : 1; +} NMPlatformLnkMacvlan; + +typedef struct { + in_addr_t local; + in_addr_t remote; + int parent_ifindex; + guint16 flags; + guint8 ttl; + guint8 tos; + guint8 proto; + bool path_mtu_discovery : 1; +} NMPlatformLnkSit; + +typedef struct { + guint32 owner; + guint32 group; + + guint8 type; + + bool owner_valid : 1; + bool group_valid : 1; + + bool pi : 1; + bool vnet_hdr : 1; + bool multi_queue : 1; + bool persist : 1; +} NMPlatformLnkTun; + +typedef struct { + /* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */ + guint16 id; + _NMVlanFlags flags; +} NMPlatformLnkVlan; + +typedef struct { + guint32 table; +} NMPlatformLnkVrf; + +typedef struct { + struct in6_addr group6; + struct in6_addr local6; + in_addr_t group; + in_addr_t local; + int parent_ifindex; + guint32 id; + guint32 ageing; + guint32 limit; + guint16 dst_port; + guint16 src_port_min; + guint16 src_port_max; + guint8 tos; + guint8 ttl; + bool learning : 1; + bool proxy : 1; + bool rsc : 1; + bool l2miss : 1; + bool l3miss : 1; +} NMPlatformLnkVxlan; + +#define NMP_WIREGUARD_PUBLIC_KEY_LEN 32 +#define NMP_WIREGUARD_SYMMETRIC_KEY_LEN 32 + +typedef struct { + guint32 fwmark; + guint16 listen_port; + guint8 private_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; + guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; +} NMPlatformLnkWireGuard; + +typedef enum { + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE = 0, + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS = (1LL << 0), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY = (1LL << 1), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT = (1LL << 2), + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK = (1LL << 3), +} NMPlatformWireGuardChangeFlags; + +typedef enum { + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE = 0, + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME = (1LL << 0), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY = (1LL << 1), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL = (1LL << 2), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT = (1LL << 3), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS = (1LL << 4), + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS = (1LL << 5), + + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT = + NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT + | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS, + +} NMPlatformWireGuardChangePeerFlags; + +typedef void (*NMPlatformAsyncCallback)(GError *error, gpointer user_data); + +/*****************************************************************************/ + +typedef enum { + NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL, + NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED, + + /* this also includes FRA_SPORT_RANGE and FRA_DPORT_RANGE which + * were added at the same time. */ + NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, + + _NM_PLATFORM_KERNEL_SUPPORT_NUM, +} NMPlatformKernelSupportType; + +extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM]; + +int _nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value); + +static inline gboolean +_nm_platform_kernel_support_detected(NMPlatformKernelSupportType type) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + return G_LIKELY(g_atomic_int_get(&_nm_platform_kernel_support_state[type]) != 0); +} + +static inline NMOptionBool +nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean init_if_not_set) +{ + int v; + + nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state)); + + v = g_atomic_int_get(&_nm_platform_kernel_support_state[type]); + if (G_UNLIKELY(v == 0)) { + if (!init_if_not_set) + return NM_OPTION_BOOL_DEFAULT; + v = _nm_platform_kernel_support_init(type, 0); + } + return (v >= 0); +} + +static inline gboolean +nm_platform_kernel_support_get(NMPlatformKernelSupportType type) +{ + return nm_platform_kernel_support_get_full(type, TRUE) != NM_OPTION_BOOL_FALSE; +} + +/*****************************************************************************/ + +struct _NMPlatformPrivate; + +struct _NMPlatform { + GObject parent; + NMPNetns * _netns; + struct _NMPlatformPrivate *_priv; +}; + +typedef struct { + GObjectClass parent; + + gboolean (*sysctl_set)(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value); + void (*sysctl_set_async)(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable); + char *(*sysctl_get)(NMPlatform *self, const char *pathid, int dirfd, const char *path); + + void (*refresh_all)(NMPlatform *self, NMPObjectType obj_type); + void (*process_events)(NMPlatform *self); + + int (*link_add)(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link); + gboolean (*link_delete)(NMPlatform *self, int ifindex); + gboolean (*link_refresh)(NMPlatform *self, int ifindex); + gboolean (*link_set_netns)(NMPlatform *self, int ifindex, int netns_fd); + int (*link_change_flags)(NMPlatform *platform, + int ifindex, + unsigned flags_mask, + unsigned flags_set); + + int (*link_set_user_ipv6ll_enabled)(NMPlatform *self, int ifindex, gboolean enabled); + gboolean (*link_set_token)(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid); + + gboolean (*link_get_permanent_address)(NMPlatform *self, + int ifindex, + guint8 * buf, + size_t * length); + int (*link_set_address)(NMPlatform *self, int ifindex, gconstpointer address, size_t length); + int (*link_set_mtu)(NMPlatform *self, int ifindex, guint32 mtu); + gboolean (*link_set_name)(NMPlatform *self, int ifindex, const char *name); + void (*link_set_sriov_params_async)(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable); + gboolean (*link_set_sriov_vfs)(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); + gboolean (*link_set_bridge_vlans)(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans); + + char *(*link_get_physical_port_id)(NMPlatform *self, int ifindex); + guint (*link_get_dev_id)(NMPlatform *self, int ifindex); + gboolean (*link_get_wake_on_lan)(NMPlatform *self, int ifindex); + gboolean (*link_get_driver_info)(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version); + + gboolean (*link_supports_carrier_detect)(NMPlatform *self, int ifindex); + gboolean (*link_supports_vlans)(NMPlatform *self, int ifindex); + gboolean (*link_supports_sriov)(NMPlatform *self, int ifindex); + + gboolean (*link_enslave)(NMPlatform *self, int master, int slave); + gboolean (*link_release)(NMPlatform *self, int master, int slave); + + gboolean (*link_can_assume)(NMPlatform *self, int ifindex); + + int (*link_wireguard_change)(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const struct _NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags); + + gboolean (*link_vlan_change)(NMPlatform * self, + int ifindex, + _NMVlanFlags flags_mask, + _NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map); + gboolean (*link_tun_add)(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd); + + gboolean (*infiniband_partition_add)(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link); + gboolean (*infiniband_partition_delete)(NMPlatform *self, int parent, int p_key); + + gboolean (*wifi_get_capabilities)(NMPlatform * self, + int ifindex, + _NMDeviceWifiCapabilities *caps); + gboolean (*wifi_get_station)(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + gboolean (*wifi_get_bssid)(NMPlatform *self, int ifindex, guint8 *bssid); + guint32 (*wifi_get_frequency)(NMPlatform *self, int ifindex); + int (*wifi_get_quality)(NMPlatform *self, int ifindex); + guint32 (*wifi_get_rate)(NMPlatform *self, int ifindex); + _NM80211Mode (*wifi_get_mode)(NMPlatform *self, int ifindex); + void (*wifi_set_mode)(NMPlatform *self, int ifindex, _NM80211Mode mode); + void (*wifi_set_powersave)(NMPlatform *self, int ifindex, guint32 powersave); + guint32 (*wifi_find_frequency)(NMPlatform *self, int ifindex, const guint32 *freqs); + void (*wifi_indicate_addressing_running)(NMPlatform *self, int ifindex, gboolean running); + _NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan)(NMPlatform *self, int ifindex); + gboolean (*wifi_set_wake_on_wlan)(NMPlatform * self, + int ifindex, + _NMSettingWirelessWakeOnWLan wowl); + + guint32 (*mesh_get_channel)(NMPlatform *self, int ifindex); + gboolean (*mesh_set_channel)(NMPlatform *self, int ifindex, guint32 channel); + gboolean (*mesh_set_ssid)(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len); + + guint16 (*wpan_get_pan_id)(NMPlatform *self, int ifindex); + gboolean (*wpan_set_pan_id)(NMPlatform *self, int ifindex, guint16 pan_id); + guint16 (*wpan_get_short_addr)(NMPlatform *self, int ifindex); + gboolean (*wpan_set_short_addr)(NMPlatform *self, int ifindex, guint16 short_addr); + gboolean (*wpan_set_channel)(NMPlatform *self, int ifindex, guint8 page, guint8 channel); + + gboolean (*object_delete)(NMPlatform *self, const NMPObject *obj); + + gboolean (*ip4_address_add)(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags, + const char *label); + gboolean (*ip6_address_add)(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags); + gboolean (*ip4_address_delete)(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); + gboolean (*ip6_address_delete)(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen); + + int (*ip_route_add)(NMPlatform * self, + NMPNlmFlags flags, + int addr_family, + const NMPlatformIPRoute *route); + int (*ip_route_get)(NMPlatform * self, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route); + + int (*routing_rule_add)(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule); + + int (*qdisc_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc); + + int (*tfilter_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter); +} NMPlatformClass; + +/* NMPlatform signals + * + * Each signal handler is called with a type-specific object that provides + * key attributes that constitute identity of the object. They may also + * provide additional attributes for convenience. + * + * The object only intended to be used by the signal handler to determine + * the current values. It is no longer valid after the signal handler exits + * but you are free to copy the provided information and use it for later + * reference. + */ +#define NM_PLATFORM_SIGNAL_LINK_CHANGED "link-changed" +#define NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED "ip4-address-changed" +#define NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED "ip6-address-changed" +#define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED "ip4-route-changed" +#define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED "ip6-route-changed" +#define NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED "routing-rule-changed" +#define NM_PLATFORM_SIGNAL_QDISC_CHANGED "qdisc-changed" +#define NM_PLATFORM_SIGNAL_TFILTER_CHANGED "tfilter-changed" + +const char *nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type); + +/*****************************************************************************/ + +GType nm_platform_get_type(void); + +/*****************************************************************************/ + +static inline in_addr_t +nm_platform_ip4_broadcast_address_create(in_addr_t address, guint8 plen) +{ + return address | ~_nm_utils_ip4_prefix_to_netmask(plen); +} + +static inline in_addr_t +nm_platform_ip4_broadcast_address_from_addr(const NMPlatformIP4Address *addr) +{ + nm_assert(addr); + + if (addr->use_ip4_broadcast_address) + return addr->broadcast_address; + + /* the set broadcast-address gets ignored, and we determine a default brd base + * on the peer IFA_ADDRESS. */ + if (addr->peer_address != 0u && addr->plen < 31 /* RFC3021 */) + return nm_platform_ip4_broadcast_address_create(addr->peer_address, addr->plen); + return 0u; +} + +/*****************************************************************************/ + +/** + * nm_platform_route_table_coerce: + * @table: the route table, in its original value as received + * from rtm_table/RTA_TABLE. + * + * Returns: returns the coerced table id, that can be stored in + * NMPlatformIPRoute.table_coerced. + */ +static inline guint32 +nm_platform_route_table_coerce(guint32 table) +{ + /* For kernel, the default table is RT_TABLE_MAIN (254). + * We want that in NMPlatformIPRoute.table_coerced a numeric + * zero is the default. Hence, @table_coerced swaps the + * value 0 and 254. Use nm_platform_route_table_coerce() + * and nm_platform_route_table_uncoerce() to convert between + * the two domains. */ + switch (table) { + case 0 /* RT_TABLE_UNSPEC */: + return 254; + case 254 /* RT_TABLE_MAIN */: + return 0; + default: + return table; + } +} + +/** + * nm_platform_route_table_uncoerce: + * @table_coerced: the route table, in its coerced value + * @normalize: whether to normalize RT_TABLE_UNSPEC to + * RT_TABLE_MAIN. For kernel, routes with a table id + * RT_TABLE_UNSPEC do not exist and are treated like + * RT_TABLE_MAIN. + * + * Returns: reverts the coerced table ID in NMPlatformIPRoute.table_coerced + * to the original value as kernel understands it. + */ +static inline guint32 +nm_platform_route_table_uncoerce(guint32 table_coerced, gboolean normalize) +{ + /* this undoes nm_platform_route_table_coerce(). */ + switch (table_coerced) { + case 0 /* RT_TABLE_UNSPEC */: + return 254; + case 254 /* RT_TABLE_MAIN */: + return normalize ? 254 : 0; + default: + return table_coerced; + } +} + +static inline gboolean +nm_platform_route_table_is_main(guint32 table) +{ + /* same as + * nm_platform_route_table_uncoerce (table, TRUE) == RT_TABLE_MAIN + * and + * nm_platform_route_table_uncoerce (nm_platform_route_table_coerce (table), TRUE) == RT_TABLE_MAIN + * + * That is, the function operates the same on @table and its coerced + * form. + */ + return table == 0 || table == 254; +} + +/** + * nm_platform_route_scope_inv: + * @scope: the route scope, either its original value, or its inverse. + * + * This function is useful, because the constants such as RT_SCOPE_NOWHERE + * are 'int', so ~scope also gives an 'int'. This function gets the type + * casts to guint8 right. + * + * Returns: the bitwise inverse of the route scope. + * */ +#define nm_platform_route_scope_inv _nm_platform_uint8_inv +static inline guint8 +_nm_platform_uint8_inv(guint8 scope) +{ + return (guint8) ~scope; +} + +/** + * nm_platform_route_type_coerce: + * @table: the route type, in its original value. + * + * Returns: returns the coerced type, that can be stored in + * NMPlatformIPRoute.type_coerced. + */ +static inline guint8 +nm_platform_route_type_coerce(guint8 type) +{ + switch (type) { + case 0 /* RTN_UNSPEC */: + return 1; + case 1 /* RTN_UNICAST */: + return 0; + default: + return type; + } +} + +/** + * nm_platform_route_type_uncoerce: + * @table: the type table, in its coerced value + * + * Returns: reverts the coerced type in NMPlatformIPRoute.type_coerced + * to the original value as kernel understands it. + */ +static inline guint8 +nm_platform_route_type_uncoerce(guint8 type_coerced) +{ + return nm_platform_route_type_coerce(type_coerced); +} + +gboolean nm_platform_get_use_udev(NMPlatform *self); +gboolean nm_platform_get_log_with_ptr(NMPlatform *self); + +NMPNetns *nm_platform_netns_get(NMPlatform *self); +gboolean nm_platform_netns_push(NMPlatform *self, NMPNetns **netns); + +const char *nm_link_type_to_string(NMLinkType link_type); + +#define NMP_SYSCTL_PATHID_ABSOLUTE(path) ((const char *) NULL), -1, (path) + +#define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path) \ + nm_sprintf_buf_unsafe_a(NM_STRLEN("net:/sys/class/net//\0") + NMP_IFNAMSIZ + ({ \ + const gsize _l = strlen(path); \ + \ + nm_assert(_l < 200); \ + _l; \ + }), \ + "net:/sys/class/net/%s/%s", \ + (ifname), \ + (path)), \ + (dirfd), (path) + +#define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path) \ + nm_sprintf_bufa(NM_STRLEN("net:/sys/class/net//" path "/\0") + NMP_IFNAMSIZ, \ + "net:/sys/class/net/%s/%s", \ + (ifname), \ + path), \ + (dirfd), ("" path "") + +int nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname); +gboolean nm_platform_sysctl_set(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + const char *value); +void nm_platform_sysctl_set_async(NMPlatform * self, + const char * pathid, + int dirfd, + const char * path, + const char *const * values, + NMPlatformAsyncCallback callback, + gpointer data, + GCancellable * cancellable); +char * nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path); +gint32 nm_platform_sysctl_get_int32(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + gint32 fallback); +gint64 nm_platform_sysctl_get_int_checked(NMPlatform *self, + const char *pathid, + int dirfd, + const char *path, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +char *nm_platform_sysctl_ip_conf_get(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property); + +gint64 nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +gboolean nm_platform_sysctl_ip_conf_set(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + const char *value); + +gboolean nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self, + int addr_family, + const char *ifname, + const char *property, + gint64 value); + +gboolean +nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value); +gboolean nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self, + const char *iface, + guint value_ms); +gboolean nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self, + const char *iface, + guint value_ms); +int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *platform, + const char *iface, + gboolean consider_all, + gboolean * out_due_to_all); + +const char *nm_platform_if_indextoname(NMPlatform *self, + int ifindex, + char out_ifname[static 16 /* IFNAMSIZ */]); +int nm_platform_if_nametoindex(NMPlatform *self, const char *ifname); + +const NMPObject *nm_platform_link_get_obj(NMPlatform *self, int ifindex, gboolean visible_only); +const NMPlatformLink *nm_platform_link_get(NMPlatform *self, int ifindex); +const NMPlatformLink *nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname); +const NMPlatformLink *nm_platform_link_get_by_address(NMPlatform * self, + NMLinkType link_type, + gconstpointer address, + size_t length); + +GPtrArray *nm_platform_link_get_all(NMPlatform *self, gboolean sort_by_name); + +int nm_platform_link_add(NMPlatform * self, + NMLinkType type, + const char * name, + int parent, + const void * address, + size_t address_len, + guint32 mtu, + gconstpointer extra_data, + const NMPlatformLink **out_link); + +static inline int +nm_platform_link_veth_add(NMPlatform * self, + const char * name, + const char * peer, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, 0, peer, out_link); +} + +static inline int +nm_platform_link_dummy_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_bridge_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + guint32 mtu, + const NMPlatformLnkBridge *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, + NM_LINK_TYPE_BRIDGE, + name, + 0, + address, + address_len, + mtu, + props, + out_link); +} + +static inline int +nm_platform_link_bond_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_team_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, 0, NULL, out_link); +} + +static inline int +nm_platform_link_gre_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + const NMPlatformLnkGre *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + + return nm_platform_link_add(self, + props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, + name, + 0, + address, + address_len, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_sit_add(NMPlatform * self, + const char * name, + const NMPlatformLnkSit *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_vlan_add(NMPlatform * self, + const char * name, + int parent, + int vlanid, + guint32 vlanflags, + const NMPlatformLink **out_link) +{ + g_return_val_if_fail(parent >= 0, -NME_BUG); + g_return_val_if_fail(vlanid >= 0, -NME_BUG); + + return nm_platform_link_add(self, + NM_LINK_TYPE_VLAN, + name, + parent, + NULL, + 0, + 0, + &((NMPlatformLnkVlan){ + .id = vlanid, + .flags = vlanflags, + }), + out_link); +} + +static inline int +nm_platform_link_vrf_add(NMPlatform * self, + const char * name, + const NMPlatformLnkVrf *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_vxlan_add(NMPlatform * self, + const char * name, + const NMPlatformLnkVxlan *props, + const NMPlatformLink ** out_link) +{ + return nm_platform_link_add(self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_6lowpan_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLink **out_link) +{ + return nm_platform_link_add(self, + NM_LINK_TYPE_6LOWPAN, + name, + parent, + NULL, + 0, + 0, + NULL, + out_link); +} + +static inline int +nm_platform_link_ip6tnl_add(NMPlatform * self, + const char * name, + const NMPlatformLnkIp6Tnl *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(!props->is_gre, -NME_BUG); + + return nm_platform_link_add(self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_ip6gre_add(NMPlatform * self, + const char * name, + const void * address, + size_t address_len, + const NMPlatformLnkIp6Tnl *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(props->is_gre, -NME_BUG); + + return nm_platform_link_add(self, + props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE, + name, + 0, + address, + address_len, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_ipip_add(NMPlatform * self, + const char * name, + const NMPlatformLnkIpIp *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + + return nm_platform_link_add(self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, 0, props, out_link); +} + +static inline int +nm_platform_link_macsec_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLnkMacsec *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(parent > 0, -NME_BUG); + + return nm_platform_link_add(self, + NM_LINK_TYPE_MACSEC, + name, + parent, + NULL, + 0, + 0, + props, + out_link); +} + +static inline int +nm_platform_link_macvlan_add(NMPlatform * self, + const char * name, + int parent, + const NMPlatformLnkMacvlan *props, + const NMPlatformLink ** out_link) +{ + g_return_val_if_fail(props, -NME_BUG); + g_return_val_if_fail(parent > 0, -NME_BUG); + + return nm_platform_link_add(self, + props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN, + name, + parent, + NULL, + 0, + 0, + props, + out_link); +} + +gboolean nm_platform_link_delete(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd); + +struct _NMDedupMultiHeadEntry; +struct _NMPLookup; +const struct _NMDedupMultiHeadEntry *nm_platform_lookup(NMPlatform * self, + const struct _NMPLookup *lookup); + +#define nm_platform_iter_obj_for_each(iter, self, lookup, obj) \ + for (nm_dedup_multi_iter_init((iter), nm_platform_lookup((self), (lookup))); \ + nm_platform_dedup_multi_iter_next_obj((iter), (obj), NMP_OBJECT_TYPE_UNKNOWN);) + +gboolean nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data); +gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj, + gpointer user_data); + +GPtrArray *nm_platform_lookup_clone(NMPlatform * self, + const struct _NMPLookup *lookup, + NMPObjectPredicateFunc predicate, + gpointer user_data); + +/* convenience methods to lookup the link and access fields of NMPlatformLink. */ +int nm_platform_link_get_ifindex(NMPlatform *self, const char *name); +const char *nm_platform_link_get_name(NMPlatform *self, int ifindex); +NMLinkType nm_platform_link_get_type(NMPlatform *self, int ifindex); +gboolean nm_platform_link_is_software(NMPlatform *self, int ifindex); +int nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags); +gboolean nm_platform_link_is_up(NMPlatform *self, int ifindex); +gboolean nm_platform_link_is_connected(NMPlatform *self, int ifindex); +gboolean nm_platform_link_uses_arp(NMPlatform *self, int ifindex); +guint32 nm_platform_link_get_mtu(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex); + +gconstpointer nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length); + +int nm_platform_link_get_master(NMPlatform *self, int slave); + +gboolean nm_platform_link_can_assume(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged); +gboolean nm_platform_link_supports_slaves(NMPlatform *self, int ifindex); +const char *nm_platform_link_get_type_name(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_refresh(NMPlatform *self, int ifindex); +void nm_platform_process_events(NMPlatform *self); + +const NMPlatformLink * +nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname); + +int nm_platform_link_change_flags_full(NMPlatform *self, + int ifindex, + unsigned flags_mask, + unsigned flags_set); + +/** + * nm_platform_link_change_flags: + * @self: platform instance + * @ifindex: interface index + * @value: flag to be set + * @set: value to be set + * + * Change the interface flag to the value set. + * + * Returns: nm-errno code. + * + */ +static inline int +nm_platform_link_change_flags(NMPlatform *self, int ifindex, unsigned value, gboolean set) +{ + return nm_platform_link_change_flags_full(self, ifindex, value, set ? value : 0u); +} + +gboolean nm_platform_link_get_udev_property(NMPlatform * self, + int ifindex, + const char * name, + const char **out_value); +const char *nm_platform_link_get_udi(NMPlatform *self, int ifindex); +const char *nm_platform_link_get_path(NMPlatform *self, int ifindex); + +struct udev_device *nm_platform_link_get_udev_device(NMPlatform *self, int ifindex); + +int nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled); +gboolean nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid); + +gboolean +nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length); +int nm_platform_link_set_address(NMPlatform *self, int ifindex, const void *address, size_t length); +int nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu); +gboolean nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name); + +void nm_platform_link_set_sriov_params_async(NMPlatform * self, + int ifindex, + guint num_vfs, + NMOptionBool autoprobe, + NMPlatformAsyncCallback callback, + gpointer callback_data, + GCancellable * cancellable); + +gboolean +nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); +gboolean nm_platform_link_set_bridge_vlans(NMPlatform * self, + int ifindex, + gboolean on_master, + const NMPlatformBridgeVlan *const *vlans); + +char * nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex); +guint nm_platform_link_get_dev_id(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex); +gboolean nm_platform_link_get_driver_info(NMPlatform *self, + int ifindex, + char ** out_driver_name, + char ** out_driver_version, + char ** out_fw_version); + +gboolean nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex); +gboolean nm_platform_link_supports_vlans(NMPlatform *self, int ifindex); +gboolean nm_platform_link_supports_sriov(NMPlatform *self, int ifindex); + +gboolean nm_platform_link_enslave(NMPlatform *self, int master, int slave); +gboolean nm_platform_link_release(NMPlatform *self, int master, int slave); + +gboolean nm_platform_sysctl_master_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value); +char * nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option); +gboolean nm_platform_sysctl_slave_set_option(NMPlatform *self, + int ifindex, + const char *option, + const char *value); +char * nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option); + +const NMPObject *nm_platform_link_get_lnk(NMPlatform * self, + int ifindex, + NMLinkType link_type, + const NMPlatformLink **out_link); +const NMPlatformLnkBridge * +nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkGre * +nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIp6Tnl * +nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkInfiniband * +nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkIpIp * +nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacsec * +nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkMacvlan * +nm_platform_link_get_lnk_macvtap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkSit * +nm_platform_link_get_lnk_sit(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkTun * +nm_platform_link_get_lnk_tun(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVlan * +nm_platform_link_get_lnk_vlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVrf * +nm_platform_link_get_lnk_vrf(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkVxlan * +nm_platform_link_get_lnk_vxlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkWireGuard * +nm_platform_link_get_lnk_wireguard(NMPlatform *self, int ifindex, const NMPlatformLink **out_link); + +gboolean nm_platform_link_vlan_set_ingress_map(NMPlatform *self, int ifindex, int from, int to); +gboolean nm_platform_link_vlan_set_egress_map(NMPlatform *self, int ifindex, int from, int to); +gboolean nm_platform_link_vlan_change(NMPlatform * self, + int ifindex, + _NMVlanFlags flags_mask, + _NMVlanFlags flags_set, + gboolean ingress_reset_all, + const NMVlanQosMapping *ingress_map, + gsize n_ingress_map, + gboolean egress_reset_all, + const NMVlanQosMapping *egress_map, + gsize n_egress_map); + +int nm_platform_link_infiniband_add(NMPlatform * self, + int parent, + int p_key, + const NMPlatformLink **out_link); +int nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key); +gboolean nm_platform_link_infiniband_get_properties(NMPlatform * self, + int ifindex, + int * parent, + int * p_key, + const char **mode); + +gboolean nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex); +gboolean nm_platform_link_tun_get_properties(NMPlatform * self, + int ifindex, + NMPlatformLnkTun *out_properties); + +gboolean +nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, _NMDeviceWifiCapabilities *caps); +guint32 nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex); +gboolean nm_platform_wifi_get_station(NMPlatform * self, + int ifindex, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); +_NM80211Mode nm_platform_wifi_get_mode(NMPlatform *self, int ifindex); +void nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, _NM80211Mode mode); +void nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave); +guint32 nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs); +void nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running); +_NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex); +gboolean +nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, _NMSettingWirelessWakeOnWLan wowl); + +guint32 nm_platform_mesh_get_channel(NMPlatform *self, int ifindex); +gboolean nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel); +gboolean nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len); + +guint16 nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex); +gboolean nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id); +guint16 nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex); +gboolean nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr); +gboolean nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel); + +void nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen); +const struct in6_addr *nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr); + +const NMPlatformIP4Address *nm_platform_ip4_address_get(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); + +int nm_platform_link_sit_add(NMPlatform * self, + const char * name, + const NMPlatformLnkSit *props, + const NMPlatformLink ** out_link); +int nm_platform_link_tun_add(NMPlatform * self, + const char * name, + const NMPlatformLnkTun *props, + const NMPlatformLink ** out_link, + int * out_fd); +gboolean nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent); + +int +nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link); + +int nm_platform_link_wireguard_change(NMPlatform * self, + int ifindex, + const NMPlatformLnkWireGuard * lnk_wireguard, + const struct _NMPWireGuardPeer * peers, + const NMPlatformWireGuardChangePeerFlags *peer_flags, + guint peers_len, + NMPlatformWireGuardChangeFlags change_flags); + +const NMPlatformIP6Address * +nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address); + +gboolean nm_platform_object_delete(NMPlatform *self, const NMPObject *route); + +gboolean nm_platform_ip4_address_add(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address, + in_addr_t broadcast_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags, + const char *label); +gboolean nm_platform_ip6_address_add(NMPlatform * self, + int ifindex, + struct in6_addr address, + guint8 plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred_lft, + guint32 flags); +gboolean nm_platform_ip4_address_delete(NMPlatform *self, + int ifindex, + in_addr_t address, + guint8 plen, + in_addr_t peer_address); +gboolean +nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen); + +gboolean nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + GPtrArray * addresses_prune); + +GPtrArray *nm_platform_ip_address_get_prune_list(NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs); + +static inline gboolean +_nm_platform_ip_address_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * known_addresses, + gboolean full_sync) +{ + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + + addresses_prune = nm_platform_ip_address_get_prune_list(self, addr_family, ifindex, !full_sync); + return nm_platform_ip_address_sync(self, + addr_family, + ifindex, + known_addresses, + addresses_prune); +} + +static inline gboolean +nm_platform_ip4_address_sync(NMPlatform *self, int ifindex, GPtrArray *known_addresses) +{ + return _nm_platform_ip_address_sync(self, AF_INET, ifindex, known_addresses, TRUE); +} + +static inline gboolean +nm_platform_ip6_address_sync(NMPlatform *self, + int ifindex, + GPtrArray * known_addresses, + gboolean full_sync) +{ + return _nm_platform_ip_address_sync(self, AF_INET6, ifindex, known_addresses, full_sync); +} + +gboolean nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex); + +static inline gconstpointer +nm_platform_ip_address_get_peer_address(int addr_family, const NMPlatformIPAddress *addr) +{ + nm_assert_addr_family(addr_family); + nm_assert(addr); + + if (NM_IS_IPv4(addr_family)) + return &((NMPlatformIP4Address *) addr)->peer_address; + return &((NMPlatformIP6Address *) addr)->peer_address; +} + +void nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route); + +static inline guint32 +nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r) +{ + nm_assert(r); + + return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, r->metric) + : r->metric; +} + +static inline guint32 +nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r) +{ + nm_assert(r); + + return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, r->metric) + : r->metric; +} + +static inline guint32 +nm_platform_ip_route_get_effective_table(const NMPlatformIPRoute *r) +{ + nm_assert(r); + nm_assert(!r->table_any || r->table_coerced == 0); + + return r->table_any ? 254u /* RT_TABLE_MAIN */ + : nm_platform_route_table_uncoerce(r->table_coerced, TRUE); +} + +static inline gconstpointer +nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route) +{ + nm_assert_addr_family(addr_family); + nm_assert(route); + + if (NM_IS_IPv4(addr_family)) + return &((NMPlatformIP4Route *) route)->gateway; + return &((NMPlatformIP6Route *) route)->gateway; +} + +int nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route); +int nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route); +int nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route); + +GPtrArray *nm_platform_ip_route_get_prune_list(NMPlatform * self, + int addr_family, + int ifindex, + NMIPRouteTableSyncMode route_table_sync); + +gboolean nm_platform_ip_route_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray * routes, + GPtrArray * routes_prune, + GPtrArray **out_temporary_not_available); + +gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex); + +int nm_platform_ip_route_get(NMPlatform * self, + int addr_family, + gconstpointer address, + int oif_ifindex, + NMPObject ** out_route); + +int nm_platform_routing_rule_add(NMPlatform * self, + NMPNlmFlags flags, + const NMPlatformRoutingRule *routing_rule); + +int nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc); +gboolean nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs); + +int nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter); +gboolean nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters); + +const char *nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len); +const char *nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len); +const char *nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len); +const char *nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len); +const char *nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len); +const char *nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len); +const char *nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len); +const char *nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vrf_to_string(const NMPlatformLnkVrf *lnk, char *buf, gsize len); +const char *nm_platform_lnk_vxlan_to_string(const NMPlatformLnkVxlan *lnk, char *buf, gsize len); +const char * +nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len); +const char * +nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len); +const char * +nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len); +const char *nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len); +const char *nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len); +const char * +nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len); +const char *nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len); +const char *nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len); +const char *nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len); +const char * +nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len); + +const char *nm_platform_vlan_qos_mapping_to_string(const char * name, + const NMVlanQosMapping *map, + gsize n_map, + char * buf, + gsize len); + +const char * +nm_platform_wireguard_peer_to_string(const struct _NMPWireGuardPeer *peer, char *buf, gsize len); + +int nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b); +int nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b); +int nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b); +int nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, + const NMPlatformLnkInfiniband *b); +int nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b); +int nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b); +int nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b); +int nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b); +int nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b); +int nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b); +int nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b); +int nm_platform_lnk_vrf_cmp(const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b); +int nm_platform_lnk_vxlan_cmp(const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b); +int nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b); +int nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b); +int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b); + +int nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1, + const NMPlatformIP4Address *a2); + +int nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1, + const NMPlatformIP6Address *a2, + gboolean prefer_temp); + +GHashTable *nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex); + +int nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, + const NMPlatformIP4Route *b, + NMPlatformIPRouteCmpType cmp_type); +int nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, + const NMPlatformIP6Route *b, + NMPlatformIPRouteCmpType cmp_type); + +static inline int +nm_platform_ip4_route_cmp_full(const NMPlatformIP4Route *a, const NMPlatformIP4Route *b) +{ + return nm_platform_ip4_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL); +} + +static inline int +nm_platform_ip6_route_cmp_full(const NMPlatformIP6Route *a, const NMPlatformIP6Route *b) +{ + return nm_platform_ip6_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL); +} + +int nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a, + const NMPlatformRoutingRule *b, + NMPlatformRoutingRuleCmpType cmp_type); + +static inline int +nm_platform_routing_rule_cmp_full(const NMPlatformRoutingRule *a, const NMPlatformRoutingRule *b) +{ + return nm_platform_routing_rule_cmp(a, b, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL); +} + +int nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b); +int nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a, + const NMPlatformQdisc *b, + gboolean compare_handle); +int nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b); + +void nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h); +void nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h); +void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h); +void nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h); +void nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, + NMPlatformIPRouteCmpType cmp_type, + NMHashState * h); +void nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj, + NMPlatformRoutingRuleCmpType cmp_type, + NMHashState * h); +void nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h); +void nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h); +void nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h); +void nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h); +void nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h); +void nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h); +void nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h); +void nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h); +void nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h); +void nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h); +void nm_platform_lnk_vrf_hash_update(const NMPlatformLnkVrf *obj, NMHashState *h); +void nm_platform_lnk_vxlan_hash_update(const NMPlatformLnkVxlan *obj, NMHashState *h); +void nm_platform_lnk_wireguard_hash_update(const NMPlatformLnkWireGuard *obj, NMHashState *h); + +void nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h); +void nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h); + +#define NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN ((gsize) 162) + +const char *nm_platform_link_flags2str(unsigned flags, char *buf, gsize len); +const char *nm_platform_link_inet6_addrgenmode2str(guint8 mode, char *buf, gsize len); +const char *nm_platform_addr_flags2str(unsigned flags, char *buf, gsize len); +const char *nm_platform_route_scope2str(int scope, char *buf, gsize len); + +int nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b); + +gboolean nm_platform_ethtool_set_wake_on_lan(NMPlatform * self, + int ifindex, + _NMSettingWiredWakeOnLan wol, + const char * wol_password); +gboolean nm_platform_ethtool_set_link_settings(NMPlatform * self, + int ifindex, + gboolean autoneg, + guint32 speed, + NMPlatformLinkDuplexType duplex); +gboolean nm_platform_ethtool_get_link_settings(NMPlatform * self, + int ifindex, + gboolean * out_autoneg, + guint32 * out_speed, + NMPlatformLinkDuplexType *out_duplex); + +NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex); +gboolean nm_platform_ethtool_set_features( + NMPlatform * self, + int ifindex, + const NMEthtoolFeatureStates *features, + const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */, + gboolean do_set /* or reset */); + +gboolean nm_platform_ethtool_get_link_coalesce(NMPlatform * self, + int ifindex, + NMEthtoolCoalesceState *coalesce); + +gboolean nm_platform_ethtool_set_coalesce(NMPlatform * self, + int ifindex, + const NMEthtoolCoalesceState *coalesce); + +gboolean nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring); + +gboolean +nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring); + +gboolean +nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause); + +gboolean +nm_platform_ethtool_set_pause(NMPlatform *self, int ifindex, const NMEthtoolPauseState *pause); + +void nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self, + int ifindex, + GPtrArray * ip4_dev_route_blacklist); + +struct _NMDedupMultiIndex *nm_platform_get_multi_idx(NMPlatform *self); + +/*****************************************************************************/ + +gboolean nm_platform_ip_address_match(int addr_family, + const NMPlatformIPAddress *addr, + NMPlatformMatchFlags match_flag); + +#endif /* __NETWORKMANAGER_PLATFORM_H__ */ diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h new file mode 100644 index 00000000..a80fd4d3 --- /dev/null +++ b/src/libnm-platform/nmp-base.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __NMP_FWD_H__ +#define __NMP_FWD_H__ + +#include "libnm-base/nm-base.h" + +/*****************************************************************************/ + +#define NM_PLATFORM_LIFETIME_PERMANENT G_MAXUINT32 + +/*****************************************************************************/ + +typedef enum { + NM_PLATFORM_LINK_DUPLEX_UNKNOWN, + NM_PLATFORM_LINK_DUPLEX_HALF, + NM_PLATFORM_LINK_DUPLEX_FULL, +} NMPlatformLinkDuplexType; + +/*****************************************************************************/ + +typedef struct { + /* We don't want to include <linux/ethtool.h> in header files, + * thus create a ABI compatible version of struct ethtool_drvinfo.*/ + guint32 _private_cmd; + char driver[32]; + char version[32]; + char fw_version[32]; + char _private_bus_info[32]; + char _private_erom_version[32]; + char _private_reserved2[12]; + guint32 _private_n_priv_flags; + guint32 _private_n_stats; + guint32 _private_testinfo_len; + guint32 _private_eedump_len; + guint32 _private_regdump_len; +} NMPUtilsEthtoolDriverInfo; + +typedef struct { + NMEthtoolID ethtool_id; + + guint8 n_kernel_names; + + /* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity + * (where one NMSettingEthtool option refers to multiple kernel features) is to follow what + * ethtool does, where "tx" is an alias for multiple features. */ + const char *const *kernel_names; +} NMEthtoolFeatureInfo; + +typedef struct { + const NMEthtoolFeatureInfo *info; + + guint idx_ss_features; + + /* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index + * of the matching info->kernel_names */ + guint8 idx_kernel_name; + + bool available : 1; + bool requested : 1; + bool active : 1; + bool never_changed : 1; +} NMEthtoolFeatureState; + +typedef struct { + guint n_states; + + guint n_ss_features; + + /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */ + const NMEthtoolFeatureState *const *states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM]; + + /* the same content, here as a list of n_states entries. */ + const NMEthtoolFeatureState states_list[]; +} NMEthtoolFeatureStates; + +/*****************************************************************************/ + +typedef struct { + guint32 + s[_NM_ETHTOOL_ID_COALESCE_NUM /* indexed by (NMEthtoolID - _NM_ETHTOOL_ID_COALESCE_FIRST) */ + ]; +} NMEthtoolCoalesceState; + +/*****************************************************************************/ + +typedef struct { + guint32 rx_pending; + guint32 rx_mini_pending; + guint32 rx_jumbo_pending; + guint32 tx_pending; +} NMEthtoolRingState; + +typedef struct { + bool autoneg : 1; + bool rx : 1; + bool tx : 1; +} NMEthtoolPauseState; + +/*****************************************************************************/ + +typedef struct _NMPNetns NMPNetns; +typedef struct _NMPlatform NMPlatform; +typedef struct _NMPlatformObject NMPlatformObject; +typedef struct _NMPlatformObjWithIfindex NMPlatformObjWithIfindex; +typedef struct _NMPlatformIP4Address NMPlatformIP4Address; +typedef struct _NMPlatformIP4Route NMPlatformIP4Route; +typedef struct _NMPlatformIP6Address NMPlatformIP6Address; +typedef struct _NMPlatformIP6Route NMPlatformIP6Route; +typedef struct _NMPlatformLink NMPlatformLink; +typedef struct _NMPObject NMPObject; + +typedef enum { + NMP_OBJECT_TYPE_UNKNOWN, + NMP_OBJECT_TYPE_LINK, + +#define NMP_OBJECT_TYPE_IP_ADDRESS(is_ipv4) \ + ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS) + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + +#define NMP_OBJECT_TYPE_IP_ROUTE(is_ipv4) \ + ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE) + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + + NMP_OBJECT_TYPE_ROUTING_RULE, + + NMP_OBJECT_TYPE_QDISC, + + NMP_OBJECT_TYPE_TFILTER, + + NMP_OBJECT_TYPE_LNK_BRIDGE, + NMP_OBJECT_TYPE_LNK_GRE, + NMP_OBJECT_TYPE_LNK_GRETAP, + NMP_OBJECT_TYPE_LNK_INFINIBAND, + NMP_OBJECT_TYPE_LNK_IP6TNL, + NMP_OBJECT_TYPE_LNK_IP6GRE, + NMP_OBJECT_TYPE_LNK_IP6GRETAP, + NMP_OBJECT_TYPE_LNK_IPIP, + NMP_OBJECT_TYPE_LNK_MACSEC, + NMP_OBJECT_TYPE_LNK_MACVLAN, + NMP_OBJECT_TYPE_LNK_MACVTAP, + NMP_OBJECT_TYPE_LNK_SIT, + NMP_OBJECT_TYPE_LNK_TUN, + NMP_OBJECT_TYPE_LNK_VLAN, + NMP_OBJECT_TYPE_LNK_VRF, + NMP_OBJECT_TYPE_LNK_VXLAN, + NMP_OBJECT_TYPE_LNK_WIREGUARD, + + __NMP_OBJECT_TYPE_LAST, + NMP_OBJECT_TYPE_MAX = __NMP_OBJECT_TYPE_LAST - 1, +} NMPObjectType; + +static inline guint32 +nmp_object_type_to_flags(NMPObjectType obj_type) +{ + G_STATIC_ASSERT_EXPR(NMP_OBJECT_TYPE_MAX < 32); + + nm_assert(_NM_INT_NOT_NEGATIVE(obj_type)); + nm_assert(obj_type < NMP_OBJECT_TYPE_MAX); + + return ((guint32) 1u) << obj_type; +} + +/*****************************************************************************/ + +/** + * NMIPRouteTableSyncMode: + * @NM_IP_ROUTE_TABLE_SYNC_MODE_NONE: indicate an invalid setting. + * @NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN: only the main table is synced. For all + * other tables, NM won't delete any extra routes. + * @NM_IP_ROUTE_TABLE_SYNC_MODE_FULL: NM will sync all tables, except the + * local table (255). + * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL: NM will sync all tables, including the + * local table (255). + * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE: NM will sync all tables (including + * the local table). It will thereby remove all addresses, that is during + * deactivation. + */ +typedef enum { + NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, + NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN, + NM_IP_ROUTE_TABLE_SYNC_MODE_FULL, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL, + NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE, +} NMIPRouteTableSyncMode; + +#endif /* __NMP_FWD_H__ */ diff --git a/src/libnm-platform/nmp-netns.c b/src/libnm-platform/nmp-netns.c new file mode 100644 index 00000000..2b28a4cd --- /dev/null +++ b/src/libnm-platform/nmp-netns.c @@ -0,0 +1,759 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-netns.h" + +#include <fcntl.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "libnm-log-core/nm-logging.h" + +/*****************************************************************************/ + +/* NOTE: NMPNetns and all code used here must be thread-safe! */ + +/* we may not call logging functions from the main-thread alone. Hence, we + * require locking from nm-logging. Indicate that by setting NM_THREAD_SAFE_ON_MAIN_THREAD + * to zero. */ +#undef NM_THREAD_SAFE_ON_MAIN_THREAD +#define NM_THREAD_SAFE_ON_MAIN_THREAD 0 + +/*****************************************************************************/ + +#define PROC_SELF_NS_MNT "/proc/self/ns/mnt" +#define PROC_SELF_NS_NET "/proc/self/ns/net" + +#define _CLONE_NS_ALL ((int) (CLONE_NEWNS | CLONE_NEWNET)) +#define _CLONE_NS_ALL_V CLONE_NEWNS, CLONE_NEWNET + +static NM_UTILS_FLAGS2STR_DEFINE(_clone_ns_to_str, + int, + NM_UTILS_FLAGS2STR(CLONE_NEWNS, "mnt"), + NM_UTILS_FLAGS2STR(CLONE_NEWNET, "net"), ); + +static const char * +__ns_types_to_str(int ns_types, int ns_types_already_set, char *buf, gsize len) +{ + const char *b = buf; + char bb[200]; + + nm_utils_strbuf_append_c(&buf, &len, '['); + if (ns_types & ~ns_types_already_set) { + nm_utils_strbuf_append_str( + &buf, + &len, + _clone_ns_to_str(ns_types & ~ns_types_already_set, bb, sizeof(bb))); + } + if (ns_types & ns_types_already_set) { + if (ns_types & ~ns_types_already_set) + nm_utils_strbuf_append_c(&buf, &len, '/'); + nm_utils_strbuf_append_str( + &buf, + &len, + _clone_ns_to_str(ns_types & ns_types_already_set, bb, sizeof(bb))); + } + nm_utils_strbuf_append_c(&buf, &len, ']'); + return b; +} +#define _ns_types_to_str(ns_types, ns_types_already_set, buf) \ + __ns_types_to_str(ns_types, ns_types_already_set, buf, sizeof(buf)) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "netns" +#define _NMLOG(level, netns, ...) \ + G_STMT_START \ + { \ + NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ + NMPNetns *_netns = (netns); \ + char _sbuf[20]; \ + \ + _nm_log(_level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (_netns ? nm_sprintf_buf(_sbuf, "[%p]", _netns) \ + : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FD_NET, PROP_FD_MNT, ); + +typedef struct { + int fd_net; + int fd_mnt; +} NMPNetnsPrivate; + +struct _NMPNetns { + GObject parent; + NMPNetnsPrivate _priv; +}; + +struct _NMPNetnsClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE(NMPNetns, nmp_netns, G_TYPE_OBJECT); + +#define NMP_NETNS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMPNetns, NMP_IS_NETNS) + +/*****************************************************************************/ + +typedef struct { + NMPNetns *netns; + int count; + int ns_types; +} NetnsInfo; + +static void _stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types); +static NMPNetns *_netns_new(GError **error); + +/*****************************************************************************/ + +static NMPNetns * +_netns_get(NetnsInfo *info) +{ + nm_assert(!info || NMP_IS_NETNS(info->netns)); + return info ? info->netns : NULL; +} + +/*****************************************************************************/ + +static _nm_thread_local GArray *_netns_stack = NULL; + +static void +_netns_stack_clear_cb(gpointer data) +{ + NetnsInfo *info = data; + + nm_assert(NMP_IS_NETNS(info->netns)); + g_object_unref(info->netns); +} + +static GArray * +_netns_stack_get_impl(void) +{ + gs_unref_object NMPNetns *netns = NULL; + gs_free_error GError *error = NULL; + GArray * s; + + s = g_array_new(FALSE, FALSE, sizeof(NetnsInfo)); + g_array_set_clear_func(s, _netns_stack_clear_cb); + _netns_stack = s; + + nm_utils_thread_local_register_destroy(s, (GDestroyNotify) g_array_unref); + + /* at the bottom of the stack we must try to create a netns instance + * that we never pop. It's the base to which we need to return. */ + netns = _netns_new(&error); + + if (!netns) { + _LOGD(NULL, "failed to create initial netns: %s", error->message); + return s; + } + + /* we leak this instance inside the stack. */ + _stack_push(s, netns, _CLONE_NS_ALL); + + return s; +} + +#define _netns_stack_get() \ + ({ \ + GArray *_s = _netns_stack; \ + \ + if (G_UNLIKELY(!_s)) \ + _s = _netns_stack_get_impl(); \ + _s; \ + }) + +/*****************************************************************************/ + +static NMPNetns * +_stack_current_netns(GArray *netns_stack, int ns_types) +{ + guint j; + + nm_assert(netns_stack && netns_stack->len > 0); + + /* we search the stack top-down to find the netns that has + * all @ns_types set. */ + for (j = netns_stack->len; ns_types && j >= 1;) { + NetnsInfo *info; + + info = &g_array_index(netns_stack, NetnsInfo, --j); + + if (NM_FLAGS_ALL(info->ns_types, ns_types)) + return info->netns; + } + + g_return_val_if_reached(NULL); +} + +static int +_stack_current_ns_types(GArray *netns_stack, NMPNetns *netns, int ns_types) +{ + const int ns_types_check[] = {_CLONE_NS_ALL_V}; + guint i, j; + int res = 0; + + nm_assert(netns); + nm_assert(netns_stack && netns_stack->len > 0); + + /* we search the stack top-down to check which of @ns_types + * are already set to @netns. */ + for (j = netns_stack->len; ns_types && j >= 1;) { + NetnsInfo *info; + + info = &g_array_index(netns_stack, NetnsInfo, --j); + if (info->netns != netns) { + ns_types = NM_FLAGS_UNSET(ns_types, info->ns_types); + continue; + } + + for (i = 0; i < G_N_ELEMENTS(ns_types_check); i++) { + if (NM_FLAGS_ANY(ns_types, ns_types_check[i]) + && NM_FLAGS_ANY(info->ns_types, ns_types_check[i])) { + res = NM_FLAGS_SET(res, ns_types_check[i]); + ns_types = NM_FLAGS_UNSET(ns_types, ns_types_check[i]); + } + } + } + + return res; +} + +static NetnsInfo * +_stack_peek(GArray *netns_stack) +{ + if (netns_stack->len > 0) + return &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1)); + return NULL; +} + +static NetnsInfo * +_stack_bottom(GArray *netns_stack) +{ + if (netns_stack->len > 0) + return &g_array_index(netns_stack, NetnsInfo, 0); + return NULL; +} + +static void +_stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types) +{ + NetnsInfo *info; + + nm_assert(netns_stack); + nm_assert(NMP_IS_NETNS(netns)); + nm_assert(NM_FLAGS_ANY(ns_types, _CLONE_NS_ALL)); + nm_assert(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL)); + + g_array_set_size(netns_stack, netns_stack->len + 1); + + info = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1)); + *info = (NetnsInfo){ + .netns = g_object_ref(netns), + .ns_types = ns_types, + .count = 1, + }; +} + +static void +_stack_pop(GArray *netns_stack) +{ + NetnsInfo *info; + + nm_assert(netns_stack); + nm_assert(netns_stack->len > 1); + + info = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1)); + + nm_assert(NMP_IS_NETNS(info->netns)); + nm_assert(info->count == 1); + + g_array_set_size(netns_stack, netns_stack->len - 1); +} + +static guint +_stack_size(GArray *netns_stack) +{ + nm_assert(netns_stack); + + return netns_stack->len; +} + +/*****************************************************************************/ + +static NMPNetns * +_netns_new(GError **error) +{ + NMPNetns *self; + int fd_net, fd_mnt; + int errsv; + + fd_net = open(PROC_SELF_NS_NET, O_RDONLY | O_CLOEXEC); + if (fd_net == -1) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Failed opening netns: %s", + nm_strerror_native(errsv)); + errno = errsv; + return NULL; + } + + fd_mnt = open(PROC_SELF_NS_MNT, O_RDONLY | O_CLOEXEC); + if (fd_mnt == -1) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Failed opening mntns: %s", + nm_strerror_native(errsv)); + nm_close(fd_net); + errno = errsv; + return NULL; + } + + self = g_object_new(NMP_TYPE_NETNS, NMP_NETNS_FD_NET, fd_net, NMP_NETNS_FD_MNT, fd_mnt, NULL); + + _LOGD(self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt); + + return self; +} + +static int +_setns(NMPNetns *self, int type) +{ + char buf[100]; + int fd; + NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self); + + nm_assert(NM_IN_SET(type, _CLONE_NS_ALL_V)); + + fd = (type == CLONE_NEWNET) ? priv->fd_net : priv->fd_mnt; + + _LOGt(self, "set netns(%s, %d)", _ns_types_to_str(type, 0, buf), fd); + + return setns(fd, type); +} + +static gboolean +_netns_switch_push(GArray *netns_stack, NMPNetns *self, int ns_types) +{ + int errsv; + + if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET) + && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET) + && _setns(self, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE(self, "failed to switch netns: %s", nm_strerror_native(errsv)); + return FALSE; + } + if (NM_FLAGS_HAS(ns_types, CLONE_NEWNS) + && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNS) + && _setns(self, CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE(self, "failed to switch mntns: %s", nm_strerror_native(errsv)); + + /* try to fix the mess by returning to the previous netns. */ + if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET) + && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET)) { + self = _stack_current_netns(netns_stack, CLONE_NEWNET); + if (self && _setns(self, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE(self, "failed to restore netns: %s", nm_strerror_native(errsv)); + } + } + return FALSE; + } + + return TRUE; +} + +static gboolean +_netns_switch_pop(GArray *netns_stack, NMPNetns *self, int ns_types) +{ + int errsv; + NMPNetns *current; + int success = TRUE; + + if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET) + && (!self || !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET))) { + current = _stack_current_netns(netns_stack, CLONE_NEWNET); + if (!current) { + g_warn_if_reached(); + success = FALSE; + } else if (_setns(current, CLONE_NEWNET) != 0) { + errsv = errno; + _LOGE(self, "failed to switch netns: %s", nm_strerror_native(errsv)); + success = FALSE; + } + } + if (NM_FLAGS_HAS(ns_types, CLONE_NEWNS) + && (!self || !_stack_current_ns_types(netns_stack, self, CLONE_NEWNS))) { + current = _stack_current_netns(netns_stack, CLONE_NEWNS); + if (!current) { + g_warn_if_reached(); + success = FALSE; + } else if (_setns(current, CLONE_NEWNS) != 0) { + errsv = errno; + _LOGE(self, "failed to switch mntns: %s", nm_strerror_native(errsv)); + success = FALSE; + } + } + + return success; +} + +/*****************************************************************************/ + +int +nmp_netns_get_fd_net(NMPNetns *self) +{ + g_return_val_if_fail(NMP_IS_NETNS(self), 0); + + return NMP_NETNS_GET_PRIVATE(self)->fd_net; +} + +int +nmp_netns_get_fd_mnt(NMPNetns *self) +{ + g_return_val_if_fail(NMP_IS_NETNS(self), 0); + + return NMP_NETNS_GET_PRIVATE(self)->fd_mnt; +} + +/*****************************************************************************/ + +static gboolean +_nmp_netns_push_type(NMPNetns *self, int ns_types) +{ + GArray * netns_stack = _netns_stack_get(); + NetnsInfo *info; + char sbuf[100]; + + info = _stack_peek(netns_stack); + g_return_val_if_fail(info, FALSE); + + if (info->netns == self && info->ns_types == ns_types) { + info->count++; + _LOGt(self, + "push#%u* %s (increase count to %d)", + _stack_size(netns_stack) - 1, + _ns_types_to_str(ns_types, ns_types, sbuf), + info->count); + return TRUE; + } + + _LOGD(self, + "push#%u %s", + _stack_size(netns_stack), + _ns_types_to_str(ns_types, _stack_current_ns_types(netns_stack, self, ns_types), sbuf)); + + if (!_netns_switch_push(netns_stack, self, ns_types)) + return FALSE; + + _stack_push(netns_stack, self, ns_types); + return TRUE; +} + +gboolean +nmp_netns_push(NMPNetns *self) +{ + g_return_val_if_fail(NMP_IS_NETNS(self), FALSE); + + return _nmp_netns_push_type(self, _CLONE_NS_ALL); +} + +gboolean +nmp_netns_push_type(NMPNetns *self, int ns_types) +{ + g_return_val_if_fail(NMP_IS_NETNS(self), FALSE); + g_return_val_if_fail(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL), FALSE); + + return _nmp_netns_push_type(self, ns_types == 0 ? _CLONE_NS_ALL : ns_types); +} + +NMPNetns * +nmp_netns_new(void) +{ + GArray * netns_stack = _netns_stack_get(); + NMPNetns * self; + int errsv; + GError * error = NULL; + unsigned long mountflags = 0; + + if (!_stack_peek(netns_stack)) { + /* there are no netns instances. We cannot create a new one + * (because after unshare we couldn't return to the original one). */ + errno = ENOTSUP; + return NULL; + } + + if (unshare(_CLONE_NS_ALL) != 0) { + errsv = errno; + _LOGE(NULL, "failed to create new net and mnt namespace: %s", nm_strerror_native(errsv)); + return NULL; + } + + if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL) != 0) { + errsv = errno; + _LOGE(NULL, "failed mount --make-rslave: %s", nm_strerror_native(errsv)); + goto err_out; + } + + if (umount2("/sys", MNT_DETACH) != 0) { + errsv = errno; + _LOGE(NULL, "failed umount /sys: %s", nm_strerror_native(errsv)); + goto err_out; + } + + if (access("/sys", W_OK) == -1) + mountflags = MS_RDONLY; + + if (mount("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) { + errsv = errno; + _LOGE(NULL, "failed mount /sys: %s", nm_strerror_native(errsv)); + goto err_out; + } + + self = _netns_new(&error); + if (!self) { + errsv = errno; + _LOGE(NULL, "failed to create netns after unshare: %s", error->message); + g_clear_error(&error); + goto err_out; + } + + _stack_push(netns_stack, self, _CLONE_NS_ALL); + + return self; +err_out: + _netns_switch_pop(netns_stack, NULL, _CLONE_NS_ALL); + errno = errsv; + return NULL; +} + +gboolean +nmp_netns_pop(NMPNetns *self) +{ + GArray * netns_stack = _netns_stack_get(); + NetnsInfo *info; + int ns_types; + + g_return_val_if_fail(NMP_IS_NETNS(self), FALSE); + + info = _stack_peek(netns_stack); + + g_return_val_if_fail(info, FALSE); + g_return_val_if_fail(info->netns == self, FALSE); + + if (info->count > 1) { + info->count--; + _LOGt(self, "pop#%u* (decrease count to %d)", _stack_size(netns_stack) - 1, info->count); + return TRUE; + } + g_return_val_if_fail(info->count == 1, FALSE); + + /* cannot pop the original netns. */ + g_return_val_if_fail(_stack_size(netns_stack) > 1, FALSE); + + _LOGD(self, "pop#%u", _stack_size(netns_stack) - 1); + + ns_types = info->ns_types; + + _stack_pop(netns_stack); + + return _netns_switch_pop(netns_stack, self, ns_types); +} + +NMPNetns * +nmp_netns_get_current(void) +{ + return _netns_get(_stack_peek(_netns_stack_get())); +} + +NMPNetns * +nmp_netns_get_initial(void) +{ + return _netns_get(_stack_bottom(_netns_stack_get())); +} + +gboolean +nmp_netns_is_initial(void) +{ + GArray *netns_stack = _netns_stack_get(); + + return (_netns_get(_stack_peek(netns_stack)) == _netns_get(_stack_bottom(netns_stack))); +} + +/*****************************************************************************/ + +gboolean +nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd) +{ + gs_free char * dirname = NULL; + int errsv; + int fd; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + + g_return_val_if_fail(NMP_IS_NETNS(self), FALSE); + g_return_val_if_fail(filename && filename[0] == '/', FALSE); + + if (!nmp_netns_push_type(self, CLONE_NEWNET)) + return FALSE; + netns_pop = self; + + dirname = g_path_get_dirname(filename); + if (mkdir(dirname, 0) != 0) { + errsv = errno; + if (errsv != EEXIST) { + _LOGE(self, + "bind: failed to create directory %s: %s", + dirname, + nm_strerror_native(errsv)); + return FALSE; + } + } + + if ((fd = creat(filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) { + errsv = errno; + _LOGE(self, "bind: failed to create %s: %s", filename, nm_strerror_native(errsv)); + return FALSE; + } + nm_close(fd); + + if (mount(PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) { + errsv = errno; + _LOGE(self, + "bind: failed to mount %s to %s: %s", + PROC_SELF_NS_NET, + filename, + nm_strerror_native(errsv)); + unlink(filename); + return FALSE; + } + + if (out_fd) { + if ((fd = open(filename, O_RDONLY | O_CLOEXEC)) == -1) { + errsv = errno; + _LOGE(self, "bind: failed to open %s: %s", filename, nm_strerror_native(errsv)); + umount2(filename, MNT_DETACH); + unlink(filename); + return FALSE; + } + *out_fd = fd; + } + + return TRUE; +} + +gboolean +nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename) +{ + int errsv; + + g_return_val_if_fail(NMP_IS_NETNS(self), FALSE); + g_return_val_if_fail(filename && filename[0] == '/', FALSE); + + if (umount2(filename, MNT_DETACH) != 0) { + errsv = errno; + _LOGE(self, "bind: failed to unmount2 %s: %s", filename, nm_strerror_native(errsv)); + return FALSE; + } + if (unlink(filename) != 0) { + errsv = errno; + _LOGE(self, "bind: failed to unlink %s: %s", filename, nm_strerror_native(errsv)); + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMPNetns * self = NMP_NETNS(object); + NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_FD_NET: + /* construct-only */ + priv->fd_net = g_value_get_int(value); + g_return_if_fail(priv->fd_net > 0); + break; + case PROP_FD_MNT: + /* construct-only */ + priv->fd_mnt = g_value_get_int(value); + g_return_if_fail(priv->fd_mnt > 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +nmp_netns_init(NMPNetns *self) +{} + +static void +dispose(GObject *object) +{ + NMPNetns * self = NMP_NETNS(object); + NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self); + + nm_close(priv->fd_net); + priv->fd_net = -1; + + nm_close(priv->fd_mnt); + priv->fd_mnt = -1; + + G_OBJECT_CLASS(nmp_netns_parent_class)->dispose(object); +} + +static void +nmp_netns_class_init(NMPNetnsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + obj_properties[PROP_FD_NET] = + g_param_spec_int(NMP_NETNS_FD_NET, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FD_MNT] = + g_param_spec_int(NMP_NETNS_FD_MNT, + "", + "", + 0, + G_MAXINT, + 0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/libnm-platform/nmp-netns.h b/src/libnm-platform/nmp-netns.h new file mode 100644 index 00000000..b18bd03e --- /dev/null +++ b/src/libnm-platform/nmp-netns.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NMP_NETNS_UTILS_H__ +#define __NMP_NETNS_UTILS_H__ + +#include "nmp-base.h" + +/*****************************************************************************/ + +#define NMP_TYPE_NETNS (nmp_netns_get_type()) +#define NMP_NETNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMP_TYPE_NETNS, NMPNetns)) +#define NMP_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NMP_TYPE_NETNS, NMPNetnsClass)) +#define NMP_IS_NETNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMP_TYPE_NETNS)) +#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMP_TYPE_NETNS)) +#define NMP_NETNS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NMP_TYPE_NETNS, NMPNetnsClass)) + +#define NMP_NETNS_FD_NET "fd-net" +#define NMP_NETNS_FD_MNT "fd-mnt" + +typedef struct _NMPNetns NMPNetns; +typedef struct _NMPNetnsClass NMPNetnsClass; + +GType nmp_netns_get_type(void); + +NMPNetns *nmp_netns_new(void); + +gboolean nmp_netns_push(NMPNetns *self); +gboolean nmp_netns_push_type(NMPNetns *self, int ns_types); +gboolean nmp_netns_pop(NMPNetns *self); + +NMPNetns *nmp_netns_get_current(void); +NMPNetns *nmp_netns_get_initial(void); +gboolean nmp_netns_is_initial(void); + +int nmp_netns_get_fd_net(NMPNetns *self); +int nmp_netns_get_fd_mnt(NMPNetns *self); + +static inline void +_nm_auto_pop_netns(NMPNetns **p) +{ + if (*p) { + int errsv = errno; + + nmp_netns_pop(*p); + errno = errsv; + } +} +#define nm_auto_pop_netns nm_auto(_nm_auto_pop_netns) + +gboolean nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd); +gboolean nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename); + +#endif /* __NMP_NETNS_UTILS_H__ */ diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c new file mode 100644 index 00000000..a7a46ef0 --- /dev/null +++ b/src/libnm-platform/nmp-object.c @@ -0,0 +1,3470 @@ +/* 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-object.h" + +#include <unistd.h> +#include <linux/rtnetlink.h> +#include <linux/if.h> +#include <libudev.h> + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-platform/nm-platform-utils.h" +#include "libnm-platform/wifi/nm-wifi-utils.h" +#include "libnm-platform/wpan/nm-wpan-utils.h" + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, obj, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + const NMPObject *const __obj = (obj); \ + \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __obj, \ + (__obj ? NMP_OBJECT_GET_CLASS(__obj)->obj_type_name \ + : "???") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +typedef struct { + NMDedupMultiIdxType parent; + NMPCacheIdType cache_id_type; +} DedupMultiIdxType; + +struct _NMPCache { + /* the cache contains only one hash table for all object types, and similarly + * it contains only one NMMultiIndex. + * This works, because different object types don't ever compare equal and + * because their index ids also don't overlap. + * + * For routes and addresses, the cache contains an address if (and only if) the + * object was reported via netlink. + * For links, the cache contain a link if it was reported by either netlink + * or udev. That means, a link object can be alive, even if it was already + * removed via netlink. + * + * This effectively merges the udev-device cache into the NMPCache. + */ + + NMDedupMultiIndex *multi_idx; + + /* an idx_type entry for each NMP_CACHE_ID_TYPE. Note that NONE (zero) + * is skipped, so the index is shifted by one: idx_type[cache_id_type - 1]. + * + * Don't bother, use _idx_type_get() instead! */ + DedupMultiIdxType idx_types[NMP_CACHE_ID_TYPE_MAX]; + + gboolean use_udev; +}; + +/*****************************************************************************/ + +int +nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b) +{ + nm_assert(!a || NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + nm_assert(!b || NM_IN_SET(b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, sa.sa_family); + switch (a->sa.sa_family) { + case AF_INET: + NM_CMP_DIRECT(ntohl(a->in.sin_addr.s_addr), ntohl(b->in.sin_addr.s_addr)); + NM_CMP_DIRECT(htons(a->in.sin_port), htons(b->in.sin_port)); + break; + case AF_INET6: + NM_CMP_DIRECT_IN6ADDR(&a->in6.sin6_addr, &b->in6.sin6_addr); + NM_CMP_DIRECT(htons(a->in6.sin6_port), htons(b->in6.sin6_port)); + NM_CMP_FIELD(a, b, in6.sin6_scope_id); + NM_CMP_FIELD(a, b, in6.sin6_flowinfo); + break; + } + return 0; +} + +void +nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h) +{ + if (!a) { + nm_hash_update_val(h, 1241364739u); + return; + } + + nm_assert(NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); + + switch (a->sa.sa_family) { + case AF_INET: + nm_hash_update_vals(h, a->in.sin_family, a->in.sin_addr.s_addr, a->in.sin_port); + return; + case AF_INET6: + nm_hash_update_vals(h, + a->in6.sin6_family, + a->in6.sin6_addr, + a->in6.sin6_port, + a->in6.sin6_scope_id, + a->in6.sin6_flowinfo); + return; + default: + nm_hash_update_val(h, a->sa.sa_family); + return; + } +} + +/** + * nm_sock_addr_union_cpy: + * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, + * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of + * error). + * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in + * memory. If not %NULL, the buffer must be at least large enough to contain + * sa.sa_family, and then, depending on sa.sa_family, it must be large enough + * to hold struct sockaddr_in or struct sockaddr_in6. + * + * @dst will always be fully initialized (including setting all un-used bytes to zero). + */ +void +nm_sock_addr_union_cpy(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */) +{ + struct sockaddr sa; + gsize src_len; + + nm_assert(dst); + + *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + + if (!src) + return; + + memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family)); + + if (sa.sa_family == AF_INET) + src_len = sizeof(struct sockaddr_in); + else if (sa.sa_family == AF_INET6) + src_len = sizeof(struct sockaddr_in6); + else + return; + + memcpy(dst, src, src_len); + nm_assert(dst->sa.sa_family == sa.sa_family); +} + +/** + * nm_sock_addr_union_cpy_untrusted: + * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, + * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of + * error). + * @src: the source buffer with an sockaddr to copy. It may be unaligned in + * memory. + * @src_len: the length of @src in bytes. + * + * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6). + * If that's the case, then @src will be interpreted as such structure (unaligned), and + * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the + * struct will be copied. + * + * On any failure, @dst will be set to sa.sa_family AF_UNSPEC. + * @dst will always be fully initialized (including setting all un-used bytes to zero). + */ +void +nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */, + gsize src_len) +{ + int f_expected; + struct sockaddr sa; + + nm_assert(dst); + + *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; + + if (src_len == sizeof(struct sockaddr_in)) + f_expected = AF_INET; + else if (src_len == sizeof(struct sockaddr_in6)) + f_expected = AF_INET6; + else + return; + + memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family)); + + if (sa.sa_family != f_expected) + return; + + memcpy(dst, src, src_len); + nm_assert(dst->sa.sa_family == sa.sa_family); +} + +const char * +nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len) +{ + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + char s_scope_id[40]; + + if (!nm_utils_to_string_buffer_init_null(sa, &buf, &len)) + return buf; + + /* maybe we should use getnameinfo(), but here implement it ourself. + * + * We want to see the actual bytes for debugging (as we understand them), + * and now what getnameinfo() makes of it. Also, it's simpler this way. */ + + switch (sa->sa.sa_family) { + case AF_INET: + g_snprintf(buf, + len, + "%s:%u", + _nm_utils_inet4_ntop(sa->in.sin_addr.s_addr, s_addr), + (guint) htons(sa->in.sin_port)); + break; + case AF_INET6: + g_snprintf(buf, + len, + "[%s%s]:%u", + _nm_utils_inet6_ntop(&sa->in6.sin6_addr, s_addr), + (sa->in6.sin6_scope_id != 0 + ? nm_sprintf_buf(s_scope_id, "%u", sa->in6.sin6_scope_id) + : ""), + (guint) htons(sa->in6.sin6_port)); + break; + case AF_UNSPEC: + g_snprintf(buf, len, "unspec"); + break; + default: + g_snprintf(buf, len, "{addr-family:%u}", (unsigned) sa->sa.sa_family); + break; + } + + return buf; +} + +/*****************************************************************************/ + +const char * +nmp_object_link_udev_device_get_property_value(const NMPObject *obj, const char *key) +{ + nm_assert(key); + + if (!obj) + return nm_assert_unreachable_val(NULL); + + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK); + + if (!obj->_link.udev.device) + return NULL; + + return udev_device_get_property_value(obj->_link.udev.device, key); +} + +/*****************************************************************************/ + +static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class; + +static void +_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + const NMPObject *o = (NMPObject *) obj; + + nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class); + nm_assert(NMP_OBJECT_GET_TYPE(o) != NMP_OBJECT_TYPE_UNKNOWN); + + nmp_object_id_hash_update(o, h); +} + +static gboolean +_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + const NMPObject *o_a = (NMPObject *) obj_a; + const NMPObject *o_b = (NMPObject *) obj_b; + + nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class); + nm_assert(NMP_OBJECT_GET_TYPE(o_a) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(NMP_OBJECT_GET_TYPE(o_b) != NMP_OBJECT_TYPE_UNKNOWN); + + return nmp_object_id_equal(o_a, o_b); +} + +static guint +_idx_obj_part(const DedupMultiIdxType *idx_type, + const NMPObject * obj_a, + const NMPObject * obj_b, + NMHashState * h) +{ + NMPObjectType obj_type; + + /* the hash/equals functions are strongly related. So, keep them + * side-by-side and do it all in _idx_obj_part(). */ + + nm_assert(idx_type); + nm_assert(idx_type->parent.klass == &_dedup_multi_idx_type_class); + nm_assert(obj_a); + nm_assert(NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_b || (NMP_OBJECT_GET_TYPE(obj_b) != NMP_OBJECT_TYPE_UNKNOWN)); + nm_assert(!h || !obj_b); + + switch (idx_type->cache_id_type) { + case NMP_CACHE_ID_TYPE_OBJECT_TYPE: + if (obj_b) + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b); + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a)); + } + return 1; + + case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME: + if (NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_LINK) { + /* first check, whether obj_a is suitable for this idx_type. + * If not, return 0 (which is correct for partitionable(), hash() and equal() + * functions. */ + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + /* we are in equal() mode. Compare obj_b with obj_a. */ + return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_LINK + && nm_streq(obj_a->link.name, obj_b->link.name); + } + if (h) { + nm_hash_update_val(h, idx_type->cache_id_type); + nm_hash_update_strarr(h, obj_a->link.name); + } + /* just return 1, to indicate that obj_a is partitionable by this idx_type. */ + return 1; + + case NMP_CACHE_ID_TYPE_DEFAULT_ROUTES: + if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE) + || !NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_a->ip_route) + || !nmp_object_is_visible(obj_a)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b) + && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_b->ip_route) + && nmp_object_is_visible(obj_b); + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a)); + } + return 1; + + case NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX: + if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER) + || !nmp_object_is_visible(obj_a)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + nm_assert(NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex > 0); + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b) + && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex + == NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_b)->ifindex + && nmp_object_is_visible(obj_b); + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->obj_with_ifindex.ifindex); + } + return 1; + + case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID: + obj_type = NMP_OBJECT_GET_TYPE(obj_a); + if (!NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) + || NMP_OBJECT_CAST_IP_ROUTE(obj_a)->ifindex <= 0) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return obj_type == NMP_OBJECT_GET_TYPE(obj_b) + && NMP_OBJECT_CAST_IP_ROUTE(obj_b)->ifindex > 0 + && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE + ? (nm_platform_ip4_route_cmp(&obj_a->ip4_route, + &obj_b->ip4_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) + == 0) + : (nm_platform_ip6_route_cmp(&obj_a->ip6_route, + &obj_b->ip6_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) + == 0)); + } + if (h) { + nm_hash_update_val(h, idx_type->cache_id_type); + if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) + nm_platform_ip4_route_hash_update(&obj_a->ip4_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + h); + else + nm_platform_ip6_route_hash_update(&obj_a->ip6_route, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, + h); + } + return 1; + + case NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY: + obj_type = NMP_OBJECT_GET_TYPE(obj_a); + /* currently, only routing rules are supported for this cache-id-type. */ + if (obj_type != NMP_OBJECT_TYPE_ROUTING_RULE + || !NM_IN_SET(obj_a->routing_rule.addr_family, AF_INET, AF_INET6)) { + if (h) + nm_hash_update_val(h, obj_a); + return 0; + } + if (obj_b) { + return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_ROUTING_RULE + && obj_a->routing_rule.addr_family == obj_b->routing_rule.addr_family; + } + if (h) { + nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->routing_rule.addr_family); + } + return 1; + + case NMP_CACHE_ID_TYPE_NONE: + case __NMP_CACHE_ID_TYPE_MAX: + break; + } + nm_assert_not_reached(); + return 0; +} + +static gboolean +_idx_obj_partitionable(const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj) +{ + return _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, NULL) != 0; +} + +static void +_idx_obj_partition_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, h); +} + +static gboolean +_idx_obj_partition_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + return _idx_obj_part((DedupMultiIdxType *) idx_type, + (NMPObject *) obj_a, + (NMPObject *) obj_b, + NULL); +} + +static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class = { + .idx_obj_id_hash_update = _idx_obj_id_hash_update, + .idx_obj_id_equal = _idx_obj_id_equal, + .idx_obj_partitionable = _idx_obj_partitionable, + .idx_obj_partition_hash_update = _idx_obj_partition_hash_update, + .idx_obj_partition_equal = _idx_obj_partition_equal, +}; + +static void +_dedup_multi_idx_type_init(DedupMultiIdxType *idx_type, NMPCacheIdType cache_id_type) +{ + nm_dedup_multi_idx_type_init((NMDedupMultiIdxType *) idx_type, &_dedup_multi_idx_type_class); + idx_type->cache_id_type = cache_id_type; +} + +/*****************************************************************************/ + +static void +_vlan_xgress_qos_mappings_hash_update(guint n_map, const NMVlanQosMapping *map, NMHashState *h) +{ + /* ensure no padding. */ + G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == 2 * sizeof(guint32)); + + nm_hash_update_val(h, n_map); + if (n_map) + nm_hash_update(h, map, n_map * sizeof(*map)); +} + +static int +_vlan_xgress_qos_mappings_cmp(guint n_map, + const NMVlanQosMapping *map1, + const NMVlanQosMapping *map2) +{ + guint i; + + for (i = 0; i < n_map; i++) { + if (map1[i].from != map2[i].from) + return map1[i].from < map2[i].from ? -1 : 1; + if (map1[i].to != map2[i].to) + return map1[i].to < map2[i].to ? -1 : 1; + } + return 0; +} + +static void +_vlan_xgress_qos_mappings_cpy(guint * dst_n_map, + NMVlanQosMapping ** dst_map, + guint src_n_map, + const NMVlanQosMapping *src_map) +{ + if (src_n_map == 0) { + nm_clear_g_free(dst_map); + *dst_n_map = 0; + } else if (src_n_map != *dst_n_map + || _vlan_xgress_qos_mappings_cmp(src_n_map, *dst_map, src_map) != 0) { + nm_clear_g_free(dst_map); + *dst_n_map = src_n_map; + *dst_map = nm_memdup(src_map, sizeof(*src_map) * src_n_map); + } +} + +/*****************************************************************************/ + +static void +_wireguard_allowed_ip_hash_update(const NMPWireGuardAllowedIP *ip, NMHashState *h) +{ + nm_hash_update_vals(h, ip->family, ip->mask); + + if (ip->family == AF_INET) + nm_hash_update_val(h, ip->addr.addr4); + else if (ip->family == AF_INET6) + nm_hash_update_val(h, ip->addr.addr6); +} + +static int +_wireguard_allowed_ip_cmp(const NMPWireGuardAllowedIP *a, const NMPWireGuardAllowedIP *b) +{ + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, family); + NM_CMP_FIELD(a, b, mask); + + if (a->family == AF_INET) + NM_CMP_FIELD(a, b, addr.addr4); + else if (a->family == AF_INET6) + NM_CMP_FIELD_IN6ADDR(a, b, addr.addr6); + + return 0; +} + +static void +_wireguard_peer_hash_update(const NMPWireGuardPeer *peer, NMHashState *h) +{ + guint i; + + nm_hash_update(h, peer->public_key, sizeof(peer->public_key)); + nm_hash_update(h, peer->preshared_key, sizeof(peer->preshared_key)); + nm_hash_update_vals(h, + peer->persistent_keepalive_interval, + peer->allowed_ips_len, + peer->rx_bytes, + peer->tx_bytes, + peer->last_handshake_time.tv_sec, + peer->last_handshake_time.tv_nsec); + + nm_sock_addr_union_hash_update(&peer->endpoint, h); + + for (i = 0; i < peer->allowed_ips_len; i++) + _wireguard_allowed_ip_hash_update(&peer->allowed_ips[i], h); +} + +static int +_wireguard_peer_cmp(const NMPWireGuardPeer *a, const NMPWireGuardPeer *b) +{ + guint i; + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, last_handshake_time.tv_sec); + NM_CMP_FIELD(a, b, last_handshake_time.tv_nsec); + NM_CMP_FIELD(a, b, rx_bytes); + NM_CMP_FIELD(a, b, tx_bytes); + NM_CMP_FIELD(a, b, allowed_ips_len); + NM_CMP_FIELD(a, b, persistent_keepalive_interval); + NM_CMP_FIELD(a, b, endpoint.sa.sa_family); + NM_CMP_FIELD_MEMCMP(a, b, public_key); + NM_CMP_FIELD_MEMCMP(a, b, preshared_key); + + NM_CMP_RETURN(nm_sock_addr_union_cmp(&a->endpoint, &b->endpoint)); + + for (i = 0; i < a->allowed_ips_len; i++) { + NM_CMP_RETURN(_wireguard_allowed_ip_cmp(&a->allowed_ips[i], &b->allowed_ips[i])); + } + + return 0; +} + +/*****************************************************************************/ + +static const char * +_link_get_driver(struct udev_device *udevice, const char *kind, int ifindex) +{ + const char *driver = NULL; + + nm_assert(kind == g_intern_string(kind)); + + if (udevice) { + driver = nmp_utils_udev_get_driver(udevice); + if (driver) + return driver; + } + + if (kind) + return kind; + + if (ifindex > 0) { + NMPUtilsEthtoolDriverInfo driver_info; + + if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) { + if (driver_info.driver[0]) + return g_intern_string(driver_info.driver); + } + } + + return "unknown"; +} + +void +_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev) +{ + const char *driver = NULL; + gboolean initialized = FALSE; + NMPObject * obj; + + nm_assert(obj_orig || *obj_new); + nm_assert(obj_new); + nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK); + nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK); + + obj = *obj_new ?: obj_orig; + + /* The link contains internal fields that are combined by + * properties from netlink and udev. Update those properties */ + + /* When a link is not in netlink, its udev fields don't matter. */ + if (obj->_link.netlink.is_in_netlink) { + driver = _link_get_driver(obj->_link.udev.device, obj->link.kind, obj->link.ifindex); + if (obj->_link.udev.device) + initialized = TRUE; + else if (!use_udev) { + /* If we don't use udev, we immediately mark the link as initialized. + * + * For that, we consult @use_udev argument, that is cached via + * nmp_cache_use_udev_get(). It is on purpose not to test + * for a writable /sys on every call. A minor reason for that is + * performance, but the real reason is reproducibility. + * */ + initialized = TRUE; + } + } + + if (nm_streq0(obj->link.driver, driver) && obj->link.initialized == initialized) + return; + + if (!*obj_new) + obj = *obj_new = nmp_object_clone(obj, FALSE); + + obj->link.driver = driver; + obj->link.initialized = initialized; +} + +static void +_nmp_object_fixup_link_master_connected(NMPObject ** obj_new, + NMPObject * obj_orig, + const NMPCache *cache) +{ + NMPObject *obj; + + nm_assert(obj_orig || *obj_new); + nm_assert(obj_new); + nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK); + nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK); + + obj = *obj_new ?: obj_orig; + + if (nmp_cache_link_connected_needs_toggle(cache, obj, NULL, NULL)) { + if (!*obj_new) + obj = *obj_new = nmp_object_clone(obj, FALSE); + obj->link.connected = !obj->link.connected; + } +} + +/*****************************************************************************/ + +static void +_vt_cmd_obj_dispose_link(NMPObject *obj) +{ + if (obj->_link.udev.device) { + udev_device_unref(obj->_link.udev.device); + obj->_link.udev.device = NULL; + } + g_clear_object(&obj->_link.ext_data); + nmp_object_unref(obj->_link.netlink.lnk); +} + +static void +_vt_cmd_obj_dispose_lnk_vlan(NMPObject *obj) +{ + g_free((gpointer) obj->_lnk_vlan.ingress_qos_map); + g_free((gpointer) obj->_lnk_vlan.egress_qos_map); +} + +static void +_wireguard_clear(NMPObjectLnkWireGuard *lnk) +{ + guint i; + + nm_explicit_bzero(lnk->_public.private_key, sizeof(lnk->_public.private_key)); + for (i = 0; i < lnk->peers_len; i++) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i]; + + nm_explicit_bzero(peer->preshared_key, sizeof(peer->preshared_key)); + } + g_free((gpointer) lnk->peers); + g_free((gpointer) lnk->_allowed_ips_buf); +} + +static void +_vt_cmd_obj_dispose_lnk_wireguard(NMPObject *obj) +{ + _wireguard_clear(&obj->_lnk_wireguard); +} + +static NMPObject * +_nmp_object_new_from_class(const NMPClass *klass) +{ + NMPObject *obj; + + nm_assert(klass); + nm_assert(klass->sizeof_data > 0); + nm_assert(klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data); + + obj = g_slice_alloc0(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object)); + obj->_class = klass; + obj->parent._ref_count = 1; + return obj; +} + +NMPObject * +nmp_object_new(NMPObjectType obj_type, gconstpointer plobj) +{ + const NMPClass *klass = nmp_class_from_type(obj_type); + NMPObject * obj; + + obj = _nmp_object_new_from_class(klass); + if (plobj) + memcpy(&obj->object, plobj, klass->sizeof_public); + return obj; +} + +NMPObject * +nmp_object_new_link(int ifindex) +{ + NMPObject *obj; + + obj = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL); + obj->link.ifindex = ifindex; + return obj; +} + +/*****************************************************************************/ + +static void +_nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass) +{ + nm_assert(obj); + nm_assert(klass); + + *obj = (NMPObject){ + .parent = + { + .klass = (const NMDedupMultiObjClass *) klass, + ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, + }, + }; +} + +static NMPObject * +_nmp_object_stackinit_from_type(NMPObject *obj, NMPObjectType obj_type) +{ + const NMPClass *klass; + + nm_assert(obj); + klass = nmp_class_from_type(obj_type); + nm_assert(klass); + + *obj = (NMPObject){ + .parent = + { + .klass = (const NMDedupMultiObjClass *) klass, + ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, + }, + }; + return obj; +} + +const NMPObject * +nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj) +{ + const NMPClass *klass = nmp_class_from_type(obj_type); + + _nmp_object_stackinit_from_class(obj, klass); + if (plobj) + memcpy(&obj->object, plobj, klass->sizeof_public); + return obj; +} + +const NMPObject * +nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src) +{ + const NMPClass *klass; + + nm_assert(NMP_OBJECT_IS_VALID(src)); + nm_assert(obj); + + klass = NMP_OBJECT_GET_CLASS(src); + _nmp_object_stackinit_from_class(obj, klass); + if (klass->cmd_plobj_id_copy) + klass->cmd_plobj_id_copy(&obj->object, &src->object); + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_link(NMPObject *obj, int ifindex) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_LINK); + obj->link.ifindex = ifindex; + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_ip4_address(NMPObject *obj, + int ifindex, + guint32 address, + guint8 plen, + guint32 peer_address) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP4_ADDRESS); + obj->ip4_address.ifindex = ifindex; + obj->ip4_address.address = address; + obj->ip4_address.plen = plen; + obj->ip4_address.peer_address = peer_address; + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP6_ADDRESS); + obj->ip4_address.ifindex = ifindex; + if (address) + obj->ip6_address.address = *address; + return obj; +} + +/*****************************************************************************/ + +const char * +nmp_object_to_string(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + + if (!nm_utils_to_string_buffer_init_null(obj, &buf, &buf_size)) + return buf; + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + + klass = NMP_OBJECT_GET_CLASS(obj); + + if (klass->cmd_obj_to_string) + return klass->cmd_obj_to_string(obj, to_string_mode, buf, buf_size); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + if (!klass->cmd_plobj_to_string_id) { + g_snprintf(buf, buf_size, "%p", obj); + return buf; + } + return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size); + case NMP_OBJECT_TO_STRING_ALL: + g_snprintf( + buf, + buf_size, + "[%s,%p,%u,%calive,%cvisible; %s]", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf2, sizeof(buf2))); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_link(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj); + char * b = buf; + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size); + case NMP_OBJECT_TO_STRING_ALL: + nm_utils_strbuf_append(&b, + &buf_size, + "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + obj->_link.netlink.is_in_netlink ? '+' : '-', + obj->_link.udev.device); + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + if (obj->_link.netlink.lnk) { + nm_utils_strbuf_append_str(&b, &buf_size, "; "); + nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + } + nm_utils_strbuf_append_c(&b, &buf_size, ']'); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size); + if (obj->_link.netlink.lnk) { + nm_utils_strbuf_seek_end(&b, &buf_size); + nm_utils_strbuf_append_str(&b, &buf_size, "; "); + nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, buf_size); + } + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_lnk_vlan(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + char * b; + gsize l; + + klass = NMP_OBJECT_GET_CLASS(obj); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + g_snprintf(buf, buf_size, "%p", obj); + return buf; + case NMP_OBJECT_TO_STRING_ALL: + + g_snprintf(buf, + buf_size, + "[%s,%p,%u,%calive,%cvisible; %s]", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2))); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + + b = buf; + l = strlen(b); + b += l; + buf_size -= l; + + if (obj->_lnk_vlan.n_ingress_qos_map) { + nm_platform_vlan_qos_mapping_to_string(" ingress-qos-map", + obj->_lnk_vlan.ingress_qos_map, + obj->_lnk_vlan.n_ingress_qos_map, + b, + buf_size); + l = strlen(b); + b += l; + buf_size -= l; + } + if (obj->_lnk_vlan.n_egress_qos_map) { + nm_platform_vlan_qos_mapping_to_string(" egress-qos-map", + obj->_lnk_vlan.egress_qos_map, + obj->_lnk_vlan.n_egress_qos_map, + b, + buf_size); + l = strlen(b); + b += l; + buf_size -= l; + } + + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +static const char * +_vt_cmd_obj_to_string_lnk_wireguard(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof(_nm_utils_to_string_buffer)]; + char * b; + guint i; + + klass = NMP_OBJECT_GET_CLASS(obj); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + g_snprintf(buf, buf_size, "%p", obj); + return buf; + case NMP_OBJECT_TO_STRING_ALL: + b = buf; + + nm_utils_strbuf_append( + &b, + &buf_size, + "[%s,%p,%u,%calive,%cvisible; %s" + "%s", + klass->obj_type_name, + obj, + obj->parent._ref_count, + nmp_object_is_alive(obj) ? '+' : '-', + nmp_object_is_visible(obj) ? '+' : '-', + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)), + obj->_lnk_wireguard.peers_len > 0 ? " peers {" : ""); + + for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) { + const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i]; + + nm_utils_strbuf_append_str(&b, &buf_size, " { "); + nm_platform_wireguard_peer_to_string(peer, b, buf_size); + nm_utils_strbuf_seek_end(&b, &buf_size); + nm_utils_strbuf_append_str(&b, &buf_size, " }"); + } + if (obj->_lnk_wireguard.peers_len) + nm_utils_strbuf_append_str(&b, &buf_size, " }"); + + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size); + + return buf; + default: + g_return_val_if_reached("ERROR"); + } +} + +#define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \ + static const char *_vt_cmd_plobj_to_string_id_##type(const NMPlatformObject *_obj, \ + char * buf, \ + gsize buf_len) \ + { \ + plat_type *const obj = (plat_type *) _obj; \ + _nm_unused char buf1[NM_UTILS_INET_ADDRSTRLEN]; \ + _nm_unused char buf2[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + g_snprintf(buf, buf_len, __VA_ARGS__); \ + return buf; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_to_string_id(link, NMPlatformLink, "%d", obj->ifindex); + +_vt_cmd_plobj_to_string_id(ip4_address, + NMPlatformIP4Address, + "%d: %s/%d%s%s", + obj->ifindex, + _nm_utils_inet4_ntop(obj->address, buf1), + obj->plen, + obj->peer_address != obj->address ? "," : "", + obj->peer_address != obj->address ? _nm_utils_inet4_ntop( + nm_utils_ip4_address_clear_host_address(obj->peer_address, + obj->plen), + buf2) + : ""); + +_vt_cmd_plobj_to_string_id(ip6_address, + NMPlatformIP6Address, + "%d: %s", + obj->ifindex, + _nm_utils_inet6_ntop(&obj->address, buf1)); + +_vt_cmd_plobj_to_string_id(qdisc, NMPlatformQdisc, "%d: %d", obj->ifindex, obj->parent); + +_vt_cmd_plobj_to_string_id(tfilter, NMPlatformTfilter, "%d: %d", obj->ifindex, obj->parent); + +void +nmp_object_hash_update(const NMPObject *obj, NMHashState *h) +{ + const NMPClass *klass; + + g_return_if_fail(NMP_OBJECT_IS_VALID(obj)); + + klass = NMP_OBJECT_GET_CLASS(obj); + + nm_hash_update_val(h, klass->obj_type); + if (klass->cmd_obj_hash_update) + klass->cmd_obj_hash_update(obj, h); + else if (klass->cmd_plobj_hash_update) + klass->cmd_plobj_hash_update(&obj->object, h); + else + nm_hash_update_val(h, obj); +} + +static void +_vt_cmd_obj_hash_update_link(const NMPObject *obj, NMHashState *h) +{ + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK); + + nm_platform_link_hash_update(&obj->link, h); + nm_hash_update_vals(h, + obj->_link.netlink.is_in_netlink, + obj->_link.wireguard_family_id, + obj->_link.udev.device); + if (obj->_link.netlink.lnk) + nmp_object_hash_update(obj->_link.netlink.lnk, h); +} + +static void +_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, NMHashState *h) +{ + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_VLAN); + + nm_platform_lnk_vlan_hash_update(&obj->lnk_vlan, h); + _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_ingress_qos_map, + obj->_lnk_vlan.ingress_qos_map, + h); + _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_egress_qos_map, + obj->_lnk_vlan.egress_qos_map, + h); +} + +static void +_vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, NMHashState *h) +{ + guint i; + + nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD); + + nm_platform_lnk_wireguard_hash_update(&obj->lnk_wireguard, h); + + nm_hash_update_val(h, obj->_lnk_wireguard.peers_len); + for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) + _wireguard_peer_hash_update(&obj->_lnk_wireguard.peers[i], h); +} + +int +nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass1, *klass2; + + NM_CMP_SELF(obj1, obj2); + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), -1); + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), 1); + + klass1 = NMP_OBJECT_GET_CLASS(obj1); + klass2 = NMP_OBJECT_GET_CLASS(obj2); + + if (klass1 != klass2) { + nm_assert(klass1->obj_type != klass2->obj_type); + return klass1->obj_type < klass2->obj_type ? -1 : 1; + } + + if (klass1->cmd_obj_cmp) + return klass1->cmd_obj_cmp(obj1, obj2); + return klass1->cmd_plobj_cmp(&obj1->object, &obj2->object); +} + +static int +_vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2) +{ + NM_CMP_RETURN(nm_platform_link_cmp(&obj1->link, &obj2->link)); + NM_CMP_DIRECT(obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink); + NM_CMP_RETURN(nmp_object_cmp(obj1->_link.netlink.lnk, obj2->_link.netlink.lnk)); + NM_CMP_DIRECT(obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id); + + if (obj1->_link.udev.device != obj2->_link.udev.device) { + if (!obj1->_link.udev.device) + return -1; + if (!obj2->_link.udev.device) + return 1; + + /* Only compare based on pointer values. That is ugly because it's not a + * stable sort order. + * + * Have this check as very last. */ + return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1; + } + + return 0; +} + +static int +_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2) +{ + int c; + + c = nm_platform_lnk_vlan_cmp(&obj1->lnk_vlan, &obj2->lnk_vlan); + if (c) + return c; + + if (obj1->_lnk_vlan.n_ingress_qos_map != obj2->_lnk_vlan.n_ingress_qos_map) + return obj1->_lnk_vlan.n_ingress_qos_map < obj2->_lnk_vlan.n_ingress_qos_map ? -1 : 1; + if (obj1->_lnk_vlan.n_egress_qos_map != obj2->_lnk_vlan.n_egress_qos_map) + return obj1->_lnk_vlan.n_egress_qos_map < obj2->_lnk_vlan.n_egress_qos_map ? -1 : 1; + + c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_ingress_qos_map, + obj1->_lnk_vlan.ingress_qos_map, + obj2->_lnk_vlan.ingress_qos_map); + if (c) + return c; + c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_egress_qos_map, + obj1->_lnk_vlan.egress_qos_map, + obj2->_lnk_vlan.egress_qos_map); + + return c; +} + +static int +_vt_cmd_obj_cmp_lnk_wireguard(const NMPObject *obj1, const NMPObject *obj2) +{ + guint i; + + NM_CMP_RETURN(nm_platform_lnk_wireguard_cmp(&obj1->lnk_wireguard, &obj2->lnk_wireguard)); + + NM_CMP_FIELD(obj1, obj2, _lnk_wireguard.peers_len); + + for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++) + NM_CMP_RETURN( + _wireguard_peer_cmp(&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i])); + + return 0; +} + +/* @src is a const object, which is not entirely correct for link types, where + * we increase the ref count for src->_link.udev.device. + * Hence, nmp_object_copy() can violate the const promise of @src. + * */ +void +nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only) +{ + g_return_if_fail(NMP_OBJECT_IS_VALID(dst)); + g_return_if_fail(NMP_OBJECT_IS_VALID(src)); + g_return_if_fail(!NMP_OBJECT_IS_STACKINIT(dst)); + + if (src != dst) { + const NMPClass *klass = NMP_OBJECT_GET_CLASS(dst); + + g_return_if_fail(klass == NMP_OBJECT_GET_CLASS(src)); + + if (id_only) { + if (klass->cmd_plobj_id_copy) + klass->cmd_plobj_id_copy(&dst->object, &src->object); + } else if (klass->cmd_obj_copy) + klass->cmd_obj_copy(dst, src); + else + memcpy(&dst->object, &src->object, klass->sizeof_data); + } +} + +static void +_vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src) +{ + if (dst->_link.udev.device != src->_link.udev.device) { + if (src->_link.udev.device) + udev_device_ref(src->_link.udev.device); + if (dst->_link.udev.device) + udev_device_unref(dst->_link.udev.device); + dst->_link.udev.device = src->_link.udev.device; + } + if (dst->_link.netlink.lnk != src->_link.netlink.lnk) { + if (src->_link.netlink.lnk) + nmp_object_ref(src->_link.netlink.lnk); + if (dst->_link.netlink.lnk) + nmp_object_unref(dst->_link.netlink.lnk); + dst->_link.netlink.lnk = src->_link.netlink.lnk; + } + if (dst->_link.ext_data != src->_link.ext_data) { + if (dst->_link.ext_data) + g_clear_object(&dst->_link.ext_data); + if (src->_link.ext_data) + dst->_link.ext_data = g_object_ref(src->_link.ext_data); + } + dst->_link = src->_link; +} + +static void +_vt_cmd_obj_copy_lnk_vlan(NMPObject *dst, const NMPObject *src) +{ + dst->lnk_vlan = src->lnk_vlan; + _vlan_xgress_qos_mappings_cpy( + &dst->_lnk_vlan.n_ingress_qos_map, + NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map), + src->_lnk_vlan.n_ingress_qos_map, + src->_lnk_vlan.ingress_qos_map); + _vlan_xgress_qos_mappings_cpy(&dst->_lnk_vlan.n_egress_qos_map, + NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map), + src->_lnk_vlan.n_egress_qos_map, + src->_lnk_vlan.egress_qos_map); +} + +static void +_vt_cmd_obj_copy_lnk_wireguard(NMPObject *dst, const NMPObject *src) +{ + guint i; + + nm_assert(dst != src); + + _wireguard_clear(&dst->_lnk_wireguard); + + dst->_lnk_wireguard = src->_lnk_wireguard; + + dst->_lnk_wireguard.peers = nm_memdup(dst->_lnk_wireguard.peers, + sizeof(NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len); + dst->_lnk_wireguard._allowed_ips_buf = + nm_memdup(dst->_lnk_wireguard._allowed_ips_buf, + sizeof(NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len); + + /* all the peers' pointers point into the buffer. They need to be readjusted. */ + for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) { + NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i]; + + if (peer->allowed_ips_len == 0) { + nm_assert(!peer->allowed_ips); + continue; + } + nm_assert(dst->_lnk_wireguard._allowed_ips_buf_len > 0); + nm_assert(src->_lnk_wireguard._allowed_ips_buf); + nm_assert(peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf); + nm_assert( + &peer->allowed_ips[peer->allowed_ips_len] + <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]); + + peer->allowed_ips = + &dst->_lnk_wireguard + ._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf]; + } + + nm_assert(nmp_object_equal(src, dst)); +} + +#define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \ + static void _vt_cmd_plobj_id_copy_##type(NMPlatformObject *_dst, const NMPlatformObject *_src) \ + { \ + plat_type *const dst = (plat_type *) _dst; \ + const plat_type *const src = (const plat_type *) _src; \ + { \ + cmd \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_copy(link, NMPlatformLink, { dst->ifindex = src->ifindex; }); + +_vt_cmd_plobj_id_copy(ip4_address, NMPlatformIP4Address, { + dst->ifindex = src->ifindex; + dst->plen = src->plen; + dst->address = src->address; + dst->peer_address = src->peer_address; +}); + +_vt_cmd_plobj_id_copy(ip6_address, NMPlatformIP6Address, { + dst->ifindex = src->ifindex; + dst->address = src->address; +}); + +_vt_cmd_plobj_id_copy(ip4_route, NMPlatformIP4Route, { + *dst = *src; + nm_assert(nm_platform_ip4_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); +}); + +_vt_cmd_plobj_id_copy(ip6_route, NMPlatformIP6Route, { + *dst = *src; + nm_assert(nm_platform_ip6_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); +}); + +_vt_cmd_plobj_id_copy(routing_rule, NMPlatformRoutingRule, { + *dst = *src; + nm_assert(nm_platform_routing_rule_cmp(dst, src, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0); +}); + +/* Uses internally nmp_object_copy(), hence it also violates the const + * promise for @obj. + * */ +NMPObject * +nmp_object_clone(const NMPObject *obj, gboolean id_only) +{ + NMPObject *dst; + + if (!obj) + return NULL; + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + + dst = _nmp_object_new_from_class(NMP_OBJECT_GET_CLASS(obj)); + nmp_object_copy(dst, obj, id_only); + return dst; +} + +int +nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass, *klass2; + + NM_CMP_SELF(obj1, obj2); + + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), FALSE); + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), FALSE); + + klass = NMP_OBJECT_GET_CLASS(obj1); + nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp); + + klass2 = NMP_OBJECT_GET_CLASS(obj2); + nm_assert(klass); + if (klass != klass2) { + nm_assert(klass2); + NM_CMP_DIRECT(klass->obj_type, klass2->obj_type); + /* resort to pointer comparison */ + NM_CMP_DIRECT_PTR(klass, klass2); + return 0; + } + + if (!klass->cmd_plobj_id_cmp) { + /* the klass doesn't implement ID cmp(). That means, different objects + * never compare equal, but the cmp() according to their pointer value. */ + NM_CMP_DIRECT_PTR(obj1, obj2); + return 0; + } + + return klass->cmd_plobj_id_cmp(&obj1->object, &obj2->object); +} + +#define _vt_cmd_plobj_id_cmp(type, plat_type, cmd) \ + static int _vt_cmd_plobj_id_cmp_##type(const NMPlatformObject *_obj1, \ + const NMPlatformObject *_obj2) \ + { \ + const plat_type *const obj1 = (const plat_type *) _obj1; \ + const plat_type *const obj2 = (const plat_type *) _obj2; \ + \ + NM_CMP_SELF(obj1, obj2); \ + { \ + cmd; \ + } \ + return 0; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_cmp(link, NMPlatformLink, { NM_CMP_FIELD(obj1, obj2, ifindex); }); + +_vt_cmd_plobj_id_cmp(ip4_address, NMPlatformIP4Address, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, plen); + NM_CMP_FIELD(obj1, obj2, address); + /* 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_IN4ADDR_SAME_PREFIX(obj1->peer_address, obj2->peer_address, obj1->plen); +}); + +_vt_cmd_plobj_id_cmp(ip6_address, NMPlatformIP6Address, { + NM_CMP_FIELD(obj1, obj2, ifindex); + /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ + NM_CMP_FIELD_IN6ADDR(obj1, obj2, address); +}); + +_vt_cmd_plobj_id_cmp(qdisc, NMPlatformQdisc, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, parent); +}); + +_vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, { + NM_CMP_FIELD(obj1, obj2, ifindex); + NM_CMP_FIELD(obj1, obj2, handle); +}); + +static int +_vt_cmd_plobj_id_cmp_ip4_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip4_route_cmp((NMPlatformIP4Route *) obj1, + (NMPlatformIP4Route *) obj2, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID); +} + +static int +_vt_cmd_plobj_id_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip6_route_cmp((NMPlatformIP6Route *) obj1, + (NMPlatformIP6Route *) obj2, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID); +} + +static int +_vt_cmd_plobj_id_cmp_routing_rule(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_routing_rule_cmp((NMPlatformRoutingRule *) obj1, + (NMPlatformRoutingRule *) obj2, + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID); +} + +void +nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h) +{ + const NMPClass *klass; + + g_return_if_fail(NMP_OBJECT_IS_VALID(obj)); + + klass = NMP_OBJECT_GET_CLASS(obj); + + nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp); + + if (!klass->cmd_plobj_id_hash_update) { + /* The klass doesn't implement ID compare. It means, to use pointer + * equality. */ + nm_hash_update_val(h, obj); + return; + } + + nm_hash_update_val(h, klass->obj_type); + klass->cmd_plobj_id_hash_update(&obj->object, h); +} + +guint +nmp_object_id_hash(const NMPObject *obj) +{ + NMHashState h; + + if (!obj) + return nm_hash_static(914932607u); + + nm_hash_init(&h, 914932607u); + nmp_object_id_hash_update(obj, &h); + return nm_hash_complete(&h); +} + +#define _vt_cmd_plobj_id_hash_update(type, plat_type, cmd) \ + static void _vt_cmd_plobj_id_hash_update_##type(const NMPlatformObject *_obj, NMHashState *h) \ + { \ + const plat_type *const obj = (const plat_type *) _obj; \ + { \ + cmd; \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +_vt_cmd_plobj_id_hash_update(link, NMPlatformLink, { nm_hash_update_val(h, obj->ifindex); }); + +_vt_cmd_plobj_id_hash_update(ip4_address, NMPlatformIP4Address, { + nm_hash_update_vals( + h, + obj->ifindex, + obj->plen, + obj->address, + /* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */ + nm_utils_ip4_address_clear_host_address(obj->peer_address, obj->plen)); +}); + +_vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, { + nm_hash_update_vals( + h, + obj->ifindex, + /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ + obj->address); +}); + +_vt_cmd_plobj_id_hash_update(ip4_route, NMPlatformIP4Route, { + nm_platform_ip4_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, { + nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(routing_rule, NMPlatformRoutingRule, { + nm_platform_routing_rule_hash_update(obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(qdisc, NMPlatformQdisc, { + nm_hash_update_vals(h, obj->ifindex, obj->parent); +}); + +_vt_cmd_plobj_id_hash_update(tfilter, NMPlatformTfilter, { + nm_hash_update_vals(h, obj->ifindex, obj->handle); +}); + +static void +_vt_cmd_plobj_hash_update_ip4_route(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip4_route_hash_update((const NMPlatformIP4Route *) obj, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + h); +} + +static void +_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj, + NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, + h); +} + +static void +_vt_cmd_plobj_hash_update_routing_rule(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_routing_rule_hash_update((const NMPlatformRoutingRule *) obj, + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, + h); +} + +guint +nmp_object_indirect_id_hash(gconstpointer a) +{ + const NMPObject *const *p_obj = a; + + return nmp_object_id_hash(*p_obj); +} + +gboolean +nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b) +{ + const NMPObject *const *p_obj_a = a; + const NMPObject *const *p_obj_b = b; + + return nmp_object_id_equal(*p_obj_a, *p_obj_b); +} + +/*****************************************************************************/ + +gboolean +nmp_object_is_alive(const NMPObject *obj) +{ + const NMPClass *klass; + + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + klass = NMP_OBJECT_GET_CLASS(obj); + return !klass->cmd_obj_is_alive || klass->cmd_obj_is_alive(obj); +} + +static gboolean +_vt_cmd_obj_is_alive_link(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_LINK(obj)->ifindex > 0 + && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device); +} + +static gboolean +_vt_cmd_obj_is_alive_ipx_address(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_IP_ADDRESS(obj)->ifindex > 0; +} + +static gboolean +_vt_cmd_obj_is_alive_ipx_route(const NMPObject *obj) +{ + /* We want to ignore routes that are RTM_F_CLONED but we still + * let nmp_object_from_nl() create such route objects, instead of + * returning NULL right away. + * + * The idea is, that if we have the same route (according to its id) + * in the cache with !RTM_F_CLONED, an update that changes the route + * to be RTM_F_CLONED must remove the instance. + * + * If nmp_object_from_nl() would just return NULL, we couldn't look + * into the cache to see if it contains a route that now disappears + * (because it changed to be cloned). + * + * Instead we create a dead object, and nmp_cache_update_netlink() + * will remove the old version of the update. + **/ + return NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex > 0 + && !NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED); +} + +static gboolean +_vt_cmd_obj_is_alive_routing_rule(const NMPObject *obj) +{ + return NM_IN_SET(obj->routing_rule.addr_family, AF_INET, AF_INET6); +} + +static gboolean +_vt_cmd_obj_is_alive_qdisc(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_QDISC(obj)->ifindex > 0; +} + +static gboolean +_vt_cmd_obj_is_alive_tfilter(const NMPObject *obj) +{ + return NMP_OBJECT_CAST_TFILTER(obj)->ifindex > 0; +} + +gboolean +nmp_object_is_visible(const NMPObject *obj) +{ + const NMPClass *klass; + + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + klass = NMP_OBJECT_GET_CLASS(obj); + + /* a dead object is never visible. */ + if (klass->cmd_obj_is_alive && !klass->cmd_obj_is_alive(obj)) + return FALSE; + + return !klass->cmd_obj_is_visible || klass->cmd_obj_is_visible(obj); +} + +static gboolean +_vt_cmd_obj_is_visible_link(const NMPObject *obj) +{ + return obj->_link.netlink.is_in_netlink && obj->link.name[0]; +} + +/*****************************************************************************/ + +static const guint8 _supported_cache_ids_object[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + 0, +}; + +static const guint8 _supported_cache_ids_link[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, + 0, +}; + +static const guint8 _supported_cache_ids_ipx_address[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + 0, +}; + +static const guint8 _supported_cache_ids_ipx_route[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + NMP_CACHE_ID_TYPE_DEFAULT_ROUTES, + NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, + 0, +}; + +static const guint8 _supported_cache_ids_routing_rules[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY, + 0, +}; + +/*****************************************************************************/ + +static void +_vt_dedup_obj_destroy(NMDedupMultiObj *obj) +{ + NMPObject * o = (NMPObject *) obj; + const NMPClass *klass; + + nm_assert(o->parent._ref_count == 0); + nm_assert(!o->parent._multi_idx); + + klass = o->_class; + if (klass->cmd_obj_dispose) + klass->cmd_obj_dispose(o); + g_slice_free1(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object), o); +} + +static const NMDedupMultiObj * +_vt_dedup_obj_clone(const NMDedupMultiObj *obj) +{ + return (const NMDedupMultiObj *) nmp_object_clone((const NMPObject *) obj, FALSE); +} + +#define DEDUP_MULTI_OBJ_CLASS_INIT() \ + { \ + .obj_clone = _vt_dedup_obj_clone, .obj_destroy = _vt_dedup_obj_destroy, \ + .obj_full_hash_update = \ + (void (*)(const NMDedupMultiObj *obj, NMHashState *h)) nmp_object_hash_update, \ + .obj_full_equal = (gboolean(*)(const NMDedupMultiObj *obj_a, \ + const NMDedupMultiObj *obj_b)) nmp_object_equal, \ + } + +/*****************************************************************************/ + +static NMDedupMultiIdxType * +_idx_type_get(const NMPCache *cache, NMPCacheIdType cache_id_type) +{ + nm_assert(cache); + nm_assert(cache_id_type > NMP_CACHE_ID_TYPE_NONE); + nm_assert(cache_id_type <= NMP_CACHE_ID_TYPE_MAX); + nm_assert((int) cache_id_type - 1 >= 0); + nm_assert((int) cache_id_type - 1 < G_N_ELEMENTS(cache->idx_types)); + + return (NMDedupMultiIdxType *) &cache->idx_types[cache_id_type - 1]; +} + +gboolean +nmp_cache_use_udev_get(const NMPCache *cache) +{ + g_return_val_if_fail(cache, TRUE); + + return cache->use_udev; +} + +/*****************************************************************************/ + +gboolean +nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave) +{ + nm_assert(NMP_OBJECT_GET_TYPE(slave) == NMP_OBJECT_TYPE_LINK); + + return ifindex_master > 0 && slave->link.master == ifindex_master && slave->link.connected + && nmp_object_is_visible(slave); +} + +/** + * nmp_cache_link_connected_needs_toggle: + * @cache: the platform cache + * @master: the link object, that is checked whether its connected property + * needs to be toggled. + * @potential_slave: (allow-none): an additional link object that is treated + * as if it was inside @cache. If given, it shaddows a link in the cache + * with the same ifindex. + * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave + * is not in the cache. + * + * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported + * from netlink) and master->link.connected. For bond and bridge master, kernel reports + * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead + * a combined @connected flag that shows masters without slaves as down. + * + * Check if the connected flag of @master should be toggled according to the content + * of @cache (including @potential_slave). + * + * Returns: %TRUE, if @master->link.connected should be flipped/toggled. + **/ +gboolean +nmp_cache_link_connected_needs_toggle(const NMPCache * cache, + const NMPObject *master, + const NMPObject *potential_slave, + const NMPObject *ignore_slave) +{ + gboolean is_lower_up = FALSE; + + if (!master || NMP_OBJECT_GET_TYPE(master) != NMP_OBJECT_TYPE_LINK || master->link.ifindex <= 0 + || !nmp_object_is_visible(master) + || !NM_IN_SET(master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND)) + return FALSE; + + /* if native IFF_LOWER_UP is down, link.connected must also be down + * regardless of the slaves. */ + if (!NM_FLAGS_HAS(master->link.n_ifi_flags, IFF_LOWER_UP)) + return !!master->link.connected; + + if (potential_slave && NMP_OBJECT_GET_TYPE(potential_slave) != NMP_OBJECT_TYPE_LINK) + potential_slave = NULL; + + if (potential_slave + && nmp_cache_link_connected_for_slave(master->link.ifindex, potential_slave)) + is_lower_up = TRUE; + else { + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink *link = NULL; + + nmp_cache_iter_for_each_link ( + &iter, + nmp_cache_lookup(cache, nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK)), + &link) { + const NMPObject *obj = NMP_OBJECT_UP_CAST((NMPlatformObject *) link); + + if ((!potential_slave || potential_slave->link.ifindex != link->ifindex) + && ignore_slave != obj + && nmp_cache_link_connected_for_slave(master->link.ifindex, obj)) { + is_lower_up = TRUE; + break; + } + } + } + return !!master->link.connected != is_lower_up; +} + +/** + * nmp_cache_link_connected_needs_toggle_by_ifindex: + * @cache: + * @master_ifindex: the ifindex of a potential master that should be checked + * whether it needs toggling. + * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle(). + * It considers @potential_slave as being inside the cache, replacing an existing + * link with the same ifindex. + * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle(). + * + * The flag obj->link.connected depends on the state of other links in the + * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex + * of a master, check if the cache contains such a master link that needs + * toggling of the connected flag. + * + * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled. + * Otherwise, return the link object from inside the cache with the given ifindex. + * The connected flag of that master should be toggled. + */ +const NMPObject * +nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache, + int master_ifindex, + const NMPObject *potential_slave, + const NMPObject *ignore_slave) +{ + const NMPObject *master; + + if (master_ifindex > 0) { + master = nmp_cache_lookup_link(cache, master_ifindex); + if (nmp_cache_link_connected_needs_toggle(cache, master, potential_slave, ignore_slave)) + return master; + } + return NULL; +} + +/*****************************************************************************/ + +static const NMDedupMultiEntry * +_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj) +{ + const NMDedupMultiEntry *entry; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + entry = + nm_dedup_multi_index_lookup_obj(cache->multi_idx, _idx_type_get(cache, cache_id_type), obj); + nm_assert(!entry + || (NMP_OBJECT_IS_VALID(entry->obj) + && NMP_OBJECT_GET_CLASS(entry->obj) == NMP_OBJECT_GET_CLASS(obj))); + return entry; +} + +static const NMDedupMultiEntry * +_lookup_entry(const NMPCache *cache, const NMPObject *obj) +{ + return _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj) +{ + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(obj, NULL); + g_return_val_if_fail(cache_id_type > NMP_CACHE_ID_TYPE_NONE + && cache_id_type <= NMP_CACHE_ID_TYPE_MAX, + NULL); + + return _lookup_entry_with_idx_type(cache, cache_id_type, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj) +{ + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(obj, NULL); + + return _lookup_entry(cache, obj); +} + +const NMDedupMultiEntry * +nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex) +{ + NMPObject obj_needle; + + g_return_val_if_fail(cache, NULL); + g_return_val_if_fail(ifindex > 0, NULL); + + nmp_object_stackinit_id_link(&obj_needle, ifindex); + return _lookup_entry(cache, &obj_needle); +} + +const NMPObject * +nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj) +{ + return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry(cache, obj)); +} + +const NMPObject * +nmp_cache_lookup_link(const NMPCache *cache, int ifindex) +{ + return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry_link(cache, ifindex)); +} + +/*****************************************************************************/ + +const NMDedupMultiHeadEntry * +nmp_cache_lookup_all(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *select_obj) +{ + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(select_obj)); + + return nm_dedup_multi_index_lookup_head(cache->multi_idx, + _idx_type_get(cache, cache_id_type), + select_obj); +} + +static const NMPLookup * +_L(const NMPLookup *lookup) +{ +#if NM_MORE_ASSERTS + DedupMultiIdxType idx_type; + + nm_assert(lookup); + _dedup_multi_idx_type_init(&idx_type, lookup->cache_id_type); + nm_assert( + idx_type.parent.klass->idx_obj_partitionable((NMDedupMultiIdxType *) &idx_type, + (NMDedupMultiObj *) &lookup->selector_obj)); +#endif + return lookup; +} + +const NMPLookup * +nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type) +{ + nm_assert(lookup); + + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_ROUTING_RULE: + case NMP_OBJECT_TYPE_QDISC: + case NMP_OBJECT_TYPE_TFILTER: + _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE; + return _L(lookup); + default: + nm_assert_not_reached(); + return NULL; + } +} + +const NMPLookup * +nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(ifname); + nm_assert(strlen(ifname) < IFNAMSIZ); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_LINK); + if (g_strlcpy(o->link.name, ifname, sizeof(o->link.name)) >= sizeof(o->link.name)) + g_return_val_if_reached(NULL); + lookup->cache_id_type = NMP_CACHE_ID_TYPE_LINK_BY_IFNAME; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_QDISC, + NMP_OBJECT_TYPE_TFILTER)); + + if (ifindex <= 0) { + return nmp_lookup_init_obj_type(lookup, obj_type); + } + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + o->obj_with_ifindex.ifindex = ifindex; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + o->ip_route.ifindex = 1; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj) +{ + const NMPlatformIP4Route *r4; + const NMPlatformIP6Route *r6; + + nm_assert(lookup); + + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + r4 = NMP_OBJECT_CAST_IP4_ROUTE(obj); + return nmp_lookup_init_ip4_route_by_weak_id(lookup, + r4->network, + r4->plen, + r4->metric, + r4->tos); + case NMP_OBJECT_TYPE_IP6_ROUTE: + r6 = NMP_OBJECT_CAST_IP6_ROUTE(obj); + return nmp_lookup_init_ip6_route_by_weak_id(lookup, + &r6->network, + r6->plen, + r6->metric, + &r6->src, + r6->src_plen); + default: + nm_assert_not_reached(); + return NULL; + } +} + +const NMPLookup * +nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos) +{ + NMPObject *o; + + nm_assert(lookup); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE); + o->ip4_route.ifindex = 1; + o->ip4_route.plen = plen; + o->ip4_route.metric = metric; + if (network) + o->ip4_route.network = network; + o->ip4_route.tos = tos; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_ip6_route_by_weak_id(NMPLookup * lookup, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + NMPObject *o; + + nm_assert(lookup); + + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE); + o->ip6_route.ifindex = 1; + o->ip6_route.plen = plen; + o->ip6_route.metric = metric; + if (network) + o->ip6_route.network = *network; + if (src) + o->ip6_route.src = *src; + o->ip6_route.src_plen = src_plen; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; + return _L(lookup); +} + +const NMPLookup * +nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family) +{ + NMPObject *o; + + nm_assert(lookup); + nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)); + + if (addr_family == AF_UNSPEC) + return nmp_lookup_init_obj_type(lookup, obj_type); + + nm_assert_addr_family(addr_family); + o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type); + NMP_OBJECT_CAST_ROUTING_RULE(o)->addr_family = addr_family; + lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY; + return _L(lookup); +} + +/*****************************************************************************/ + +GArray * +nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry, + NMPObjectType obj_type, + gboolean visible_only) +{ + const NMPClass * klass = nmp_class_from_type(obj_type); + NMDedupMultiIter iter; + const NMPObject *o; + GArray * array; + + g_return_val_if_fail(klass, NULL); + + array = g_array_sized_new(FALSE, FALSE, klass->sizeof_public, head_entry ? head_entry->len : 0); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + nm_assert(NMP_OBJECT_GET_CLASS(o) == klass); + if (visible_only && !nmp_object_is_visible(o)) + continue; + g_array_append_vals(array, &o->object, 1); + } + return array; +} + +/*****************************************************************************/ + +const NMPObject * +nmp_cache_lookup_link_full(const NMPCache * cache, + int ifindex, + const char * ifname, + gboolean visible_only, + NMLinkType link_type, + NMPObjectMatchFn match_fn, + gpointer user_data) +{ + NMPObject obj_needle; + const NMPObject * obj; + NMDedupMultiIter iter; + const NMDedupMultiHeadEntry *head_entry; + const NMPlatformLink * link = NULL; + NMPLookup lookup; + + if (ifindex > 0) { + obj = nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&obj_needle, ifindex)); + + if (!obj || (visible_only && !nmp_object_is_visible(obj)) + || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) + || (ifname && strcmp(obj->link.name, ifname)) + || (match_fn && !match_fn(obj, user_data))) + return NULL; + return obj; + } else if (!ifname && !match_fn) + return NULL; + else { + const NMPObject *obj_best = NULL; + + if (ifname) { + if (strlen(ifname) >= IFNAMSIZ) + return NULL; + nmp_lookup_init_link_by_ifname(&lookup, ifname); + } else + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + + head_entry = nmp_cache_lookup(cache, &lookup); + nmp_cache_iter_for_each_link (&iter, head_entry, &link) { + obj = NMP_OBJECT_UP_CAST(link); + + if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) + continue; + if (visible_only && !nmp_object_is_visible(obj)) + continue; + if (match_fn && !match_fn(obj, user_data)) + continue; + + /* if there are multiple candidates, prefer the visible ones. */ + if (visible_only || nmp_object_is_visible(obj)) + return obj; + if (!obj_best) + obj_best = obj; + } + return obj_best; + } +} + +/*****************************************************************************/ + +static NMDedupMultiIdxMode +_obj_get_add_mode(const NMPObject *obj) +{ + /* new objects are usually appended to the list. Except for + * addresses, which are prepended during `ip address add`. + * + * Actually, for routes it is more complicated, because depending on + * `ip route append`, `ip route replace`, `ip route prepend`, the object + * will be added at the tail, at the front, or even replace an element + * in the list. However, that is handled separately by nmp_cache_update_netlink_route() + * and of no concern here. */ + if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS)) + return NM_DEDUP_MULTI_IDX_MODE_PREPEND; + return NM_DEDUP_MULTI_IDX_MODE_APPEND; +} + +static void +_idxcache_update_order_for_dump(NMPCache *cache, const NMDedupMultiEntry *entry) +{ + const NMPClass * klass; + const guint8 * i_idx_type; + const NMDedupMultiEntry *entry2; + + nm_dedup_multi_entry_reorder(entry, NULL, TRUE); + + klass = NMP_OBJECT_GET_CLASS(entry->obj); + for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { + NMPCacheIdType id_type = *i_idx_type; + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) + continue; + + entry2 = nm_dedup_multi_index_lookup_obj(cache->multi_idx, + _idx_type_get(cache, id_type), + entry->obj); + if (!entry2) + continue; + + nm_assert(entry2 != entry); + nm_assert(entry2->obj == entry->obj); + + nm_dedup_multi_entry_reorder(entry2, NULL, TRUE); + } +} + +static void +_idxcache_update_other_cache_ids(NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj_old, + const NMPObject *obj_new, + gboolean is_dump) +{ + const NMDedupMultiEntry *entry_new; + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_order; + NMDedupMultiIdxType * idx_type; + + nm_assert(obj_new || obj_old); + nm_assert(!obj_new || NMP_OBJECT_GET_TYPE(obj_new) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_old || NMP_OBJECT_GET_TYPE(obj_old) != NMP_OBJECT_TYPE_UNKNOWN); + nm_assert(!obj_old || !obj_new + || NMP_OBJECT_GET_CLASS(obj_new) == NMP_OBJECT_GET_CLASS(obj_old)); + nm_assert(!obj_old || !obj_new || !nmp_object_equal(obj_new, obj_old)); + nm_assert(!obj_new || obj_new == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_new)); + nm_assert(!obj_old || obj_old == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_old)); + + idx_type = _idx_type_get(cache, cache_id_type); + + if (obj_old) { + entry_old = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_old); + if (!obj_new) { + if (entry_old) + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); + return; + } + } else + entry_old = NULL; + + if (obj_new) { + if (obj_old && nm_dedup_multi_idx_type_id_equal(idx_type, obj_old, obj_new) + && nm_dedup_multi_idx_type_partition_equal(idx_type, obj_old, obj_new)) { + /* optimize. We just looked up the @obj_old entry and @obj_new compares equal + * according to idx_obj_id_equal(). entry_new is the same as entry_old. */ + entry_new = entry_old; + } else { + entry_new = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_new); + } + + if (entry_new) + entry_order = entry_new; + else if (entry_old + && nm_dedup_multi_idx_type_partition_equal(idx_type, entry_old->obj, obj_new)) + entry_order = entry_old; + else + entry_order = NULL; + nm_dedup_multi_index_add_full( + cache->multi_idx, + idx_type, + obj_new, + is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : _obj_get_add_mode(obj_new), + is_dump ? NULL : entry_order, + entry_new ?: NM_DEDUP_MULTI_ENTRY_MISSING, + entry_new ? entry_new->head : (entry_order ? entry_order->head : NULL), + &entry_new, + NULL); + +#if NM_MORE_ASSERTS + if (entry_new) { + nm_assert(idx_type->klass->idx_obj_partitionable); + nm_assert(idx_type->klass->idx_obj_partition_equal); + nm_assert(idx_type->klass->idx_obj_partitionable(idx_type, entry_new->obj)); + nm_assert(idx_type->klass->idx_obj_partition_equal(idx_type, + (gpointer) obj_new, + entry_new->obj)); + } +#endif + } else + entry_new = NULL; + + if (entry_old && entry_old != entry_new) + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); +} + +static void +_idxcache_update(NMPCache * cache, + const NMDedupMultiEntry * entry_old, + NMPObject * obj_new, + gboolean is_dump, + const NMDedupMultiEntry **out_entry_new) +{ + const NMPClass * klass; + const guint8 * i_idx_type; + NMDedupMultiIdxType * idx_type_o = _idx_type_get(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE); + const NMDedupMultiEntry *entry_new = NULL; + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + /* we update an object in the cache. + * + * Note that @entry_old MUST be what is currently tracked in multi_idx, and it must + * have the same ID as @obj_new. */ + + nm_assert(cache); + nm_assert(entry_old || obj_new); + nm_assert(!obj_new || nmp_object_is_alive(obj_new)); + nm_assert( + !entry_old + || entry_old + == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, entry_old->obj)); + nm_assert(!obj_new + || entry_old + == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, obj_new)); + nm_assert(!entry_old || entry_old->head->idx_type == idx_type_o); + nm_assert(!entry_old || !obj_new + || nm_dedup_multi_idx_type_partition_equal(idx_type_o, entry_old->obj, obj_new)); + nm_assert(!entry_old || !obj_new + || nm_dedup_multi_idx_type_id_equal(idx_type_o, entry_old->obj, obj_new)); + nm_assert(!entry_old || !obj_new + || (obj_new->parent.klass == ((const NMPObject *) entry_old->obj)->parent.klass + && !obj_new->parent.klass->obj_full_equal((NMDedupMultiObj *) obj_new, + entry_old->obj))); + + /* keep a reference to the pre-existing entry */ + if (entry_old) + obj_old = nmp_object_ref(entry_old->obj); + + /* first update the main index NMP_CACHE_ID_TYPE_OBJECT_TYPE. + * We already know the pre-existing @entry old, so all that + * nm_dedup_multi_index_add_full() effectively does, is update the + * obj reference. + * + * We also get the new boxed object, which we need below. */ + if (obj_new) { + nm_auto_nmpobj NMPObject *obj_old2 = NULL; + + nm_dedup_multi_index_add_full(cache->multi_idx, + idx_type_o, + obj_new, + is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE + : _obj_get_add_mode(obj_new), + NULL, + entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, + NULL, + &entry_new, + (const NMDedupMultiObj **) &obj_old2); + nm_assert(entry_new); + nm_assert(obj_old == obj_old2); + nm_assert(!entry_old || entry_old == entry_new); + } else + nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old); + + /* now update all other indexes. We know the previously boxed entry, and the + * newly boxed one. */ + klass = NMP_OBJECT_GET_CLASS(entry_new ? entry_new->obj : obj_old); + for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { + NMPCacheIdType id_type = *i_idx_type; + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) + continue; + _idxcache_update_other_cache_ids(cache, + id_type, + obj_old, + entry_new ? entry_new->obj : NULL, + is_dump); + } + + NM_SET_OUT(out_entry_new, entry_new); +} + +NMPCacheOpsType +nmp_cache_remove(NMPCache * cache, + const NMPObject * obj_needle, + gboolean equals_by_ptr, + gboolean only_dirty, + const NMPObject **out_obj_old) +{ + const NMDedupMultiEntry *entry_old; + const NMPObject * obj_old; + + entry_old = _lookup_entry(cache, obj_needle); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (equals_by_ptr && obj_old != obj_needle) { + /* We found an identical object, but we only delete it if it's the same pointer as + * @obj_needle. */ + return NMP_CACHE_OPS_UNCHANGED; + } + if (only_dirty && !entry_old->dirty) { + /* the entry is not dirty. Skip. */ + return NMP_CACHE_OPS_UNCHANGED; + } + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + return NMP_CACHE_OPS_REMOVED; +} + +NMPCacheOpsType +nmp_cache_remove_netlink(NMPCache * cache, + const NMPObject * obj_needle, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new = NULL; + const NMPObject * obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + + entry_old = _lookup_entry(cache, obj_needle); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + if (NMP_OBJECT_GET_TYPE(obj_needle) == NMP_OBJECT_TYPE_LINK) { + /* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be + * removed from netlink. Link objects are alive without being in netlink when they + * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink + * flag. */ + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (!obj_old->_link.netlink.is_in_netlink) { + nm_assert(obj_old->_link.udev.device); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (!obj_old->_link.udev.device) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + obj_new->_link.netlink.is_in_netlink = FALSE; + + _nmp_object_fixup_link_master_connected(&obj_new, NULL, cache); + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; + } + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, NULL); + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + return NMP_CACHE_OPS_REMOVED; +} + +/** + * nmp_cache_update_netlink: + * @cache: the platform cache + * @obj_hand_over: a #NMPObject instance as received from netlink and created via + * nmp_object_from_nl(). Especially for link, it must not have the udev + * replated fields set. + * This instance will be modified and might be put into the cache. When + * calling nmp_cache_update_netlink() you hand @obj over to the cache. + * Except, that the cache will increment the ref count as appropriate. You + * must still unref the obj to release your part of the ownership. + * @is_dump: whether this update comes during a dump of object of the same kind. + * kernel dumps objects in a certain order, which matters especially for routes. + * Before a dump we mark all objects as dirty, and remove all untouched objects + * afterwards. Hence, during a dump, every update should move the object to the + * end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, + * instead of NM_DEDUP_MULTI_IDX_MODE_APPEND. + * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over, + * that was in the cache before update. If an object is returned, the caller must + * unref it afterwards. + * @out_obj_new: (allow-none) (out): return the object from the cache after update. + * The caller must unref this object. + * + * Returns: how the cache changed. + * + * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old + * and @out_obj_new will be set accordingly. + **/ +NMPCacheOpsType +nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new; + const NMPObject * obj_old; + gboolean is_alive; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over)); + /* A link object from netlink must have the udev related fields unset. + * We could implement to handle that, but there is no need to support such + * a use-case */ + nm_assert(NMP_OBJECT_GET_TYPE(obj_hand_over) != NMP_OBJECT_TYPE_LINK + || (!obj_hand_over->_link.udev.device && !obj_hand_over->link.driver)); + nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over); + + entry_old = _lookup_entry(cache, obj_hand_over); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + + if (!nmp_object_is_alive(obj_hand_over)) { + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) { + _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache); + _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev); + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_ADDED; + } + + obj_old = entry_old->obj; + + if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) { + if (!obj_hand_over->_link.netlink.is_in_netlink) { + if (!obj_old->_link.netlink.is_in_netlink) { + nm_assert(obj_old->_link.udev.device); + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + if (obj_old->_link.udev.device) { + /* @obj_hand_over is not in netlink. + * + * This is similar to nmp_cache_remove_netlink(), but there we preserve the + * preexisting netlink properties. The use case of that is when kernel_get_object() + * cannot load an object (based on the id of a needle). + * + * Here we keep the data provided from @obj_hand_over. The usecase is when receiving + * a valid @obj_hand_over instance from netlink with RTM_DELROUTE. + */ + is_alive = TRUE; + } else + is_alive = FALSE; + } else + is_alive = TRUE; + + if (is_alive) { + _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache); + + /* Merge the netlink parts with what we have from udev. */ + udev_device_unref(obj_hand_over->_link.udev.device); + obj_hand_over->_link.udev.device = + obj_old->_link.udev.device ? udev_device_ref(obj_old->_link.udev.device) : NULL; + _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev); + + if (obj_hand_over->_link.netlink.lnk) { + nm_auto_nmpobj const NMPObject *lnk_old = obj_hand_over->_link.netlink.lnk; + + /* let's dedup/intern the lnk object. */ + obj_hand_over->_link.netlink.lnk = + nm_dedup_multi_index_obj_intern(cache->multi_idx, lnk_old); + } + } + } else + is_alive = nmp_object_is_alive(obj_hand_over); + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (!is_alive) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + if (nmp_object_equal(obj_old, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump(cache, entry_old); + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; +} + +NMPCacheOpsType +nmp_cache_update_netlink_route(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + guint16 nlmsgflags, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + const NMPObject **out_obj_replace, + gboolean * out_resync_required) +{ + NMDedupMultiIter iter; + const NMDedupMultiEntry * entry_old; + const NMDedupMultiEntry * entry_new; + const NMDedupMultiEntry * entry_cur; + const NMDedupMultiEntry * entry_replace; + const NMDedupMultiHeadEntry *head_entry; + gboolean is_alive; + NMPCacheOpsType ops_type = NMP_CACHE_OPS_UNCHANGED; + gboolean resync_required; + + nm_assert(cache); + nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over)); + nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over)); + /* A link object from netlink must have the udev related fields unset. + * We could implement to handle that, but there is no need to support such + * a use-case */ + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_hand_over), + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over); + + entry_old = _lookup_entry(cache, obj_hand_over); + entry_new = NULL; + + NM_SET_OUT(out_obj_old, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_old))); + + if (!entry_old) { + if (!nmp_object_is_alive(obj_hand_over)) + goto update_done; + + _idxcache_update(cache, NULL, obj_hand_over, is_dump, &entry_new); + ops_type = NMP_CACHE_OPS_ADDED; + goto update_done; + } + + is_alive = nmp_object_is_alive(obj_hand_over); + + if (!is_alive) { + /* the update would make @entry_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + ops_type = NMP_CACHE_OPS_REMOVED; + goto update_done; + } + + if (nmp_object_equal(entry_old->obj, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump(cache, entry_old); + nm_dedup_multi_entry_set_dirty(entry_old, FALSE); + goto update_done; + } + + _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new); + ops_type = NMP_CACHE_OPS_UPDATED; + +update_done: + NM_SET_OUT(out_obj_new, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_new))); + + /* a RTM_GETROUTE event may signal that another object was replaced. + * Find out whether that is the case and return it as @obj_replaced. + * + * Also, fixup the order of @entry_new within NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID + * index. For most parts, we don't care about the order of objects (including routes). + * But NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID we must keep in the correct order, to + * properly find @obj_replaced. */ + resync_required = FALSE; + entry_replace = NULL; + if (is_dump) + goto out; + + if (!entry_new) { + if (NM_FLAGS_HAS(nlmsgflags, NLM_F_REPLACE) + && nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj_hand_over)) { + /* hm. @obj_hand_over was not added, meaning it was not alive. + * However, we track some other objects with the same weak-id. + * It's unclear what that means. To be sure, resync. */ + resync_required = TRUE; + } + goto out; + } + + /* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID + * index. For all other indexes their order becomes messed up. */ + entry_cur = + _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj); + if (!entry_cur) { + nm_assert_not_reached(); + goto out; + } + nm_assert(entry_cur->obj == entry_new->obj); + + head_entry = entry_cur->head; + nm_assert(head_entry + == nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_cur->obj)); + + if (head_entry->len == 1) { + /* there is only one object, and we expect it to be @obj_new. */ + nm_assert(nm_dedup_multi_head_entry_get_idx(head_entry, 0) == entry_cur); + goto out; + } + + switch (nlmsgflags & (NLM_F_REPLACE | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND)) { + case NLM_F_REPLACE: + /* ip route change */ + + /* get the first element (but skip @obj_new). */ + nm_dedup_multi_iter_init(&iter, head_entry); + if (!nm_dedup_multi_iter_next(&iter)) + nm_assert_not_reached(); + if (iter.current == entry_cur) { + if (!nm_dedup_multi_iter_next(&iter)) + nm_assert_not_reached(); + } + entry_replace = iter.current; + + nm_assert(entry_replace && entry_cur != entry_replace); + + nm_dedup_multi_entry_reorder(entry_cur, entry_replace, FALSE); + break; + case NLM_F_CREATE | NLM_F_APPEND: + /* ip route append */ + nm_dedup_multi_entry_reorder(entry_cur, NULL, TRUE); + break; + case NLM_F_CREATE: + /* ip route prepend */ + nm_dedup_multi_entry_reorder(entry_cur, NULL, FALSE); + break; + default: + /* this is an unexpected case, probably a bug that we need to handle better. */ + resync_required = TRUE; + break; + } + +out: + NM_SET_OUT(out_obj_replace, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_replace))); + NM_SET_OUT(out_resync_required, resync_required); + return ops_type; +} + +NMPCacheOpsType +nmp_cache_update_link_udev(NMPCache * cache, + int ifindex, + struct udev_device *udevice, + const NMPObject ** out_obj_old, + const NMPObject ** out_obj_new) +{ + const NMPObject *obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + const NMDedupMultiEntry * entry_old; + const NMDedupMultiEntry * entry_new; + + entry_old = nmp_cache_lookup_entry_link(cache, ifindex); + + if (!entry_old) { + if (!udevice) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_new = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL); + obj_new->link.ifindex = ifindex; + obj_new->_link.udev.device = udev_device_ref(udevice); + + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, NULL, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_ADDED; + } else { + obj_old = entry_old->obj; + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + + if (obj_old->_link.udev.device == udevice) { + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (!udevice && !obj_old->_link.netlink.is_in_netlink) { + /* the update would make @obj_old invalid. Remove it. */ + _idxcache_update(cache, entry_old, NULL, FALSE, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_REMOVED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + + udev_device_unref(obj_new->_link.udev.device); + obj_new->_link.udev.device = udevice ? udev_device_ref(udevice) : NULL; + + _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev); + + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; + } +} + +NMPCacheOpsType +nmp_cache_update_link_master_connected(NMPCache * cache, + int ifindex, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new) +{ + const NMDedupMultiEntry *entry_old; + const NMDedupMultiEntry *entry_new = NULL; + const NMPObject * obj_old; + nm_auto_nmpobj NMPObject *obj_new = NULL; + + entry_old = nmp_cache_lookup_entry_link(cache, ifindex); + + if (!entry_old) { + NM_SET_OUT(out_obj_old, NULL); + NM_SET_OUT(out_obj_new, NULL); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_old = entry_old->obj; + + if (!nmp_cache_link_connected_needs_toggle(cache, obj_old, NULL, NULL)) { + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old)); + return NMP_CACHE_OPS_UNCHANGED; + } + + obj_new = nmp_object_clone(obj_old, FALSE); + obj_new->link.connected = !obj_old->link.connected; + + NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old)); + _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new); + NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj)); + return NMP_CACHE_OPS_UPDATED; +} + +/*****************************************************************************/ + +void +nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + + nm_assert(cache); + nm_assert(lookup); + + head_entry = nmp_cache_lookup(cache, lookup); + + nm_dedup_multi_iter_init(&iter, head_entry); + while (nm_dedup_multi_iter_next(&iter)) { + const NMDedupMultiEntry *main_entry; + + main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup); + + nm_dedup_multi_entry_set_dirty(main_entry, TRUE); + } +} + +/*****************************************************************************/ + +NMPCache * +nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev) +{ + NMPCache *cache = g_slice_new0(NMPCache); + guint i; + + for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) + _dedup_multi_idx_type_init((DedupMultiIdxType *) _idx_type_get(cache, i), i); + + cache->multi_idx = nm_dedup_multi_index_ref(multi_idx); + + cache->use_udev = !!use_udev; + return cache; +} + +void +nmp_cache_free(NMPCache *cache) +{ + guint i; + + for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) + nm_dedup_multi_index_remove_idx(cache->multi_idx, _idx_type_get(cache, i)); + + nm_dedup_multi_index_unref(cache->multi_idx); + + g_slice_free(NMPCache, cache); +} + +/*****************************************************************************/ + +void +nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache) +{} + +/*****************************************************************************/ + +/* below, ensure that addr_family get's automatically initialize to AF_UNSPEC. */ +G_STATIC_ASSERT(AF_UNSPEC == 0); + +typedef const char *(*CmdPlobjToStringFunc)(const NMPlatformObject *obj, char *buf, gsize len); +typedef const char *(*CmdPlobjToStringIdFunc)(const NMPlatformObject *obj, char *buf, gsize len); +typedef void (*CmdPlobjHashUpdateFunc)(const NMPlatformObject *obj, NMHashState *h); +typedef int (*CmdPlobjCmpFunc)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); + +const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { + [NMP_OBJECT_TYPE_LINK - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LINK, + .sizeof_data = sizeof(NMPObjectLink), + .sizeof_public = sizeof(NMPlatformLink), + .obj_type_name = "link", + .rtm_gettype = RTM_GETLINK, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_LINK, + .signal_type = NM_PLATFORM_SIGNAL_LINK_CHANGED, + .supported_cache_ids = _supported_cache_ids_link, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_link, + .cmd_obj_cmp = _vt_cmd_obj_cmp_link, + .cmd_obj_copy = _vt_cmd_obj_copy_link, + .cmd_obj_dispose = _vt_cmd_obj_dispose_link, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_link, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_link, + .cmd_obj_to_string = _vt_cmd_obj_to_string_link, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_link, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_link, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_link, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_link, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_link_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_link_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_link_cmp, + }, + [NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP4_ADDRESS, + .sizeof_data = sizeof(NMPObjectIP4Address), + .sizeof_public = sizeof(NMPlatformIP4Address), + .obj_type_name = "ip4-address", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETADDR, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_address, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_address, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_address, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_address_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_ip4_address_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip4_address_cmp, + }, + [NMP_OBJECT_TYPE_IP6_ADDRESS + - 1] = {.parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP6_ADDRESS, + .sizeof_data = sizeof(NMPObjectIP6Address), + .sizeof_public = sizeof(NMPlatformIP6Address), + .obj_type_name = "ip6-address", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETADDR, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_address, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_address, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_address, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_address_to_string, + .cmd_plobj_hash_update = + (CmdPlobjHashUpdateFunc) nm_platform_ip6_address_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip6_address_cmp}, + [NMP_OBJECT_TYPE_IP4_ROUTE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE, + .sizeof_data = sizeof(NMPObjectIP4Route), + .sizeof_public = sizeof(NMPlatformIP4Route), + .obj_type_name = "ip4-route", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETROUTE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_route, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_route, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_route, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip4_route_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_route_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip4_route, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip4_route_cmp_full, + }, + [NMP_OBJECT_TYPE_IP6_ROUTE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP6_ROUTE, + .sizeof_data = sizeof(NMPObjectIP6Route), + .sizeof_public = sizeof(NMPlatformIP6Route), + .obj_type_name = "ip6-route", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETROUTE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + .supported_cache_ids = _supported_cache_ids_ipx_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_route, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_route, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_route, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip6_route_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_route_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip6_route, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip6_route_cmp_full, + }, + [NMP_OBJECT_TYPE_ROUTING_RULE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_ROUTING_RULE, + .sizeof_data = sizeof(NMPObjectRoutingRule), + .sizeof_public = sizeof(NMPlatformRoutingRule), + .obj_type_name = "routing-rule", + .rtm_gettype = RTM_GETRULE, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, + .signal_type = NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, + .supported_cache_ids = _supported_cache_ids_routing_rules, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_routing_rule, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_routing_rule, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_routing_rule, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_routing_rule, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_routing_rule_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_routing_rule_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_routing_rule, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_routing_rule_cmp_full, + }, + [NMP_OBJECT_TYPE_QDISC - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_QDISC, + .sizeof_data = sizeof(NMPObjectQdisc), + .sizeof_public = sizeof(NMPlatformQdisc), + .obj_type_name = "qdisc", + .rtm_gettype = RTM_GETQDISC, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_QDISC, + .signal_type = NM_PLATFORM_SIGNAL_QDISC_CHANGED, + .supported_cache_ids = _supported_cache_ids_object, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_qdisc, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_qdisc, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_qdisc, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_qdisc, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_qdisc_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_qdisc_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_qdisc_cmp, + }, + [NMP_OBJECT_TYPE_TFILTER - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_TFILTER, + .sizeof_data = sizeof(NMPObjectTfilter), + .sizeof_public = sizeof(NMPlatformTfilter), + .obj_type_name = "tfilter", + .rtm_gettype = RTM_GETTFILTER, + .signal_type_id = NM_PLATFORM_SIGNAL_ID_TFILTER, + .signal_type = NM_PLATFORM_SIGNAL_TFILTER_CHANGED, + .supported_cache_ids = _supported_cache_ids_object, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_tfilter, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_tfilter, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_tfilter, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_tfilter, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_tfilter_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_tfilter_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_tfilter_cmp, + }, + [NMP_OBJECT_TYPE_LNK_BRIDGE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_BRIDGE, + .sizeof_data = sizeof(NMPObjectLnkBridge), + .sizeof_public = sizeof(NMPlatformLnkBridge), + .obj_type_name = "bridge", + .lnk_link_type = NM_LINK_TYPE_BRIDGE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_bridge_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_bridge_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_bridge_cmp, + }, + [NMP_OBJECT_TYPE_LNK_GRE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_GRE, + .sizeof_data = sizeof(NMPObjectLnkGre), + .sizeof_public = sizeof(NMPlatformLnkGre), + .obj_type_name = "gre", + .lnk_link_type = NM_LINK_TYPE_GRE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp, + }, + [NMP_OBJECT_TYPE_LNK_GRETAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_GRETAP, + .sizeof_data = sizeof(NMPObjectLnkGre), + .sizeof_public = sizeof(NMPlatformLnkGre), + .obj_type_name = "gretap", + .lnk_link_type = NM_LINK_TYPE_GRETAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp, + }, + [NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_INFINIBAND, + .sizeof_data = sizeof(NMPObjectLnkInfiniband), + .sizeof_public = sizeof(NMPlatformLnkInfiniband), + .obj_type_name = "infiniband", + .lnk_link_type = NM_LINK_TYPE_INFINIBAND, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_infiniband_to_string, + .cmd_plobj_hash_update = + (CmdPlobjHashUpdateFunc) nm_platform_lnk_infiniband_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_infiniband_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6TNL, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6tnl", + .lnk_link_type = NM_LINK_TYPE_IP6TNL, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6GRE - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRE, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6gre", + .lnk_link_type = NM_LINK_TYPE_IP6GRE, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRETAP, + .sizeof_data = sizeof(NMPObjectLnkIp6Tnl), + .sizeof_public = sizeof(NMPlatformLnkIp6Tnl), + .obj_type_name = "ip6gretap", + .lnk_link_type = NM_LINK_TYPE_IP6GRETAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp, + }, + [NMP_OBJECT_TYPE_LNK_IPIP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_IPIP, + .sizeof_data = sizeof(NMPObjectLnkIpIp), + .sizeof_public = sizeof(NMPlatformLnkIpIp), + .obj_type_name = "ipip", + .lnk_link_type = NM_LINK_TYPE_IPIP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_ipip_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ipip_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_ipip_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACSEC - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACSEC, + .sizeof_data = sizeof(NMPObjectLnkMacsec), + .sizeof_public = sizeof(NMPlatformLnkMacsec), + .obj_type_name = "macsec", + .lnk_link_type = NM_LINK_TYPE_MACSEC, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macsec_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macsec_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macsec_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACVLAN, + .sizeof_data = sizeof(NMPObjectLnkMacvlan), + .sizeof_public = sizeof(NMPlatformLnkMacvlan), + .obj_type_name = "macvlan", + .lnk_link_type = NM_LINK_TYPE_MACVLAN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_MACVTAP, + .sizeof_data = sizeof(NMPObjectLnkMacvtap), + .sizeof_public = sizeof(NMPlatformLnkMacvlan), + .obj_type_name = "macvtap", + .lnk_link_type = NM_LINK_TYPE_MACVTAP, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_SIT - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_SIT, + .sizeof_data = sizeof(NMPObjectLnkSit), + .sizeof_public = sizeof(NMPlatformLnkSit), + .obj_type_name = "sit", + .lnk_link_type = NM_LINK_TYPE_SIT, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_sit_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_sit_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_sit_cmp, + }, + [NMP_OBJECT_TYPE_LNK_TUN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_TUN, + .sizeof_data = sizeof(NMPObjectLnkTun), + .sizeof_public = sizeof(NMPlatformLnkTun), + .obj_type_name = "tun", + .lnk_link_type = NM_LINK_TYPE_TUN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_tun_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_tun_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_tun_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VLAN, + .sizeof_data = sizeof(NMPObjectLnkVlan), + .sizeof_public = sizeof(NMPlatformLnkVlan), + .obj_type_name = "vlan", + .lnk_link_type = NM_LINK_TYPE_VLAN, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_lnk_vlan, + .cmd_obj_cmp = _vt_cmd_obj_cmp_lnk_vlan, + .cmd_obj_copy = _vt_cmd_obj_copy_lnk_vlan, + .cmd_obj_dispose = _vt_cmd_obj_dispose_lnk_vlan, + .cmd_obj_to_string = _vt_cmd_obj_to_string_lnk_vlan, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VRF - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VRF, + .sizeof_data = sizeof(NMPObjectLnkVrf), + .sizeof_public = sizeof(NMPlatformLnkVrf), + .obj_type_name = "vrf", + .lnk_link_type = NM_LINK_TYPE_VRF, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vrf_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vrf_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vrf_cmp, + }, + [NMP_OBJECT_TYPE_LNK_VXLAN - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_VXLAN, + .sizeof_data = sizeof(NMPObjectLnkVxlan), + .sizeof_public = sizeof(NMPlatformLnkVxlan), + .obj_type_name = "vxlan", + .lnk_link_type = NM_LINK_TYPE_VXLAN, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_vxlan_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vxlan_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_vxlan_cmp, + }, + [NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_WIREGUARD, + .sizeof_data = sizeof(NMPObjectLnkWireGuard), + .sizeof_public = sizeof(NMPlatformLnkWireGuard), + .obj_type_name = "wireguard", + .lnk_link_type = NM_LINK_TYPE_WIREGUARD, + .cmd_obj_hash_update = _vt_cmd_obj_hash_update_lnk_wireguard, + .cmd_obj_cmp = _vt_cmd_obj_cmp_lnk_wireguard, + .cmd_obj_copy = _vt_cmd_obj_copy_lnk_wireguard, + .cmd_obj_dispose = _vt_cmd_obj_dispose_lnk_wireguard, + .cmd_obj_to_string = _vt_cmd_obj_to_string_lnk_wireguard, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_wireguard_to_string, + .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_wireguard_hash_update, + .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_wireguard_cmp, + }, +}; diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h new file mode 100644 index 00000000..021829db --- /dev/null +++ b/src/libnm-platform/nmp-object.h @@ -0,0 +1,1163 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#ifndef __NMP_OBJECT_H__ +#define __NMP_OBJECT_H__ + +#include <netinet/in.h> + +#include "libnm-glib-aux/nm-obj.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "nm-platform.h" + +struct udev_device; + +/*****************************************************************************/ + +/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice, + * these are the same types. */ +G_STATIC_ASSERT(sizeof(long long) == sizeof(gint64)); + +typedef struct { + /* like "struct __kernel_timespec". */ + gint64 tv_sec; + gint64 tv_nsec; +} NMPTimespec64; + +/*****************************************************************************/ + +typedef union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +} NMSockAddrUnion; + +G_STATIC_ASSERT(sizeof(NMSockAddrUnion) == sizeof(((NMSockAddrUnion *) NULL)->in6)); + +/* we initialize the largest union member, to ensure that all fields are initialized. */ + +#define NM_SOCK_ADDR_UNION_INIT_UNSPEC \ + { \ + .in6 = { \ + .sin6_family = AF_UNSPEC, \ + }, \ + } + +int nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b); + +void nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h); + +void nm_sock_addr_union_cpy(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */); + +void nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst, + gconstpointer src /* unaligned (const NMSockAddrUnion *) */, + gsize src_len); + +const char *nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len); + +/*****************************************************************************/ + +typedef struct { + NMIPAddr addr; + guint8 family; + guint8 mask; +} NMPWireGuardAllowedIP; + +typedef struct _NMPWireGuardPeer { + NMSockAddrUnion endpoint; + + NMPTimespec64 last_handshake_time; + + guint64 rx_bytes; + guint64 tx_bytes; + + union { + const NMPWireGuardAllowedIP *allowed_ips; + guint _construct_idx_start; + }; + union { + guint allowed_ips_len; + guint _construct_idx_end; + }; + + guint16 persistent_keepalive_interval; + + guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; + guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN]; +} NMPWireGuardPeer; + +/*****************************************************************************/ + +typedef enum { /*< skip >*/ + NMP_OBJECT_TO_STRING_ID, + NMP_OBJECT_TO_STRING_PUBLIC, + NMP_OBJECT_TO_STRING_ALL, +} NMPObjectToStringMode; + +typedef enum { /*< skip >*/ + NMP_CACHE_OPS_UNCHANGED = NM_PLATFORM_SIGNAL_NONE, + NMP_CACHE_OPS_ADDED = NM_PLATFORM_SIGNAL_ADDED, + NMP_CACHE_OPS_UPDATED = NM_PLATFORM_SIGNAL_CHANGED, + NMP_CACHE_OPS_REMOVED = NM_PLATFORM_SIGNAL_REMOVED, +} NMPCacheOpsType; + +/* The NMPCacheIdType are the different index types. + * + * An object of a certain object-type, can be candidate to being + * indexed by a certain NMPCacheIdType or not. For example, all + * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE, + * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT. + * + * Of one index type, there can be multiple indexes or not. + * For example, of the index type NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX there + * are multiple instances (for different route/addresses, v4/v6, per-ifindex). + * + * But one object, can only be indexed by one particular index of a + * type. For example, a certain address instance is only indexed by + * the index NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX with + * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible. + * */ +typedef enum { /*< skip >*/ + NMP_CACHE_ID_TYPE_NONE, + + /* all the objects of a certain type. + * + * This index is special. It is the only one that contains *all* object. + * Other indexes may consider some object as non "partitionable", hence + * they don't track all objects. + * + * Hence, this index type is used when looking at all objects (still + * partitioned by type). + * + * Also, note that links may be considered invisible. This index type + * expose all links, even invisible ones. For addresses/routes, this + * distinction doesn't exist, as all addresses/routes that are alive + * are visible as well. */ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + + /* index for the link objects by ifname. */ + NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, + + /* indices for the visible default-routes, ignoring ifindex. + * This index only contains two partitions: all visible default-routes, + * separate for IPv4 and IPv6. */ + NMP_CACHE_ID_TYPE_DEFAULT_ROUTES, + + /* all the objects that have an ifindex (by object-type) for an ifindex. */ + NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, + + /* Consider all the destination fields of a route, that is, the ID without the ifindex + * and gateway (meaning: network/plen,metric). + * The reason for this is that `ip route change` can replace an existing route + * and modify its ifindex/gateway. Effectively, that means it deletes an existing + * route and adds a different one (as the ID of the route changes). However, it only + * sends one RTM_NEWADDR notification without notifying about the deletion. We detect + * that by having this index to contain overlapping routes which require special + * cache-resync. */ + NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, + + /* a filter for objects that track an explicit address family. + * + * Note that currently on NMPObjectRoutingRule is indexed by this filter. */ + NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY, + + __NMP_CACHE_ID_TYPE_MAX, + NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1, +} NMPCacheIdType; + +typedef struct { + NMDedupMultiObjClass parent; + const char * obj_type_name; + const char * signal_type; + const guint8 * supported_cache_ids; + int sizeof_data; + int sizeof_public; + int addr_family; + int rtm_gettype; + NMPObjectType obj_type; + NMPlatformSignalIdType signal_type_id; + + /* Only for NMPObjectLnk* types. */ + NMLinkType lnk_link_type; + + void (*cmd_obj_hash_update)(const NMPObject *obj, NMHashState *h); + int (*cmd_obj_cmp)(const NMPObject *obj1, const NMPObject *obj2); + void (*cmd_obj_copy)(NMPObject *dst, const NMPObject *src); + void (*cmd_obj_dispose)(NMPObject *obj); + gboolean (*cmd_obj_is_alive)(const NMPObject *obj); + gboolean (*cmd_obj_is_visible)(const NMPObject *obj); + const char *(*cmd_obj_to_string)(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size); + + /* functions that operate on NMPlatformObject */ + void (*cmd_plobj_id_copy)(NMPlatformObject *dst, const NMPlatformObject *src); + int (*cmd_plobj_id_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); + void (*cmd_plobj_id_hash_update)(const NMPlatformObject *obj, NMHashState *h); + const char *(*cmd_plobj_to_string_id)(const NMPlatformObject *obj, char *buf, gsize buf_size); + const char *(*cmd_plobj_to_string)(const NMPlatformObject *obj, char *buf, gsize len); + void (*cmd_plobj_hash_update)(const NMPlatformObject *obj, NMHashState *h); + int (*cmd_plobj_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2); +} NMPClass; + +extern const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX]; + +typedef struct { + NMPlatformLink _public; + + struct { + bool is_in_netlink; + + /* Additional data that depends on the link-type (IFLA_INFO_DATA) */ + const NMPObject *lnk; + } netlink; + + struct { + /* note that "struct udev_device" references the library context + * "struct udev", but doesn't own it. + * + * Hence, the udev.device shall not be used after the library + * context is destroyed. + * + * In case of NMPObjectLink instances that you obtained from the + * platform cache, that means that you shall no keep references + * to those instances that outlife the NMPlatform instance. + * + * In practice, the requirement is less strict and you'll be even + * fine if the platform instance (and the "struct udev" instance) + * are already destroyed while you still hold onto a reference to + * the NMPObjectLink instance. Just don't make use of udev functions + * that cause access to the udev library context. + */ + struct udev_device *device; + } udev; + + /* Auxiliary data object for Wi-Fi and WPAN */ + GObject *ext_data; + + /* FIXME: not every NMPObjectLink should pay the price for tracking + * the wireguard family id. This should be tracked via ext_data, which + * would be exactly the right place. */ + int wireguard_family_id; +} NMPObjectLink; + +typedef struct { + NMPlatformLnkBridge _public; +} NMPObjectLnkBridge; + +typedef struct { + NMPlatformLnkGre _public; +} NMPObjectLnkGre; + +typedef struct { + NMPlatformLnkInfiniband _public; +} NMPObjectLnkInfiniband; + +typedef struct { + NMPlatformLnkIp6Tnl _public; +} NMPObjectLnkIp6Tnl; + +typedef struct { + NMPlatformLnkIpIp _public; +} NMPObjectLnkIpIp; + +typedef struct { + NMPlatformLnkMacsec _public; +} NMPObjectLnkMacsec; + +typedef struct { + NMPlatformLnkMacvlan _public; +} NMPObjectLnkMacvlan; + +typedef NMPObjectLnkMacvlan NMPObjectLnkMacvtap; + +typedef struct { + NMPlatformLnkSit _public; +} NMPObjectLnkSit; + +typedef struct { + NMPlatformLnkTun _public; +} NMPObjectLnkTun; + +typedef struct { + NMPlatformLnkVlan _public; + + guint n_ingress_qos_map; + guint n_egress_qos_map; + const NMVlanQosMapping *ingress_qos_map; + const NMVlanQosMapping *egress_qos_map; +} NMPObjectLnkVlan; + +typedef struct { + NMPlatformLnkVrf _public; +} NMPObjectLnkVrf; + +typedef struct { + NMPlatformLnkVxlan _public; +} NMPObjectLnkVxlan; + +typedef struct { + NMPlatformLnkWireGuard _public; + const NMPWireGuardPeer * peers; + const NMPWireGuardAllowedIP *_allowed_ips_buf; + guint peers_len; + guint _allowed_ips_buf_len; +} NMPObjectLnkWireGuard; + +typedef struct { + NMPlatformIP4Address _public; +} NMPObjectIP4Address; + +typedef struct { + NMPlatformIP4Route _public; +} NMPObjectIP4Route; + +typedef struct { + NMPlatformIP6Address _public; +} NMPObjectIP6Address; + +typedef struct { + NMPlatformIP6Route _public; +} NMPObjectIP6Route; + +typedef struct { + NMPlatformRoutingRule _public; +} NMPObjectRoutingRule; + +typedef struct { + NMPlatformQdisc _public; +} NMPObjectQdisc; + +typedef struct { + NMPlatformTfilter _public; +} NMPObjectTfilter; + +struct _NMPObject { + union { + NMDedupMultiObj parent; + const NMPClass *_class; + }; + union { + NMPlatformObject object; + + NMPlatformObjWithIfindex obj_with_ifindex; + + NMPlatformLink link; + NMPObjectLink _link; + + NMPlatformLnkBridge lnk_bridge; + NMPObjectLnkBridge _lnk_bridge; + + NMPlatformLnkGre lnk_gre; + NMPObjectLnkGre _lnk_gre; + + NMPlatformLnkInfiniband lnk_infiniband; + NMPObjectLnkInfiniband _lnk_infiniband; + + NMPlatformLnkIpIp lnk_ipip; + NMPObjectLnkIpIp _lnk_ipip; + + NMPlatformLnkIp6Tnl lnk_ip6tnl; + NMPObjectLnkIp6Tnl _lnk_ip6tnl; + + NMPlatformLnkMacsec lnk_macsec; + NMPObjectLnkMacsec _lnk_macsec; + + NMPlatformLnkMacvlan lnk_macvlan; + NMPObjectLnkMacvlan _lnk_macvlan; + + NMPlatformLnkSit lnk_sit; + NMPObjectLnkSit _lnk_sit; + + NMPlatformLnkTun lnk_tun; + NMPObjectLnkTun _lnk_tun; + + NMPlatformLnkVlan lnk_vlan; + NMPObjectLnkVlan _lnk_vlan; + + NMPlatformLnkVrf lnk_vrf; + NMPObjectLnkVrf _lnk_vrf; + + NMPlatformLnkVxlan lnk_vxlan; + NMPObjectLnkVxlan _lnk_vxlan; + + NMPlatformLnkWireGuard lnk_wireguard; + NMPObjectLnkWireGuard _lnk_wireguard; + + NMPlatformIPAddress ip_address; + NMPlatformIPXAddress ipx_address; + NMPlatformIP4Address ip4_address; + NMPlatformIP6Address ip6_address; + NMPObjectIP4Address _ip4_address; + NMPObjectIP6Address _ip6_address; + + NMPlatformIPRoute ip_route; + NMPlatformIPXRoute ipx_route; + NMPlatformIP4Route ip4_route; + NMPlatformIP6Route ip6_route; + NMPObjectIP4Route _ip4_route; + NMPObjectIP6Route _ip6_route; + + NMPlatformRoutingRule routing_rule; + NMPObjectRoutingRule _routing_rule; + + NMPlatformQdisc qdisc; + NMPObjectQdisc _qdisc; + NMPlatformTfilter tfilter; + NMPObjectTfilter _tfilter; + }; +}; + +/*****************************************************************************/ + +static inline gboolean +NMP_CLASS_IS_VALID(const NMPClass *klass) +{ + return klass >= &_nmp_classes[0] && klass <= &_nmp_classes[G_N_ELEMENTS(_nmp_classes)] + && ((((char *) klass) - ((char *) _nmp_classes)) % (sizeof(_nmp_classes[0]))) == 0; +} + +static inline const NMPClass * +nmp_class_from_type(NMPObjectType obj_type) +{ + nm_assert(obj_type > 0); + nm_assert(obj_type <= G_N_ELEMENTS(_nmp_classes)); + nm_assert(_nmp_classes[obj_type - 1].obj_type == obj_type); + nm_assert(NMP_CLASS_IS_VALID(&_nmp_classes[obj_type - 1])); + + return &_nmp_classes[obj_type - 1]; +} + +static inline NMPObject * +NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj) +{ + NMPObject *obj; + + obj = plobj ? (NMPObject *) (&(((char *) plobj)[-((int) G_STRUCT_OFFSET(NMPObject, object))])) + : NULL; + nm_assert(!obj || (obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class))); + return obj; +} +#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST((const NMPlatformObject *) (plobj))) + +static inline gboolean +NMP_OBJECT_IS_VALID(const NMPObject *obj) +{ + nm_assert(!obj || (obj && obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class))); + + /* There isn't really much to check. Either @obj is NULL, or we must + * assume that it points to valid memory. */ + return obj != NULL; +} + +static inline gboolean +NMP_OBJECT_IS_STACKINIT(const NMPObject *obj) +{ + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + return obj && obj->parent._ref_count == NM_OBJ_REF_COUNT_STACKINIT; +} + +static inline const NMPClass * +NMP_OBJECT_GET_CLASS(const NMPObject *obj) +{ + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + return obj->_class; +} + +static inline NMPObjectType +NMP_OBJECT_GET_TYPE(const NMPObject *obj) +{ + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN; +} + +static inline gboolean +_NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type) +{ + switch (obj_type) { + case NMP_OBJECT_TYPE_LINK: + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + + case NMP_OBJECT_TYPE_QDISC: + + case NMP_OBJECT_TYPE_TFILTER: + + case NMP_OBJECT_TYPE_LNK_BRIDGE: + case NMP_OBJECT_TYPE_LNK_GRE: + case NMP_OBJECT_TYPE_LNK_GRETAP: + case NMP_OBJECT_TYPE_LNK_INFINIBAND: + case NMP_OBJECT_TYPE_LNK_IP6TNL: + case NMP_OBJECT_TYPE_LNK_IP6GRE: + case NMP_OBJECT_TYPE_LNK_IP6GRETAP: + case NMP_OBJECT_TYPE_LNK_IPIP: + case NMP_OBJECT_TYPE_LNK_MACSEC: + case NMP_OBJECT_TYPE_LNK_MACVLAN: + case NMP_OBJECT_TYPE_LNK_MACVTAP: + case NMP_OBJECT_TYPE_LNK_SIT: + case NMP_OBJECT_TYPE_LNK_TUN: + case NMP_OBJECT_TYPE_LNK_VLAN: + case NMP_OBJECT_TYPE_LNK_VRF: + case NMP_OBJECT_TYPE_LNK_VXLAN: + case NMP_OBJECT_TYPE_LNK_WIREGUARD: + return TRUE; + + case NMP_OBJECT_TYPE_ROUTING_RULE: + return FALSE; + + case NMP_OBJECT_TYPE_UNKNOWN: + case __NMP_OBJECT_TYPE_LAST: + break; + } + nm_assert_not_reached(); + return FALSE; +} + +#define NMP_OBJECT_CAST_OBJECT(obj) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert ( !_obj \ + || nmp_class_from_type (NMP_OBJECT_GET_TYPE (_obj)))); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->object : NULL; \ + }) + +#define NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert(!_obj || _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMP_OBJECT_GET_TYPE(_obj))); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->obj_with_ifindex : NULL; \ + }) + +#define _NMP_OBJECT_CAST(obj, field, ...) \ + ({ \ + typeof(obj) _obj = (obj); \ + \ + nm_assert(!_obj || NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj), __VA_ARGS__)); \ + _obj ? &NM_CONSTCAST(NMPObject, _obj)->field : NULL; \ + }) + +#define NMP_OBJECT_CAST_LINK(obj) _NMP_OBJECT_CAST(obj, link, NMP_OBJECT_TYPE_LINK) +#define NMP_OBJECT_CAST_IP_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ipx_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip4_address, NMP_OBJECT_TYPE_IP4_ADDRESS) +#define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \ + _NMP_OBJECT_CAST(obj, ip6_address, NMP_OBJECT_TYPE_IP6_ADDRESS) +#define NMP_OBJECT_CAST_IP_ROUTE(obj) \ + _NMP_OBJECT_CAST(obj, ip_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_IPX_ROUTE(obj) \ + _NMP_OBJECT_CAST(obj, ipx_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_IP4_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip4_route, NMP_OBJECT_TYPE_IP4_ROUTE) +#define NMP_OBJECT_CAST_IP6_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip6_route, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \ + _NMP_OBJECT_CAST(obj, routing_rule, NMP_OBJECT_TYPE_ROUTING_RULE) +#define NMP_OBJECT_CAST_QDISC(obj) _NMP_OBJECT_CAST(obj, qdisc, NMP_OBJECT_TYPE_QDISC) +#define NMP_OBJECT_CAST_TFILTER(obj) _NMP_OBJECT_CAST(obj, tfilter, NMP_OBJECT_TYPE_TFILTER) +#define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) \ + _NMP_OBJECT_CAST(obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD) +#define NMP_OBJECT_CAST_LNK_BRIDGE(obj) \ + _NMP_OBJECT_CAST(obj, lnk_bridge, NMP_OBJECT_TYPE_LNK_BRIDGE) + +static inline int +NMP_OBJECT_TYPE_TO_ADDR_FAMILY(NMPObjectType obj_type) +{ + return nmp_class_from_type(obj_type)->addr_family; +} + +static inline int +NMP_OBJECT_GET_ADDR_FAMILY(const NMPObject *obj) +{ + return NMP_OBJECT_GET_CLASS(obj)->addr_family; +} + +static inline const NMPObject * +nmp_object_ref(const NMPObject *obj) +{ + if (!obj) { + /* for convenience, allow NULL. */ + return NULL; + } + + /* ref and unref accept const pointers. NMPObject is supposed to be shared + * and kept immutable. Disallowing to take/return a reference to a const + * NMPObject is cumbersome, because callers are precisely expected to + * keep a ref on the otherwise immutable object. */ + g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL); + g_return_val_if_fail(obj->parent._ref_count != NM_OBJ_REF_COUNT_STACKINIT, NULL); + + return (const NMPObject *) nm_dedup_multi_obj_ref((const NMDedupMultiObj *) obj); +} + +static inline void +nmp_object_unref(const NMPObject *obj) +{ + if (obj) { + nm_assert(NMP_OBJECT_IS_VALID(obj)); + + nm_dedup_multi_obj_unref((const NMDedupMultiObj *) obj); + } +} + +#define nm_clear_nmp_object(ptr) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + typeof(*_ptr) _pptr; \ + gboolean _changed = FALSE; \ + \ + if (_ptr && (_pptr = *_ptr)) { \ + *_ptr = NULL; \ + nmp_object_unref(_pptr); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +static inline gboolean +nmp_object_ref_set(const NMPObject **pp, const NMPObject *obj) +{ + gboolean _changed = FALSE; + const NMPObject *p; + + nm_assert(!pp || !*pp || NMP_OBJECT_IS_VALID(*pp)); + nm_assert(!obj || NMP_OBJECT_IS_VALID(obj)); + + if (pp && ((p = *pp) != obj)) { + nmp_object_ref(obj); + *pp = obj; + nmp_object_unref(p); + _changed = TRUE; + } + return _changed; +} + +NMPObject *nmp_object_new(NMPObjectType obj_type, gconstpointer plobj); +NMPObject *nmp_object_new_link(int ifindex); + +const NMPObject *nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj); + +static inline NMPObject * +nmp_object_stackinit_obj(NMPObject *obj, const NMPObject *src) +{ + return obj == src + ? obj + : (NMPObject *) nmp_object_stackinit(obj, NMP_OBJECT_GET_TYPE(src), &src->object); +} + +const NMPObject *nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src); +const NMPObject *nmp_object_stackinit_id_link(NMPObject *obj, int ifindex); +const NMPObject *nmp_object_stackinit_id_ip4_address(NMPObject *obj, + int ifindex, + guint32 address, + guint8 plen, + guint32 peer_address); +const NMPObject * +nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address); + +const char *nmp_object_to_string(const NMPObject * obj, + NMPObjectToStringMode to_string_mode, + char * buf, + gsize buf_size); +void nmp_object_hash_update(const NMPObject *obj, NMHashState *h); +int nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2); + +static inline gboolean +nmp_object_equal(const NMPObject *obj1, const NMPObject *obj2) +{ + return nmp_object_cmp(obj1, obj2) == 0; +} + +void nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only); +NMPObject *nmp_object_clone(const NMPObject *obj, gboolean id_only); + +int nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2); +void nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h); +guint nmp_object_id_hash(const NMPObject *obj); + +static inline gboolean +nmp_object_id_equal(const NMPObject *obj1, const NMPObject *obj2) +{ + return nmp_object_id_cmp(obj1, obj2) == 0; +} + +guint nmp_object_indirect_id_hash(gconstpointer a); +gboolean nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b); + +gboolean nmp_object_is_alive(const NMPObject *obj); +gboolean nmp_object_is_visible(const NMPObject *obj); + +void +_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev); + +static inline void +_nm_auto_nmpobj_cleanup(gpointer p) +{ + nmp_object_unref(*((const NMPObject **) p)); +} +#define nm_auto_nmpobj nm_auto(_nm_auto_nmpobj_cleanup) + +typedef struct _NMPCache NMPCache; + +typedef void (*NMPCachePreHook)(NMPCache * cache, + const NMPObject *old, + const NMPObject *new, + NMPCacheOpsType ops_type, + gpointer user_data); +typedef gboolean (*NMPObjectMatchFn)(const NMPObject *obj, gpointer user_data); + +const NMDedupMultiEntry *nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj); +const NMDedupMultiEntry *nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *obj); +const NMDedupMultiEntry *nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex); +const NMPObject * nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj); +const NMPObject * nmp_cache_lookup_link(const NMPCache *cache, int ifindex); + +typedef struct _NMPLookup NMPLookup; + +struct _NMPLookup { + NMPCacheIdType cache_id_type; + NMPObject selector_obj; +}; + +const NMDedupMultiHeadEntry *nmp_cache_lookup_all(const NMPCache * cache, + NMPCacheIdType cache_id_type, + const NMPObject *select_obj); + +static inline const NMDedupMultiHeadEntry * +nmp_cache_lookup(const NMPCache *cache, const NMPLookup *lookup) +{ + return nmp_cache_lookup_all(cache, lookup->cache_id_type, &lookup->selector_obj); +} + +const NMPLookup *nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type); +const NMPLookup *nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname); +const NMPLookup *nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex); +const NMPLookup *nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type); +const NMPLookup *nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj); +const NMPLookup *nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos); +const NMPLookup *nmp_lookup_init_ip6_route_by_weak_id(NMPLookup * lookup, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); +const NMPLookup * +nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family); + +GArray *nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry, + NMPObjectType obj_type, + gboolean visible_only); + +static inline gboolean +nmp_cache_iter_next(NMDedupMultiIter *iter, const NMPObject **out_obj) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(iter); + nm_assert(!has_next || NMP_OBJECT_IS_VALID(iter->current->obj)); + if (out_obj) + *out_obj = has_next ? iter->current->obj : NULL; + return has_next; +} + +static inline gboolean +nmp_cache_iter_prev(NMDedupMultiIter *iter, const NMPObject **out_obj) +{ + gboolean has_prev; + + has_prev = nm_dedup_multi_iter_prev(iter); + nm_assert(!has_prev || NMP_OBJECT_IS_VALID(iter->current->obj)); + if (out_obj) + *out_obj = has_prev ? iter->current->obj : NULL; + return has_prev; +} + +static inline gboolean +nmp_cache_iter_next_link(NMDedupMultiIter *iter, const NMPlatformLink **out_obj) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(iter); + nm_assert(!has_next || NMP_OBJECT_GET_TYPE(iter->current->obj) == NMP_OBJECT_TYPE_LINK); + if (out_obj) + *out_obj = has_next ? &(((const NMPObject *) iter->current->obj)->link) : NULL; + return has_next; +} + +#define nmp_cache_iter_for_each(iter, head, obj) \ + for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next((iter), (obj));) + +#define nmp_cache_iter_for_each_reverse(iter, head, obj) \ + for (nm_dedup_multi_iter_init_reverse((iter), (head)); nmp_cache_iter_prev((iter), (obj));) + +#define nmp_cache_iter_for_each_link(iter, head, obj) \ + for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next_link((iter), (obj));) + +const NMPObject *nmp_cache_lookup_link_full(const NMPCache * cache, + int ifindex, + const char * ifname, + gboolean visible_only, + NMLinkType link_type, + NMPObjectMatchFn match_fn, + gpointer user_data); + +gboolean nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave); +gboolean nmp_cache_link_connected_needs_toggle(const NMPCache * cache, + const NMPObject *master, + const NMPObject *potential_slave, + const NMPObject *ignore_slave); +const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache, + int master_ifindex, + const NMPObject *potential_slave, + const NMPObject *ignore_slave); + +gboolean nmp_cache_use_udev_get(const NMPCache *cache); + +void nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache); + +NMPCacheOpsType nmp_cache_remove(NMPCache * cache, + const NMPObject * obj_needle, + gboolean equals_by_ptr, + gboolean only_dirty, + const NMPObject **out_obj_old); +NMPCacheOpsType nmp_cache_remove_netlink(NMPCache * cache, + const NMPObject * obj_needle, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); +NMPCacheOpsType nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); +NMPCacheOpsType nmp_cache_update_netlink_route(NMPCache * cache, + NMPObject * obj_hand_over, + gboolean is_dump, + guint16 nlmsgflags, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + const NMPObject **out_obj_replace, + gboolean * out_resync_required); +NMPCacheOpsType nmp_cache_update_link_udev(NMPCache * cache, + int ifindex, + struct udev_device *udevice, + const NMPObject ** out_obj_old, + const NMPObject ** out_obj_new); +NMPCacheOpsType nmp_cache_update_link_master_connected(NMPCache * cache, + int ifindex, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new); + +static inline const NMDedupMultiEntry * +nmp_cache_reresolve_main_entry(NMPCache * cache, + const NMDedupMultiEntry *entry, + const NMPLookup * lookup) +{ + const NMDedupMultiEntry *main_entry; + + nm_assert(cache); + nm_assert(entry); + nm_assert(lookup); + + if (lookup->cache_id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) { + nm_assert(entry == nmp_cache_lookup_entry(cache, entry->obj)); + return entry; + } + + /* we only track the dirty flag for the OBJECT-TYPE index. That means, + * for other lookup types we need to check the dirty flag of the main-entry. */ + main_entry = nmp_cache_lookup_entry(cache, entry->obj); + + nm_assert(main_entry); + nm_assert(main_entry->obj == entry->obj); + + return main_entry; +} + +void nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup); + +NMPCache *nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev); +void nmp_cache_free(NMPCache *cache); + +static inline void +ASSERT_nmp_cache_ops(const NMPCache * cache, + NMPCacheOpsType ops_type, + const NMPObject *obj_old, + const NMPObject *obj_new) +{ +#if NM_MORE_ASSERTS + nm_assert(cache); + nm_assert(obj_old || obj_new); + nm_assert(!obj_old + || (NMP_OBJECT_IS_VALID(obj_old) && !NMP_OBJECT_IS_STACKINIT(obj_old) + && nmp_object_is_alive(obj_old))); + nm_assert(!obj_new + || (NMP_OBJECT_IS_VALID(obj_new) && !NMP_OBJECT_IS_STACKINIT(obj_new) + && nmp_object_is_alive(obj_new))); + + switch (ops_type) { + case NMP_CACHE_OPS_UNCHANGED: + nm_assert(obj_old == obj_new); + break; + case NMP_CACHE_OPS_ADDED: + nm_assert(!obj_old && obj_new); + break; + case NMP_CACHE_OPS_UPDATED: + nm_assert(obj_old && obj_new && obj_old != obj_new); + break; + case NMP_CACHE_OPS_REMOVED: + nm_assert(obj_old && !obj_new); + break; + default: + nm_assert_not_reached(); + } + + nm_assert(obj_new == NULL || obj_old == NULL || nmp_object_id_equal(obj_new, obj_old)); + nm_assert(!obj_old || !obj_new + || NMP_OBJECT_GET_CLASS(obj_old) == NMP_OBJECT_GET_CLASS(obj_new)); + + nm_assert(obj_new == nmp_cache_lookup_obj(cache, obj_new ?: obj_old)); +#endif +} + +const NMDedupMultiHeadEntry * +nm_platform_lookup_all(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj); + +const NMDedupMultiEntry * +nm_platform_lookup_entry(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj); + +static inline const NMPObject * +nm_platform_lookup_obj(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj) +{ + return nm_dedup_multi_entry_get_obj(nm_platform_lookup_entry(platform, cache_id_type, obj)); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_obj_type(NMPlatform *platform, NMPObjectType obj_type) +{ + NMPLookup lookup; + + nmp_lookup_init_obj_type(&lookup, obj_type); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_link_by_ifname(NMPlatform *platform, const char *ifname) +{ + NMPLookup lookup; + + nmp_lookup_init_link_by_ifname(&lookup, ifname); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_object(NMPlatform *platform, NMPObjectType obj_type, int ifindex) +{ + NMPLookup lookup; + + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nm_platform_lookup(platform, &lookup); +} + +static inline GPtrArray * +nm_platform_lookup_object_clone(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + NMPLookup lookup; + + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nm_platform_lookup_clone(platform, &lookup, predicate, user_data); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_route_default(NMPlatform *platform, NMPObjectType obj_type) +{ + NMPLookup lookup; + + nmp_lookup_init_route_default(&lookup, obj_type); + return nm_platform_lookup(platform, &lookup); +} + +static inline GPtrArray * +nm_platform_lookup_route_default_clone(NMPlatform * platform, + NMPObjectType obj_type, + NMPObjectPredicateFunc predicate, + gpointer user_data) +{ + NMPLookup lookup; + + nmp_lookup_init_route_default(&lookup, obj_type); + return nm_platform_lookup_clone(platform, &lookup, predicate, user_data); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_ip4_route_by_weak_id(NMPlatform *platform, + in_addr_t network, + guint plen, + guint32 metric, + guint8 tos) +{ + NMPLookup lookup; + + nmp_lookup_init_ip4_route_by_weak_id(&lookup, network, plen, metric, tos); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_ip6_route_by_weak_id(NMPlatform * platform, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + NMPLookup lookup; + + nmp_lookup_init_ip6_route_by_weak_id(&lookup, network, plen, metric, src, src_plen); + return nm_platform_lookup(platform, &lookup); +} + +static inline const NMDedupMultiHeadEntry * +nm_platform_lookup_object_by_addr_family(NMPlatform * platform, + NMPObjectType obj_type, + int addr_family) +{ + NMPLookup lookup; + + nmp_lookup_init_object_by_addr_family(&lookup, obj_type, addr_family); + return nm_platform_lookup(platform, &lookup); +} + +/*****************************************************************************/ + +static inline const char * +nmp_object_link_get_ifname(const NMPObject *obj) +{ + if (!obj) + return NULL; + return NMP_OBJECT_CAST_LINK(obj)->name; +} + +static inline gboolean +nmp_object_ip_route_is_best_defaut_route(const NMPObject *obj) +{ + const NMPlatformIPRoute *r = NMP_OBJECT_CAST_IP_ROUTE(obj); + + /* return whether @obj is considered a default-route. + * + * NMIP4Config/NMIP6Config tracks the (best) default-route explicitly, because + * at various places we act differently depending on whether there is a default-route + * configured. + * + * Note that this only considers the main routing table. */ + return r && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) + && nm_platform_route_table_is_main(r->table_coerced) + && r->type_coerced == nm_platform_route_type_coerce(1 /* RTN_UNICAST */); +} + +static inline gboolean +nmp_object_ip6_address_is_not_link_local(const NMPObject *obj) +{ + return !IN6_IS_ADDR_LINKLOCAL(&NMP_OBJECT_CAST_IP6_ADDRESS(obj)->address); +} + +/*****************************************************************************/ + +const char *nmp_object_link_udev_device_get_property_value(const NMPObject *obj, const char *key); + +/*****************************************************************************/ + +static inline gboolean +nm_platform_dedup_multi_iter_next_obj(NMDedupMultiIter *ipconf_iter, + const NMPObject **out_obj, + NMPObjectType assert_obj_type) +{ + gboolean has_next; + + has_next = nm_dedup_multi_iter_next(ipconf_iter); + nm_assert(assert_obj_type == NMP_OBJECT_TYPE_UNKNOWN || !has_next + || NMP_OBJECT_GET_TYPE(ipconf_iter->current->obj) == assert_obj_type); + NM_SET_OUT(out_obj, has_next ? ipconf_iter->current->obj : NULL); + return has_next; +} + +#define _nm_platform_dedup_multi_iter_next(ipconf_iter, out_obj, field, ...) \ + ({ \ + NMDedupMultiIter *const _ipconf_iter = (ipconf_iter); \ + const typeof(((NMPObject *) NULL)->field) **const _out_obj = (out_obj); \ + gboolean _has_next; \ + \ + if (G_LIKELY(nm_dedup_multi_iter_next(_ipconf_iter))) { \ + if (_out_obj) { \ + *_out_obj = _NMP_OBJECT_CAST(_ipconf_iter->current->obj, field, __VA_ARGS__); \ + } else { \ + nm_assert( \ + NM_IN_SET(NMP_OBJECT_GET_TYPE(_ipconf_iter->current->obj), __VA_ARGS__)); \ + } \ + _has_next = TRUE; \ + } else { \ + if (_out_obj) \ + *_out_obj = NULL; \ + _has_next = FALSE; \ + } \ + _has_next; \ + }) + +#define nm_platform_dedup_multi_iter_next_ip_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip_address, \ + NMP_OBJECT_TYPE_IP4_ADDRESS, \ + NMP_OBJECT_TYPE_IP6_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip4_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip4_address, \ + NMP_OBJECT_TYPE_IP4_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip6_address(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip6_address, \ + NMP_OBJECT_TYPE_IP6_ADDRESS) + +#define nm_platform_dedup_multi_iter_next_ip_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip_route, \ + NMP_OBJECT_TYPE_IP4_ROUTE, \ + NMP_OBJECT_TYPE_IP6_ROUTE) + +#define nm_platform_dedup_multi_iter_next_ip4_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip4_route, \ + NMP_OBJECT_TYPE_IP4_ROUTE) + +#define nm_platform_dedup_multi_iter_next_ip6_route(ipconf_iter, out_obj) \ + _nm_platform_dedup_multi_iter_next((ipconf_iter), \ + (out_obj), \ + ip6_route, \ + NMP_OBJECT_TYPE_IP6_ROUTE) + +#endif /* __NMP_OBJECT_H__ */ diff --git a/src/libnm-platform/nmp-rules-manager.c b/src/libnm-platform/nmp-rules-manager.c new file mode 100644 index 00000000..636c90b2 --- /dev/null +++ b/src/libnm-platform/nmp-rules-manager.c @@ -0,0 +1,809 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nmp-rules-manager.h" + +#include <linux/fib_rules.h> +#include <linux/rtnetlink.h> + +#include "libnm-log-core/nm-logging.h" +#include "libnm-std-aux/c-list-util.h" +#include "nmp-object.h" + +/*****************************************************************************/ + +struct _NMPRulesManager { + NMPlatform *platform; + GHashTable *by_obj; + GHashTable *by_user_tag; + GHashTable *by_data; + guint ref_count; +}; + +/*****************************************************************************/ + +static void _rules_init(NMPRulesManager *self); + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG_PREFIX_NAME "rules-manager" + +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + \ + if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \ + _nm_log(__level, \ + _NMLOG_DOMAIN, \ + 0, \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static gboolean +NMP_IS_RULES_MANAGER(gpointer self) +{ + return self && ((NMPRulesManager *) self)->ref_count > 0 + && NM_IS_PLATFORM(((NMPRulesManager *) self)->platform); +} + +#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr(1240261787u, (user_tag)) + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gconstpointer user_tag; + CList obj_lst; + CList user_tag_lst; + + /* track_priority_val zero is special: those are weakly tracked rules. + * That means: NetworkManager will restore them only if it removed them earlier. + * But it will not remove or add them otherwise. + * + * Otherwise, the track_priority_val goes together with track_priority_present. + * In case of one rule being tracked multiple times (with different priorities), + * the one with higher priority wins. See _rules_obj_get_best_data(). + * Then, the winning present state either enforces that the rule is present + * or absent. + * + * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming + * that it was added externally by the user. But unlike weakly tracked rules, + * NM will *not* restore such rules if NetworkManager themself removed them. */ + guint32 track_priority_val; + bool track_priority_present : 1; + + bool dirty : 1; +} RulesData; + +typedef enum { + CONFIG_STATE_NONE = 0, + CONFIG_STATE_ADDED_BY_US = 1, + CONFIG_STATE_REMOVED_BY_US = 2, + + /* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE). + * + * Maybe we would only need to track whether we touched the rule at all. But we + * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US) + * or did we remove it (CONFIG_STATE_REMOVED_BY_US)? + * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively + * add/remove it, but whenever we are about to undo the add/remove, we need to do it. + * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally + * force the state next time when necessary. */ + CONFIG_STATE_OWNED_BY_US = 3, +} ConfigState; + +typedef struct { + const NMPObject *obj; + CList obj_lst_head; + + /* indicates whether we configured/removed the rule (during sync()). We need that, so + * if the rule gets untracked, that we know to remove/restore it. + * + * This makes NMPRulesManager stateful (beyond the configuration that indicates + * which rules are tracked). + * After a restart, NetworkManager would no longer remember which rules were added + * by us. + * + * That is partially fixed by NetworkManager taking over the rules that it + * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */ + ConfigState config_state; +} RulesObjData; + +typedef struct { + gconstpointer user_tag; + CList user_tag_lst_head; +} RulesUserTagData; + +/*****************************************************************************/ + +static void _rules_data_untrack(NMPRulesManager *self, + RulesData * rules_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us); + +/*****************************************************************************/ + +static void +_rules_data_assert(const RulesData *rules_data, gboolean linked) +{ + nm_assert(rules_data); + nm_assert(NMP_OBJECT_GET_TYPE(rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + nm_assert(nmp_object_is_visible(rules_data->obj)); + nm_assert(rules_data->user_tag); + nm_assert(!linked || !c_list_is_empty(&rules_data->obj_lst)); + nm_assert(!linked || !c_list_is_empty(&rules_data->user_tag_lst)); +} + +static guint +_rules_data_hash(gconstpointer data) +{ + const RulesData *rules_data = data; + NMHashState h; + + _rules_data_assert(rules_data, FALSE); + + nm_hash_init(&h, 269297543u); + nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(rules_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + nm_hash_update_val(&h, rules_data->user_tag); + return nm_hash_complete(&h); +} + +static gboolean +_rules_data_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesData *rules_data_a = data_a; + const RulesData *rules_data_b = data_b; + + _rules_data_assert(rules_data_a, FALSE); + _rules_data_assert(rules_data_b, FALSE); + + return rules_data_a->user_tag == rules_data_b->user_tag + && (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(rules_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE(rules_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); +} + +static void +_rules_data_destroy(gpointer data) +{ + RulesData *rules_data = data; + + _rules_data_assert(rules_data, FALSE); + + c_list_unlink_stale(&rules_data->obj_lst); + c_list_unlink_stale(&rules_data->user_tag_lst); + nmp_object_unref(rules_data->obj); + g_slice_free(RulesData, rules_data); +} + +static const RulesData * +_rules_obj_get_best_data(RulesObjData *obj_data) +{ + RulesData * rules_data; + const RulesData *rd_best = NULL; + + c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) { + _rules_data_assert(rules_data, TRUE); + + if (rd_best) { + if (rd_best->track_priority_val > rules_data->track_priority_val) + continue; + if (rd_best->track_priority_val == rules_data->track_priority_val) { + if (rd_best->track_priority_present || !rules_data->track_priority_present) { + /* if the priorities are identical, then "present" wins over + * "!present" (absent). */ + continue; + } + } + } + + rd_best = rules_data; + } + + return rd_best; +} + +static guint +_rules_obj_hash(gconstpointer data) +{ + const RulesObjData *obj_data = data; + NMHashState h; + + nm_hash_init(&h, 432817559u); + nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, + &h); + return nm_hash_complete(&h); +} + +static gboolean +_rules_obj_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesObjData *obj_data_a = data_a; + const RulesObjData *obj_data_b = data_b; + + return (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj_data_a->obj), + NMP_OBJECT_CAST_ROUTING_RULE(obj_data_b->obj), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); +} + +static void +_rules_obj_destroy(gpointer data) +{ + RulesObjData *obj_data = data; + + c_list_unlink_stale(&obj_data->obj_lst_head); + nmp_object_unref(obj_data->obj); + g_slice_free(RulesObjData, obj_data); +} + +static guint +_rules_user_tag_hash(gconstpointer data) +{ + const RulesUserTagData *user_tag_data = data; + + return nm_hash_val(644693447u, user_tag_data->user_tag); +} + +static gboolean +_rules_user_tag_equal(gconstpointer data_a, gconstpointer data_b) +{ + const RulesUserTagData *user_tag_data_a = data_a; + const RulesUserTagData *user_tag_data_b = data_b; + + return user_tag_data_a->user_tag == user_tag_data_b->user_tag; +} + +static void +_rules_user_tag_destroy(gpointer data) +{ + RulesUserTagData *user_tag_data = data; + + c_list_unlink_stale(&user_tag_data->user_tag_lst_head); + g_slice_free(RulesUserTagData, user_tag_data); +} + +static RulesData * +_rules_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag) +{ + RulesData rules_data_needle = { + .obj = obj, + .user_tag = user_tag, + }; + + return g_hash_table_lookup(by_data, &rules_data_needle); +} + +/** + * nmp_rules_manager_track: + * @self: the #NMPRulesManager instance + * @routing_rule: the #NMPlatformRoutingRule to track or untrack + * @track_priority: the priority for tracking the rule. Note that + * negative values indicate a forced absence of the rule. Priorities + * are compared with their absolute values (with higher absolute + * value being more important). For example, if you track the same + * rule twice, once with priority -5 and +10, then the rule is + * present (because the positive number is more important). + * The special value 0 indicates weakly-tracked rules. + * @user_tag: the tag associated with tracking this rule. The same tag + * must be used to untrack the rule later. + * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag + * for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(), + * because it enforces ownership of the now tracked rule. On the other hand, + * a plain nmp_rules_manager_untrack() merely forgets about the tracking. + * The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. + */ +void +nmp_rules_manager_track(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack) +{ + NMPObject obj_stack; + const NMPObject * p_obj_stack; + RulesData * rules_data; + RulesObjData * obj_data; + RulesUserTagData *user_tag_data; + gboolean changed = FALSE; + guint32 track_priority_val; + gboolean track_priority_present; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(routing_rule); + g_return_if_fail(user_tag); + nm_assert(track_priority != G_MININT32); + + _rules_init(self); + + p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + if (track_priority >= 0) { + track_priority_val = track_priority; + track_priority_present = TRUE; + } else { + track_priority_val = -track_priority; + track_priority_present = FALSE; + } + + rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); + + if (!rules_data) { + rules_data = g_slice_new(RulesData); + *rules_data = (RulesData){ + .obj = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform), + p_obj_stack), + .user_tag = user_tag, + .track_priority_val = track_priority_val, + .track_priority_present = track_priority_present, + .dirty = FALSE, + }; + g_hash_table_add(self->by_data, rules_data); + + obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); + if (!obj_data) { + obj_data = g_slice_new(RulesObjData); + *obj_data = (RulesObjData){ + .obj = nmp_object_ref(rules_data->obj), + .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head), + .config_state = CONFIG_STATE_NONE, + }; + g_hash_table_add(self->by_obj, obj_data); + } + c_list_link_tail(&obj_data->obj_lst_head, &rules_data->obj_lst); + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); + if (!user_tag_data) { + user_tag_data = g_slice_new(RulesUserTagData); + *user_tag_data = (RulesUserTagData){ + .user_tag = user_tag, + .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head), + }; + g_hash_table_add(self->by_user_tag, user_tag_data); + } + c_list_link_tail(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst); + changed = TRUE; + } else { + rules_data->dirty = FALSE; + if (rules_data->track_priority_val != track_priority_val + || rules_data->track_priority_present != track_priority_present) { + rules_data->track_priority_val = track_priority_val; + rules_data->track_priority_present = track_priority_present; + changed = TRUE; + } + } + + if (user_tag_untrack) { + if (user_tag != user_tag_untrack) { + RulesData *rules_data_untrack; + + rules_data_untrack = _rules_data_lookup(self->by_data, p_obj_stack, user_tag_untrack); + if (rules_data_untrack) + _rules_data_untrack(self, rules_data_untrack, FALSE, TRUE); + } else + nm_assert_not_reached(); + } + + _rules_data_assert(rules_data, TRUE); + + if (changed) { + _LOGD("routing-rule: track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] \"%s\")", + _USER_TAG_LOG(rules_data->user_tag), + (rules_data->track_priority_val == 0 + ? "" + : (rules_data->track_priority_present ? "+" : "-")), + (guint) rules_data->track_priority_val, + nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + } +} + +static void +_rules_data_untrack(NMPRulesManager *self, + RulesData * rules_data, + gboolean remove_user_tag_data, + gboolean make_owned_by_us) +{ + RulesObjData *obj_data; + + nm_assert(NMP_IS_RULES_MANAGER(self)); + _rules_data_assert(rules_data, TRUE); + nm_assert(self->by_data); + nm_assert(g_hash_table_lookup(self->by_data, rules_data) == rules_data); + + _LOGD("routing-rule: untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] \"%s\"", + _USER_TAG_LOG(rules_data->user_tag), + nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + +#if NM_MORE_ASSERTS + { + RulesUserTagData *user_tag_data; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag); + nm_assert(user_tag_data); + nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst)); + } +#endif + + nm_assert(!c_list_is_empty(&rules_data->user_tag_lst)); + + obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj); + nm_assert(obj_data); + nm_assert(c_list_contains(&obj_data->obj_lst_head, &rules_data->obj_lst)); + nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &rules_data->obj)); + + if (make_owned_by_us) { + if (obj_data->config_state == CONFIG_STATE_NONE) { + /* we need to mark this entry that it requires a touch on the next + * sync. */ + obj_data->config_state = CONFIG_STATE_OWNED_BY_US; + } + } else if (remove_user_tag_data && c_list_length_is(&rules_data->user_tag_lst, 1)) + g_hash_table_remove(self->by_user_tag, &rules_data->user_tag); + + /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry + * around for the next sync -- so that we can undo what we did earlier. */ + if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&rules_data->obj_lst, 1)) + g_hash_table_remove(self->by_obj, &rules_data->obj); + + g_hash_table_remove(self->by_data, rules_data); +} + +void +nmp_rules_manager_untrack(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag) +{ + NMPObject obj_stack; + const NMPObject *p_obj_stack; + RulesData * rules_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(routing_rule); + g_return_if_fail(user_tag); + + _rules_init(self); + + p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule); + + nm_assert(nmp_object_is_visible(p_obj_stack)); + + rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag); + if (rules_data) + _rules_data_untrack(self, rules_data, TRUE, FALSE); +} + +void +nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag) +{ + RulesData * rules_data; + RulesUserTagData *user_tag_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst) + rules_data->dirty = TRUE; +} + +void +nmp_rules_manager_untrack_all(NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */) +{ + RulesData * rules_data; + RulesData * rules_data_safe; + RulesUserTagData *user_tag_data; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + g_return_if_fail(user_tag); + + if (!self->by_data) + return; + + user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag); + if (!user_tag_data) + return; + + c_list_for_each_entry_safe (rules_data, + rules_data_safe, + &user_tag_data->user_tag_lst_head, + user_tag_lst) { + if (all || rules_data->dirty) + _rules_data_untrack(self, rules_data, FALSE, FALSE); + } + if (c_list_is_empty(&user_tag_data->user_tag_lst_head)) + g_hash_table_remove(self->by_user_tag, user_tag_data); +} + +void +nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules) +{ + const NMDedupMultiHeadEntry *pl_head_entry; + NMDedupMultiIter pl_iter; + const NMPObject * plobj; + gs_unref_ptrarray GPtrArray *rules_to_delete = NULL; + RulesObjData * obj_data; + GHashTableIter h_iter; + guint i; + const RulesData * rd_best; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (!self->by_data) + return; + + _LOGD("sync%s", keep_deleted_rules ? " (don't remove any rules)" : ""); + + pl_head_entry = nm_platform_lookup_obj_type(self->platform, NMP_OBJECT_TYPE_ROUTING_RULE); + if (pl_head_entry) { + nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) { + obj_data = g_hash_table_lookup(self->by_obj, &plobj); + + if (!obj_data) { + /* this rule is not tracked. It was externally added, hence we + * ignore it. */ + continue; + } + + rd_best = _rules_obj_get_best_data(obj_data); + if (rd_best) { + if (rd_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + continue; + } + if (rd_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_ADDED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + } + + if (keep_deleted_rules) { + _LOGD("forget/leak rule added by us: %s", + nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + continue; + } + + if (!rules_to_delete) + rules_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(rules_to_delete, (gpointer) nmp_object_ref(plobj)); + + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + } + } + + if (rules_to_delete) { + for (i = 0; i < rules_to_delete->len; i++) + nm_platform_object_delete(self->platform, rules_to_delete->pdata[i]); + } + + g_hash_table_iter_init(&h_iter, self->by_obj); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_data, NULL)) { + rd_best = _rules_obj_get_best_data(obj_data); + + if (!rd_best) { + g_hash_table_iter_remove(&h_iter); + continue; + } + + if (!rd_best->track_priority_present) { + if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US) + obj_data->config_state = CONFIG_STATE_REMOVED_BY_US; + continue; + } + if (rd_best->track_priority_val == 0) { + if (!NM_IN_SET(obj_data->config_state, + CONFIG_STATE_REMOVED_BY_US, + CONFIG_STATE_OWNED_BY_US)) { + obj_data->config_state = CONFIG_STATE_NONE; + continue; + } + obj_data->config_state = CONFIG_STATE_NONE; + } + + plobj = + nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj); + if (plobj) + continue; + + obj_data->config_state = CONFIG_STATE_ADDED_BY_US; + nm_platform_routing_rule_add(self->platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj)); + } +} + +void +nmp_rules_manager_track_from_platform(NMPRulesManager *self, + NMPlatform * platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag) +{ + NMPLookup lookup; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (!platform) + platform = self->platform; + else + g_return_if_fail(NM_IS_PLATFORM(platform)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o); + + if (addr_family != AF_UNSPEC && rr->addr_family != addr_family) + continue; + + nmp_rules_manager_track(self, rr, tracking_priority, user_tag, NULL); + } +} + +/*****************************************************************************/ + +void +nmp_rules_manager_track_default(NMPRulesManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + /* track the default rules. See also `man ip-rule`. */ + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET, + .priority = 32767, + .table = RT_TABLE_DEFAULT, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 0, + .table = RT_TABLE_LOCAL, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + nmp_rules_manager_track(self, + &((NMPlatformRoutingRule){ + .addr_family = AF_INET6, + .priority = 32766, + .table = RT_TABLE_MAIN, + .action = FR_ACT_TO_TBL, + .protocol = RTPROT_KERNEL, + }), + track_priority, + user_tag, + NULL); + } +} + +static void +_rules_init(NMPRulesManager *self) +{ + if (self->by_data) + return; + + self->by_data = + g_hash_table_new_full(_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy); + self->by_obj = + g_hash_table_new_full(_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy); + self->by_user_tag = g_hash_table_new_full(_rules_user_tag_hash, + _rules_user_tag_equal, + NULL, + _rules_user_tag_destroy); +} + +/*****************************************************************************/ + +NMPRulesManager * +nmp_rules_manager_new(NMPlatform *platform) +{ + NMPRulesManager *self; + + g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL); + + self = g_slice_new(NMPRulesManager); + *self = (NMPRulesManager){ + .ref_count = 1, + .platform = g_object_ref(platform), + }; + return self; +} + +void +nmp_rules_manager_ref(NMPRulesManager *self) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + self->ref_count++; +} + +void +nmp_rules_manager_unref(NMPRulesManager *self) +{ + g_return_if_fail(NMP_IS_RULES_MANAGER(self)); + + if (--self->ref_count > 0) + return; + + if (self->by_data) { + g_hash_table_destroy(self->by_user_tag); + g_hash_table_destroy(self->by_obj); + g_hash_table_destroy(self->by_data); + } + g_object_unref(self->platform); + g_slice_free(NMPRulesManager, self); +} diff --git a/src/libnm-platform/nmp-rules-manager.h b/src/libnm-platform/nmp-rules-manager.h new file mode 100644 index 00000000..69cf9075 --- /dev/null +++ b/src/libnm-platform/nmp-rules-manager.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NMP_RULES_MANAGER_H__ +#define __NMP_RULES_MANAGER_H__ + +#include "nm-platform.h" + +/*****************************************************************************/ + +#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new) + +typedef struct _NMPRulesManager NMPRulesManager; + +NMPRulesManager *nmp_rules_manager_new(NMPlatform *platform); + +void nmp_rules_manager_ref(NMPRulesManager *self); +void nmp_rules_manager_unref(NMPRulesManager *self); + +#define nm_auto_unref_rules_manager nm_auto(_nmp_rules_manager_unref) +NM_AUTO_DEFINE_FCN0(NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref); + +void nmp_rules_manager_track(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gint32 track_priority, + gconstpointer user_tag, + gconstpointer user_tag_untrack); + +void nmp_rules_manager_track_default(NMPRulesManager *self, + int addr_family, + gint32 track_priority, + gconstpointer user_tag); + +void nmp_rules_manager_track_from_platform(NMPRulesManager *self, + NMPlatform * platform, + int addr_family, + gint32 tracking_priority, + gconstpointer user_tag); + +void nmp_rules_manager_untrack(NMPRulesManager * self, + const NMPlatformRoutingRule *routing_rule, + gconstpointer user_tag); + +void nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag); + +void nmp_rules_manager_untrack_all(NMPRulesManager *self, + gconstpointer user_tag, + gboolean all /* or only dirty */); + +void nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules); + +/*****************************************************************************/ + +#endif /* __NMP_RULES_MANAGER_H__ */ diff --git a/src/libnm-platform/tests/meson.build b/src/libnm-platform/tests/meson.build new file mode 100644 index 00000000..bec385eb --- /dev/null +++ b/src/libnm-platform/tests/meson.build @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +exe = executable( + 'test-nm-platform', + 'test-nm-platform.c', + include_directories: [ + src_inc, + top_inc, + ], + dependencies: [ + glib_dep, + libudev_dep, + ], + link_with: [ + libnm_platform, + libnm_base, + libnm_udev_aux, + libnm_log_core, + libnm_glib_aux, + libnm_std_aux, + libc_siphash, + ], +) + +test( + 'src/libnm-platform/tests/test-nm-platform', + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c new file mode 100644 index 00000000..d4de0dd5 --- /dev/null +++ b/src/libnm-platform/tests/test-nm-platform.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-prog.h" + +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-netlink.h" +#include "libnm-platform/nmp-netns.h" +#include "libnm-platform/nm-platform-utils.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +/*****************************************************************************/ + +void +_nm_logging_clear_platform_logging_cache(void) +{ + /* this symbols is required by nm-log-core library. */ +} + +/*****************************************************************************/ + +static void +test_use_symbols(void) +{ + static void (*const SYMBOLS[])(void) = { + (void (*)(void)) nl_nlmsghdr_to_str, + (void (*)(void)) nlmsg_hdr, + (void (*)(void)) nlmsg_reserve, + (void (*)(void)) nla_reserve, + (void (*)(void)) nlmsg_alloc_size, + (void (*)(void)) nlmsg_alloc, + (void (*)(void)) nlmsg_alloc_convert, + (void (*)(void)) nlmsg_alloc_simple, + (void (*)(void)) nlmsg_free, + (void (*)(void)) nlmsg_append, + (void (*)(void)) nlmsg_parse, + (void (*)(void)) nlmsg_put, + (void (*)(void)) nla_strlcpy, + (void (*)(void)) nla_memcpy, + (void (*)(void)) nla_put, + (void (*)(void)) nla_find, + (void (*)(void)) nla_nest_cancel, + (void (*)(void)) nla_nest_start, + (void (*)(void)) nla_nest_end, + (void (*)(void)) nla_parse, + (void (*)(void)) nlmsg_get_proto, + (void (*)(void)) nlmsg_set_proto, + (void (*)(void)) nlmsg_set_src, + (void (*)(void)) nlmsg_get_creds, + (void (*)(void)) nlmsg_set_creds, + (void (*)(void)) genlmsg_put, + (void (*)(void)) genlmsg_data, + (void (*)(void)) genlmsg_user_hdr, + (void (*)(void)) genlmsg_hdr, + (void (*)(void)) genlmsg_user_data, + (void (*)(void)) genlmsg_attrdata, + (void (*)(void)) genlmsg_len, + (void (*)(void)) genlmsg_attrlen, + (void (*)(void)) genlmsg_valid_hdr, + (void (*)(void)) genlmsg_parse, + (void (*)(void)) genl_ctrl_resolve, + (void (*)(void)) nl_socket_alloc, + (void (*)(void)) nl_socket_free, + (void (*)(void)) nl_socket_get_fd, + (void (*)(void)) nl_socket_get_local_port, + (void (*)(void)) nl_socket_get_msg_buf_size, + (void (*)(void)) nl_socket_set_passcred, + (void (*)(void)) nl_socket_set_msg_buf_size, + (void (*)(void)) nlmsg_get_dst, + (void (*)(void)) nl_socket_set_nonblocking, + (void (*)(void)) nl_socket_set_buffer_size, + (void (*)(void)) nl_socket_add_memberships, + (void (*)(void)) nl_socket_set_ext_ack, + (void (*)(void)) nl_socket_disable_msg_peek, + (void (*)(void)) nl_connect, + (void (*)(void)) nl_wait_for_ack, + (void (*)(void)) nl_recvmsgs, + (void (*)(void)) nl_sendmsg, + (void (*)(void)) nl_send_iovec, + (void (*)(void)) nl_complete_msg, + (void (*)(void)) nl_send, + (void (*)(void)) nl_send_auto, + (void (*)(void)) nl_recv, + + (void (*)(void)) nmp_netns_bind_to_path, + (void (*)(void)) nmp_netns_bind_to_path_destroy, + (void (*)(void)) nmp_netns_get_current, + (void (*)(void)) nmp_netns_get_fd_mnt, + (void (*)(void)) nmp_netns_get_fd_net, + (void (*)(void)) nmp_netns_get_initial, + (void (*)(void)) nmp_netns_is_initial, + (void (*)(void)) nmp_netns_new, + (void (*)(void)) nmp_netns_pop, + (void (*)(void)) nmp_netns_push, + (void (*)(void)) nmp_netns_push_type, + + NULL, + }; + + /* The only (not very exciting) purpose of this test is to see that + * we can use various symbols and don't get a linker error. */ + assert(G_N_ELEMENTS(SYMBOLS) == NM_PTRARRAY_LEN(SYMBOLS) + 1); +} + +/*****************************************************************************/ + +static void +test_nmp_link_mode_all_advertised_modes_bits(void) +{ + guint32 flags[(SCHAR_MAX + 1) / 32]; + guint max_bit; + int i; + + memset(flags, 0, sizeof(flags)); + + max_bit = 0; + for (i = 0; i < (int) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes_bits); i++) { + if (i > 0) { + g_assert_cmpint(_nmp_link_mode_all_advertised_modes_bits[i - 1], + <, + _nmp_link_mode_all_advertised_modes_bits[i]); + } + g_assert_cmpint(_nmp_link_mode_all_advertised_modes_bits[i], <, SCHAR_MAX); + g_assert_cmpint(_nmp_link_mode_all_advertised_modes_bits[i] / 32, <, G_N_ELEMENTS(flags)); + flags[_nmp_link_mode_all_advertised_modes_bits[i] / 32] |= + (1u << (_nmp_link_mode_all_advertised_modes_bits[i] % 32u)); + max_bit = NM_MAX(max_bit, _nmp_link_mode_all_advertised_modes_bits[i]); + } + + g_assert_cmpint((max_bit + 31u) / 32u, ==, G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes)); + + for (i = 0; i < (int) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes); i++) { + if (flags[i] != _nmp_link_mode_all_advertised_modes[i]) { + g_error("_nmp_link_mode_all_advertised_modes[%d] should be 0x%0x but is 0x%0x " + "(according to the bits in _nmp_link_mode_all_advertised_modes_bits)", + i, + flags[i], + _nmp_link_mode_all_advertised_modes[i]); + } + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/nm-platform/test_use_symbols", test_use_symbols); + g_test_add_func("/nm-platform/test_nmp_link_mode_all_advertised_modes_bits", + test_nmp_link_mode_all_advertised_modes_bits); + + return g_test_run(); +} diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c new file mode 100644 index 00000000..148657ea --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c @@ -0,0 +1,908 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + * Copyright (C) 2011 Intel Corporation. All rights reserved. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-wifi-utils-nl80211.h" + +#include <sys/ioctl.h> +#include <net/ethernet.h> +#include <unistd.h> +#include <linux/nl80211.h> +#include <linux/if.h> + +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-netlink.h" +#include "nm-wifi-utils-private.h" +#include "libnm-platform/nm-platform-utils.h" + +#define _NMLOG_PREFIX_NAME "wifi-nl80211" +#define _NMLOG_DOMAIN LOGD_PLATFORM | LOGD_WIFI +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _ifname_buf[IFNAMSIZ]; \ + const char *_ifname = \ + self ? nmp_utils_if_indextoname(self->parent.ifindex, _ifname_buf) : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + _ifname ?: NULL, \ + NULL, \ + "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +typedef struct { + NMWifiUtils parent; + struct nl_sock *nl_sock; + guint32 * freqs; + int id; + int num_freqs; + int phy; + bool can_wowlan : 1; +} NMWifiUtilsNl80211; + +typedef struct { + NMWifiUtilsClass parent; +} NMWifiUtilsNl80211Class; + +G_DEFINE_TYPE(NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS) + +static int +ack_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_STOP; +} + +static int +finish_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_SKIP; +} + +static int +error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *done = arg; + *done = err->error; + return NL_SKIP; +} + +static struct nl_msg * +_nl80211_alloc_msg(int id, int ifindex, int phy, guint32 cmd, guint32 flags) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc(); + genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex); + if (phy != -1) + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, phy); + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +nl80211_alloc_msg(NMWifiUtilsNl80211 *self, guint32 cmd, guint32 flags) +{ + return _nl80211_alloc_msg(self->id, self->parent.ifindex, self->phy, cmd, flags); +} + +static int +nl80211_send_and_recv(NMWifiUtilsNl80211 *self, + struct nl_msg * msg, + int (*valid_handler)(struct nl_msg *, void *), + void *valid_data) +{ + int err; + int done = 0; + const struct nl_cb cb = { + .err_cb = error_handler, + .err_arg = &done, + .finish_cb = finish_handler, + .finish_arg = &done, + .ack_cb = ack_handler, + .ack_arg = &done, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg != NULL, -ENOMEM); + + err = nl_send_auto(self->nl_sock, msg); + if (err < 0) + return err; + + /* Loop until one of our NL callbacks says we're done; on success + * done will be 1, on error it will be < 0. + */ + while (!done) { + err = nl_recvmsgs(self->nl_sock, &cb); + if (err < 0 && err != -EAGAIN) { + /* Kernel scan list can change while we are dumping it, as new scan + * results from H/W can arrive. BSS info is assured to be consistent + * and we don't need consistent view of whole scan list. Hence do + * not warn on DUMP_INTR error for get scan command. + */ + if (err == -NME_NL_DUMP_INTR + && genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN) + break; + + _LOGW("nl_recvmsgs() error: (%d) %s", err, nm_strerror(err)); + break; + } + } + + if (err >= 0 && done < 0) + err = done; + return err; +} + +static void +dispose(GObject *object) +{ + NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211(object); + + nm_clear_g_free(&self->freqs); +} + +struct nl80211_iface_info { + _NM80211Mode mode; + uint32_t freq; +}; + +static int +nl80211_iface_info_handler(struct nl_msg *msg, void *arg) +{ + struct nl80211_iface_info *info = arg; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (!tb[NL80211_ATTR_IFTYPE]) + return NL_SKIP; + + switch (nla_get_u32(tb[NL80211_ATTR_IFTYPE])) { + case NL80211_IFTYPE_ADHOC: + info->mode = _NM_802_11_MODE_ADHOC; + break; + case NL80211_IFTYPE_AP: + info->mode = _NM_802_11_MODE_AP; + break; + case NL80211_IFTYPE_STATION: + info->mode = _NM_802_11_MODE_INFRA; + break; + case NL80211_IFTYPE_MESH_POINT: + info->mode = _NM_802_11_MODE_MESH; + break; + } + + if (tb[NL80211_ATTR_WIPHY_FREQ] != NULL) + info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); + + return NL_SKIP; +} + +static _NM80211Mode +wifi_nl80211_get_mode(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + struct nl80211_iface_info iface_info = { + .mode = _NM_802_11_MODE_UNKNOWN, + }; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) + return _NM_802_11_MODE_UNKNOWN; + + return iface_info.mode; +} + +static gboolean +wifi_nl80211_set_mode(NMWifiUtils *data, const _NM80211Mode mode) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); + + switch (mode) { + case _NM_802_11_MODE_INFRA: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION); + break; + case _NM_802_11_MODE_ADHOC: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_ADHOC); + break; + case _NM_802_11_MODE_AP: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP); + break; + case _NM_802_11_MODE_MESH: + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT); + break; + default: + g_assert_not_reached(); + } + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +wifi_nl80211_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_POWER_SAVE, 0); + NLA_PUT_U32(msg, + NL80211_ATTR_PS_STATE, + powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static int +nl80211_get_wake_on_wlan_handler(struct nl_msg *msg, void *arg) +{ + _NMSettingWirelessWakeOnWLan *wowl = arg; + struct nlattr * attrs[NL80211_ATTR_MAX + 1]; + struct nlattr * trig[NUM_NL80211_WOWLAN_TRIG]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + nla_parse_arr(attrs, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) + return NL_SKIP; + + nla_parse_arr(trig, + nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + NULL); + + *wowl = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE; + if (trig[NL80211_WOWLAN_TRIG_ANY]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY; + if (trig[NL80211_WOWLAN_TRIG_DISCONNECT]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT; + if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC; + if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE; + if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST; + if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE; + if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE; + if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) + *wowl |= _NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP; + + return NL_SKIP; +} + +static _NMSettingWirelessWakeOnWLan +wifi_nl80211_get_wake_on_wlan(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + _NMSettingWirelessWakeOnWLan wowl = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WOWLAN, 0); + + nl80211_send_and_recv(self, msg, nl80211_get_wake_on_wlan_handler, &wowl); + + return wowl; +} + +static gboolean +wifi_nl80211_set_wake_on_wlan(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nlattr * triggers; + int err; + + if (wowl == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) + return TRUE; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WOWLAN, 0); + + triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); + if (!triggers) + goto nla_put_failure; + + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); + if (NM_FLAGS_HAS(wowl, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE)) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); + + nla_nest_end(msg, triggers); + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static guint32 +wifi_nl80211_get_freq(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + struct nl80211_iface_info iface_info = {}; + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0) + return 0; + + return iface_info.freq; +} + +static guint32 +wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data; + int i; + + for (i = 0; i < self->num_freqs; i++) { + while (*freqs) { + if (self->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + return 0; +} + +/* @divisor: pass what value @xbm should be divided by to get dBm */ +static guint32 +nl80211_xbm_to_percent(gint32 xbm, guint32 divisor) +{ +#define NOISE_FLOOR_DBM -90 +#define SIGNAL_MAX_DBM -20 + + xbm /= divisor; + xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM); + + return 100 + - 70 + * (((float) SIGNAL_MAX_DBM - (float) xbm) + / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM)); +} + +struct nl80211_station_info { + gboolean valid; + guint8 bssid[ETH_ALEN]; + guint32 txrate; + gboolean txrate_valid; + guint8 signal; + gboolean signal_valid; +}; + +static int +nl80211_station_dump_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy stats_policy[] = { + [NL80211_STA_INFO_INACTIVE_TIME] = {.type = NLA_U32}, + [NL80211_STA_INFO_RX_BYTES] = {.type = NLA_U32}, + [NL80211_STA_INFO_TX_BYTES] = {.type = NLA_U32}, + [NL80211_STA_INFO_RX_PACKETS] = {.type = NLA_U32}, + [NL80211_STA_INFO_TX_PACKETS] = {.type = NLA_U32}, + [NL80211_STA_INFO_SIGNAL] = {.type = NLA_U8}, + [NL80211_STA_INFO_TX_BITRATE] = {.type = NLA_NESTED}, + [NL80211_STA_INFO_LLID] = {.type = NLA_U16}, + [NL80211_STA_INFO_PLID] = {.type = NLA_U16}, + [NL80211_STA_INFO_PLINK_STATE] = {.type = NLA_U8}, + [NL80211_STA_INFO_STA_FLAGS] = {.minlen = sizeof(struct nl80211_sta_flag_update)}, + [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = {.type = NLA_U8}, + }; + static const struct nla_policy rate_policy[] = { + [NL80211_RATE_INFO_BITRATE] = {.type = NLA_U16}, + [NL80211_RATE_INFO_MCS] = {.type = NLA_U8}, + [NL80211_RATE_INFO_40_MHZ_WIDTH] = {.type = NLA_FLAG}, + [NL80211_RATE_INFO_SHORT_GI] = {.type = NLA_FLAG}, + }; + struct nlattr * rinfo[G_N_ELEMENTS(rate_policy)]; + struct nlattr * sinfo[G_N_ELEMENTS(stats_policy)]; + struct nl80211_station_info *info = arg; + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_MAC] == NULL) + return NL_SKIP; + + if (tb[NL80211_ATTR_STA_INFO] == NULL) + return NL_SKIP; + + if (nla_parse_nested_arr(sinfo, tb[NL80211_ATTR_STA_INFO], stats_policy)) + return NL_SKIP; + + if (sinfo[NL80211_STA_INFO_STA_FLAGS] != NULL) { + const struct nl80211_sta_flag_update *flags = nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]); + + if (flags->mask & ~flags->set & (1 << NL80211_STA_FLAG_ASSOCIATED)) + return NL_SKIP; + } + + memcpy(info->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN); + info->valid = TRUE; + + if (sinfo[NL80211_STA_INFO_TX_BITRATE] != NULL + && !nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy) + && rinfo[NL80211_RATE_INFO_BITRATE] != NULL) { + /* convert from nl80211's units of 100kbps to NM's kbps */ + info->txrate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100; + info->txrate_valid = TRUE; + } + + if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) { + info->signal = + nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), 1); + info->signal_valid = TRUE; + } else if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG] != NULL) { + /* Fall back to beacon signal strength */ + info->signal = + nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]), + 1); + info->signal_valid = TRUE; + } + + return NL_SKIP; +} + +static gboolean +wifi_nl80211_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nl80211_station_info sta_info = {}; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, NLM_F_DUMP); + + nl80211_send_and_recv(self, msg, nl80211_station_dump_handler, &sta_info); + + if (!sta_info.valid || (out_quality && !sta_info.signal_valid) + || (out_rate && !sta_info.txrate_valid)) + return FALSE; + + if (out_bssid) + memcpy(out_bssid, sta_info.bssid, ETH_ALEN); + + if (out_quality) + *out_quality = sta_info.signal; + + if (out_rate) + *out_rate = sta_info.txrate; + + return TRUE; +} + +static gboolean +wifi_nl80211_indicate_addressing_running(NMWifiUtils *data, gboolean running) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, + running ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */ + : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */, + 0); + /* Despite the DHCP name, we're using this for any type of IP addressing, + * DHCPv4, DHCPv6, and IPv6 SLAAC. + */ + NLA_PUT_U16(msg, 179 /* NL80211_ATTR_CRIT_PROT_ID */, 1 /* NL80211_CRIT_PROTO_DHCP */); + if (running) { + /* Give DHCP 5 seconds to complete */ + NLA_PUT_U16(msg, 180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */, 5000); + } + + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +struct nl80211_device_info { + NMWifiUtilsNl80211 *self; + int phy; + guint32 * freqs; + int num_freqs; + guint32 freq; + guint32 caps; + gboolean can_scan; + gboolean can_scan_ssid; + gboolean supported; + gboolean success; + gboolean can_wowlan; +}; + +#define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00 +#define WLAN_CIPHER_SUITE_WEP40 0x000FAC01 +#define WLAN_CIPHER_SUITE_TKIP 0x000FAC02 +#define WLAN_CIPHER_SUITE_CCMP 0x000FAC04 +#define WLAN_CIPHER_SUITE_WEP104 0x000FAC05 +#define WLAN_CIPHER_SUITE_AES_CMAC 0x000FAC06 +#define WLAN_CIPHER_SUITE_GCMP 0x000FAC08 +#define WLAN_CIPHER_SUITE_SMS4 0x00147201 + +static int +nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy freq_policy[] = { + [NL80211_FREQUENCY_ATTR_FREQ] = {.type = NLA_U32}, + [NL80211_FREQUENCY_ATTR_DISABLED] = {.type = NLA_FLAG}, +#ifdef NL80211_FREQUENCY_ATTR_NO_IR + [NL80211_FREQUENCY_ATTR_NO_IR] = {.type = NLA_FLAG}, +#else + [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = {.type = NLA_FLAG}, + [NL80211_FREQUENCY_ATTR_NO_IBSS] = {.type = NLA_FLAG}, +#endif + [NL80211_FREQUENCY_ATTR_RADAR] = {.type = NLA_FLAG}, + [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = {.type = NLA_U32}, + }; + struct nlattr * tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nl80211_device_info *info = arg; + NMWifiUtilsNl80211 * self = info->self; + struct nlattr * tb_band[NL80211_BAND_ATTR_MAX + 1]; + struct nlattr * tb_freq[G_N_ELEMENTS(freq_policy)]; + struct nlattr * nl_band; + struct nlattr * nl_freq; + int rem_freq; + int rem_band; + int freq_idx; + +#ifdef NL80211_FREQUENCY_ATTR_NO_IR + G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR + && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR); +#else + G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS); +#endif + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_WIPHY] == NULL || tb[NL80211_ATTR_WIPHY_BANDS] == NULL) + return NL_SKIP; + + info->phy = nla_get_u32(tb[NL80211_ATTR_WIPHY]); + + if (tb[NL80211_ATTR_WIPHY_FREQ]) + info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); + else + info->freq = 0; + + if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) { + info->can_scan_ssid = nla_get_u8(tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0; + } else { + /* old kernel that only had mac80211, so assume it can */ + info->can_scan_ssid = TRUE; + } + + if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) { + struct nlattr *nl_cmd; + int i; + + nla_for_each_nested (nl_cmd, tb[NL80211_ATTR_SUPPORTED_COMMANDS], i) { + switch (nla_get_u32(nl_cmd)) { + case NL80211_CMD_TRIGGER_SCAN: + info->can_scan = TRUE; + break; + case NL80211_CMD_CONNECT: + case NL80211_CMD_AUTHENTICATE: + /* Only devices that support CONNECT or AUTH actually support + * 802.11, unlike say ipw2x00 (up to at least kernel 3.4) which + * has minimal info support, but no actual command support. + * This check mirrors what wpa_supplicant does to determine + * whether or not to use the nl80211 driver. + */ + info->supported = TRUE; + break; + default: + break; + } + } + } + + /* Find number of supported frequencies */ + info->num_freqs = 0; + + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0) + return NL_SKIP; + + nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { + if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0) + continue; + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->num_freqs++; + } + } + + /* Read supported frequencies */ + info->freqs = g_malloc0(sizeof(guint32) * info->num_freqs); + + freq_idx = 0; + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0) + return NL_SKIP; + + nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { + if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0) + continue; + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->freqs[freq_idx] = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + + info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID; + + if (info->freqs[freq_idx] > 2400 && info->freqs[freq_idx] < 2500) + info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ; + if (info->freqs[freq_idx] > 4900 && info->freqs[freq_idx] < 6000) + info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ; + + freq_idx++; + } + } + + /* Read security/encryption support */ + if (tb[NL80211_ATTR_CIPHER_SUITES]) { + guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]); + guint i, num; + + num = nla_len(tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof(guint32); + for (i = 0; i < num; i++) { + switch (ciphers[i]) { + case WLAN_CIPHER_SUITE_WEP40: + info->caps |= _NM_WIFI_DEVICE_CAP_CIPHER_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + info->caps |= _NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + info->caps |= (_NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_WPA); + break; + case WLAN_CIPHER_SUITE_CCMP: + info->caps |= (_NM_WIFI_DEVICE_CAP_CIPHER_CCMP | _NM_WIFI_DEVICE_CAP_RSN); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_SMS4: + break; + default: + _LOGD("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.", ciphers[i]); + break; + } + } + } + + if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) { + struct nlattr *nl_mode; + int i; + + nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) { + switch (nla_type(nl_mode)) { + case NL80211_IFTYPE_AP: + info->caps |= _NM_WIFI_DEVICE_CAP_AP; + break; + case NL80211_IFTYPE_ADHOC: + info->caps |= _NM_WIFI_DEVICE_CAP_ADHOC; + break; + case NL80211_IFTYPE_MESH_POINT: + info->caps |= _NM_WIFI_DEVICE_CAP_MESH; + break; + } + } + } + + if (tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]) + info->can_wowlan = TRUE; + + if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN]) + info->caps |= _NM_WIFI_DEVICE_CAP_IBSS_RSN; + + info->success = TRUE; + + return NL_SKIP; +} + +static guint32 +wifi_nl80211_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + struct nl80211_device_info device_info = {.self = self}; + int i; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0); + + if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { + _LOGW("NL80211_CMD_GET_WIPHY request failed"); + return 0; + } + + for (i = 0; i < self->num_freqs; i++) { + if (device_info.freq == self->freqs[i]) + return i + 1; + } + return 0; +} + +static gboolean +wifi_nl80211_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + if (channel > self->num_freqs) + return FALSE; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WIPHY, 0); + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1]); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static gboolean +wifi_nl80211_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsNl80211 * self = (NMWifiUtilsNl80211 *) data; + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0); + NLA_PUT(msg, NL80211_ATTR_MESH_ID, len, ssid); + err = nl80211_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +static void +nm_wifi_utils_nl80211_init(NMWifiUtilsNl80211 *self) +{} + +static void +nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass); + + object_class->dispose = dispose; + + wifi_utils_class->get_mode = wifi_nl80211_get_mode; + wifi_utils_class->set_mode = wifi_nl80211_set_mode; + wifi_utils_class->set_powersave = wifi_nl80211_set_powersave; + wifi_utils_class->get_wake_on_wlan = wifi_nl80211_get_wake_on_wlan, + wifi_utils_class->set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan, + wifi_utils_class->get_freq = wifi_nl80211_get_freq; + wifi_utils_class->find_freq = wifi_nl80211_find_freq; + wifi_utils_class->get_station = wifi_nl80211_get_station; + wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running; + wifi_utils_class->get_mesh_channel = wifi_nl80211_get_mesh_channel; + wifi_utils_class->set_mesh_channel = wifi_nl80211_set_mesh_channel; + wifi_utils_class->set_mesh_ssid = wifi_nl80211_set_mesh_ssid; +} + +NMWifiUtils * +nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl) +{ + gs_unref_object NMWifiUtilsNl80211 *self = NULL; + nm_auto_nlmsg struct nl_msg * msg = NULL; + struct nl80211_device_info device_info = {}; + + if (!genl) + return NULL; + + self = g_object_new(NM_TYPE_WIFI_UTILS_NL80211, NULL); + + self->parent.ifindex = ifindex; + self->nl_sock = genl; + + self->id = genl_ctrl_resolve(self->nl_sock, "nl80211"); + if (self->id < 0) { + _LOGD("genl_ctrl_resolve: failed to resolve \"nl80211\""); + return NULL; + } + + self->phy = -1; + + msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0); + + device_info.self = self; + if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) { + _LOGD("NL80211_CMD_GET_WIPHY request failed"); + return NULL; + } + + if (!device_info.success) { + _LOGD("NL80211_CMD_GET_WIPHY request indicated failure"); + return NULL; + } + + if (!device_info.supported) { + _LOGD("driver does not fully support nl80211, falling back to WEXT"); + return NULL; + } + + if (!device_info.can_scan_ssid) { + _LOGE("driver does not support SSID scans"); + return NULL; + } + + if (device_info.num_freqs == 0 || device_info.freqs == NULL) { + _LOGE("driver reports no supported frequencies"); + return NULL; + } + + if (device_info.caps == 0) { + _LOGE("driver doesn't report support of any encryption"); + return NULL; + } + + self->phy = device_info.phy; + self->freqs = device_info.freqs; + self->num_freqs = device_info.num_freqs; + self->parent.caps = device_info.caps; + self->can_wowlan = device_info.can_wowlan; + + _LOGD("using nl80211 for Wi-Fi device control"); + return (NMWifiUtils *) g_steal_pointer(&self); +} diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.h b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.h new file mode 100644 index 00000000..4a633307 --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 Intel Corporation. All rights reserved. + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_NL80211_H__ +#define __WIFI_UTILS_NL80211_H__ + +#include "nm-wifi-utils.h" +#include "libnm-platform/nm-netlink.h" + +#define NM_TYPE_WIFI_UTILS_NL80211 (nm_wifi_utils_nl80211_get_type()) +#define NM_WIFI_UTILS_NL80211(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211)) +#define NM_WIFI_UTILS_NL80211_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class)) +#define NM_IS_WIFI_UTILS_NL80211(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_NL80211)) +#define NM_IS_WIFI_UTILS_NL80211_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_NL80211)) +#define NM_WIFI_UTILS_NL80211_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class)) + +GType nm_wifi_utils_nl80211_get_type(void); + +NMWifiUtils *nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl); + +#endif /* __WIFI_UTILS_NL80211_H__ */ diff --git a/src/libnm-platform/wifi/nm-wifi-utils-private.h b/src/libnm-platform/wifi/nm-wifi-utils-private.h new file mode 100644 index 00000000..bc1e75ec --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils-private.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_PRIVATE_H__ +#define __WIFI_UTILS_PRIVATE_H__ + +#include "nm-wifi-utils.h" + +typedef struct { + GObjectClass parent; + + _NM80211Mode (*get_mode)(NMWifiUtils *data); + + gboolean (*set_mode)(NMWifiUtils *data, const _NM80211Mode mode); + + /* Set power saving mode on an interface */ + gboolean (*set_powersave)(NMWifiUtils *data, guint32 powersave); + + /* Get WakeOnWLAN configuration on an interface */ + _NMSettingWirelessWakeOnWLan (*get_wake_on_wlan)(NMWifiUtils *data); + + /* Set WakeOnWLAN mode on an interface */ + gboolean (*set_wake_on_wlan)(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl); + + /* Return current frequency in MHz (really associated BSS frequency) */ + guint32 (*get_freq)(NMWifiUtils *data); + + /* Return first supported frequency in the zero-terminated list */ + guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs); + + /* + * @out_bssid: must be NULL or an ETH_ALEN-byte buffer + * @out_quality: receives signal strength percentage 0 - 100% for the current BSSID, if not NULL + * @out_rate: receives current bitrate in Kbps if not NULL + * + * Returns %TRUE on succcess, %FALSE on errors or if not associated. + */ + gboolean (*get_station)(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + + /* OLPC Mesh-only functions */ + + guint32 (*get_mesh_channel)(NMWifiUtils *data); + + /* channel == 0 means "auto channel" */ + gboolean (*set_mesh_channel)(NMWifiUtils *data, guint32 channel); + + /* ssid == NULL means "auto SSID" */ + gboolean (*set_mesh_ssid)(NMWifiUtils *data, const guint8 *ssid, gsize len); + + gboolean (*indicate_addressing_running)(NMWifiUtils *data, gboolean running); +} NMWifiUtilsClass; + +struct NMWifiUtils { + GObject parent; + + int ifindex; + _NMDeviceWifiCapabilities caps; +}; + +#endif /* __WIFI_UTILS_PRIVATE_H__ */ diff --git a/src/libnm-platform/wifi/nm-wifi-utils-wext.c b/src/libnm-platform/wifi/nm-wifi-utils-wext.c new file mode 100644 index 00000000..13b47c2c --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils-wext.c @@ -0,0 +1,834 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-wifi-utils-wext.h" + +#include <sys/ioctl.h> +#include <net/ethernet.h> +#include <unistd.h> + +/* Hacks necessary to #include wireless.h; yay for WEXT */ +#ifndef __user + #define __user +#endif +#include <sys/types.h> +#include <linux/types.h> +#include <sys/socket.h> +#include <linux/wireless.h> + +#include "libnm-log-core/nm-logging.h" +#include "nm-wifi-utils-private.h" +#include "libnm-platform/nm-platform-utils.h" + +typedef struct { + NMWifiUtils parent; + int fd; + struct iw_quality max_qual; + gint8 num_freqs; + guint32 freqs[IW_MAX_FREQUENCIES]; +} NMWifiUtilsWext; + +typedef struct { + NMWifiUtilsClass parent; +} NMWifiUtilsWextClass; + +G_DEFINE_TYPE(NMWifiUtilsWext, nm_wifi_utils_wext, NM_TYPE_WIFI_UTILS) + +/* Until a new wireless-tools comes out that has the defs and the structure, + * need to copy them here. + */ +/* Scan capability flags - in (struct iw_range *)->scan_capa */ +#define NM_IW_SCAN_CAPA_NONE 0x00 +#define NM_IW_SCAN_CAPA_ESSID 0x01 + +struct iw_range_with_scan_capa { + guint32 throughput; + guint32 min_nwid; + guint32 max_nwid; + guint16 old_num_channels; + guint8 old_num_frequency; + + guint8 scan_capa; + /* don't need the rest... */ +}; + +#define _NMLOG_PREFIX_NAME "wifi-wext" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (domain), \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +static guint32 +iw_freq_to_uint32(const struct iw_freq *freq) +{ + if (freq->e == 0) { + /* Some drivers report channel not frequency. Convert to a + * frequency; but this assumes that the device is in b/g mode. + */ + if ((freq->m >= 1) && (freq->m <= 13)) + return 2407 + (5 * freq->m); + else if (freq->m == 14) + return 2484; + } + return (guint32) ((((double) freq->m) * nm_utils_exp10(freq->e)) / 1000000.0); +} + +static void +dispose(GObject *object) +{ + NMWifiUtilsWext *wext = NM_WIFI_UTILS_WEXT(object); + + wext->fd = nm_close(wext->fd); +} + +static gboolean +get_ifname(int ifindex, char *buffer, const char *op) +{ + int errsv; + + if (!nmp_utils_if_indextoname(ifindex, buffer)) { + errsv = errno; + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "error getting interface name for ifindex %d, operation '%s': %s (%d)", + ifindex, + op, + nm_strerror_native(errsv), + errsv); + return FALSE; + } + + return TRUE; +} + +static _NM80211Mode +wifi_wext_get_mode_ifname(NMWifiUtils *data, const char *ifname) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + int errsv; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (ioctl(wext->fd, SIOCGIWMODE, &wrq) < 0) { + errsv = errno; + if (errsv != ENODEV) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, "(%s): error %d getting card mode", ifname, errsv); + } + return _NM_802_11_MODE_UNKNOWN; + } + + switch (wrq.u.mode) { + case IW_MODE_ADHOC: + return _NM_802_11_MODE_ADHOC; + case IW_MODE_MASTER: + return _NM_802_11_MODE_AP; + case IW_MODE_INFRA: + case IW_MODE_AUTO: /* hack for WEXT devices reporting IW_MODE_AUTO */ + return _NM_802_11_MODE_INFRA; + default: + break; + } + return _NM_802_11_MODE_UNKNOWN; +} + +static _NM80211Mode +wifi_wext_get_mode(NMWifiUtils *data) +{ + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-mode")) + return FALSE; + + return wifi_wext_get_mode_ifname(data, ifname); +} + +static gboolean +wifi_wext_set_mode(NMWifiUtils *data, const _NM80211Mode mode) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-mode")) + return FALSE; + + if (wifi_wext_get_mode_ifname(data, ifname) == mode) + return TRUE; + + memset(&wrq, 0, sizeof(struct iwreq)); + switch (mode) { + case _NM_802_11_MODE_ADHOC: + wrq.u.mode = IW_MODE_ADHOC; + break; + case _NM_802_11_MODE_AP: + wrq.u.mode = IW_MODE_MASTER; + break; + case _NM_802_11_MODE_INFRA: + wrq.u.mode = IW_MODE_INFRA; + break; + default: + g_warn_if_reached(); + return FALSE; + } + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWMODE, &wrq) < 0) { + if (errno != ENODEV) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, "(%s): error setting mode %d", ifname, mode); + } + return FALSE; + } + + return TRUE; +} + +static gboolean +wifi_wext_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-powersave")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + if (powersave == 1) { + wrq.u.power.flags = IW_POWER_ALL_R; + } else + wrq.u.power.disabled = 1; + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWPOWER, &wrq) < 0) { + if (errno != ENODEV) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error setting powersave %" G_GUINT32_FORMAT, + ifname, + powersave); + } + return FALSE; + } + + return TRUE; +} + +static guint32 +wifi_wext_get_freq(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-freq")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCGIWFREQ, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting frequency: %s", + ifname, + nm_strerror_native(errno)); + return 0; + } + + return iw_freq_to_uint32(&wrq.u.freq); +} + +static guint32 +wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + int i; + + for (i = 0; i < wext->num_freqs; i++) { + while (*freqs) { + if (wext->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + return 0; +} + +static gboolean +wifi_wext_get_bssid(NMWifiUtils *data, NMEtherAddr *out_bssid) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-bssid")) + return FALSE; + + memset(&wrq, 0, sizeof(wrq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCGIWAP, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting associated BSSID: %s", + ifname, + nm_strerror_native(errno)); + return FALSE; + } + memcpy(out_bssid, &(wrq.u.ap_addr.sa_data), ETH_ALEN); + return TRUE; +} + +static guint32 +wifi_wext_get_rate(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + int err; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-rate")) + return FALSE; + + memset(&wrq, 0, sizeof(wrq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + err = ioctl(wext->fd, SIOCGIWRATE, &wrq); + return ((err == 0) ? wrq.u.bitrate.value / 1000 : 0); +} + +static int +wext_qual_to_percent(const struct iw_quality *qual, const struct iw_quality *max_qual) +{ + int percent = -1; + int level_percent = -1; + + g_return_val_if_fail(qual != NULL, -1); + g_return_val_if_fail(max_qual != NULL, -1); + + /* Magically convert the many different WEXT quality representations to a percentage */ + + _LOGD(LOGD_WIFI, + "QL: qual %d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X ** MAX: qual " + "%d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X", + (__s8) qual->qual, + qual->qual, + qual->qual, + (__s8) qual->level, + qual->level, + qual->level, + (__s8) qual->noise, + qual->noise, + qual->noise, + qual->updated, + (__s8) max_qual->qual, + max_qual->qual, + max_qual->qual, + (__s8) max_qual->level, + max_qual->level, + max_qual->level, + (__s8) max_qual->noise, + max_qual->noise, + max_qual->noise, + max_qual->updated); + + /* Try using the card's idea of the signal quality first as long as it tells us what the max quality is. + * Drivers that fill in quality values MUST treat them as percentages, ie the "Link Quality" MUST be + * bounded by 0 and max_qual->qual, and MUST change in a linear fashion. Within those bounds, drivers + * are free to use whatever they want to calculate "Link Quality". + */ + if ((max_qual->qual != 0) && !(max_qual->updated & IW_QUAL_QUAL_INVALID) + && !(qual->updated & IW_QUAL_QUAL_INVALID)) + percent = (int) (100 * ((double) qual->qual / (double) max_qual->qual)); + + /* If the driver doesn't specify a complete and valid quality, we have two options: + * + * 1) dBm: driver must specify max_qual->level = 0, and have valid values for + * qual->level and (qual->noise OR max_qual->noise) + * 2) raw RSSI: driver must specify max_qual->level > 0, and have valid values for + * qual->level and max_qual->level + * + * This is the WEXT spec. If this interpretation is wrong, I'll fix it. Otherwise, + * If drivers don't conform to it, they are wrong and need to be fixed. + */ + + if ((max_qual->level == 0) + && !(max_qual->updated & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level == 0 */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID) /* Must have valid qual->level */ + && (((max_qual->noise > 0) + && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) /* Must have valid max_qual->noise */ + || ((qual->noise > 0) + && !(qual->updated & IW_QUAL_NOISE_INVALID))) /* OR valid qual->noise */ + ) { +/* Absolute power values (dBm) */ + +/* Reasonable fallbacks for dumb drivers that don't specify either level. */ +#define FALLBACK_NOISE_FLOOR_DBM -90 +#define FALLBACK_SIGNAL_MAX_DBM -20 + int max_level = FALLBACK_SIGNAL_MAX_DBM; + int noise = FALLBACK_NOISE_FLOOR_DBM; + int level = qual->level - 0x100; + + level = CLAMP(level, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM); + + if ((qual->noise > 0) && !(qual->updated & IW_QUAL_NOISE_INVALID)) + noise = qual->noise - 0x100; + else if ((max_qual->noise > 0) && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) + noise = max_qual->noise - 0x100; + noise = CLAMP(noise, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM - 1); + + /* A sort of signal-to-noise ratio calculation */ + level_percent = (int) (100 + - 70 + * (((double) max_level - (double) level) + / ((double) max_level - (double) noise))); + _LOGD(LOGD_WIFI, + "QL1: level_percent is %d. max_level %d, level %d, noise_floor %d.", + level_percent, + max_level, + level, + noise); + } else if ((max_qual->level != 0) + && !(max_qual->updated + & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level as upper bound */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID)) { + /* Relative power values (RSSI) */ + + int level = qual->level; + + /* Signal level is relavtive (0 -> max_qual->level) */ + level = CLAMP(level, 0, max_qual->level); + level_percent = (int) (100 * ((double) level / (double) max_qual->level)); + _LOGD(LOGD_WIFI, + "QL2: level_percent is %d. max_level %d, level %d.", + level_percent, + max_qual->level, + level); + } else if (percent == -1) { + _LOGD(LOGD_WIFI, + "QL: Could not get quality %% value from driver. Driver is probably buggy."); + } + + /* If the quality percent was 0 or doesn't exist, then try to use signal levels instead */ + if ((percent < 1) && (level_percent >= 0)) + percent = level_percent; + + _LOGD(LOGD_WIFI, "QL: Final quality percent is %d (%d).", percent, CLAMP(percent, 0, 100)); + return (CLAMP(percent, 0, 100)); +} + +static int +wifi_wext_get_qual(NMWifiUtils *data) +{ + NMWifiUtilsWext * wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + struct iw_statistics stats; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "get-qual")) + return FALSE; + + memset(&stats, 0, sizeof(stats)); + wrq.u.data.pointer = &stats; + wrq.u.data.length = sizeof(stats); + wrq.u.data.flags = 1; /* Clear updated flag */ + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (ioctl(wext->fd, SIOCGIWSTATS, &wrq) < 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): error getting signal strength: %s", + ifname, + nm_strerror_native(errno)); + return -1; + } + + return wext_qual_to_percent(&stats.qual, &wext->max_qual); +} + +static gboolean +wifi_wext_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + NMEtherAddr local_addr; + + if (!out_bssid && !out_quality && !out_rate) { + /* hm, the caller requested no parameter at all? + * Don't simply return TRUE, but at least check that + * we can successfully fetch the bssid. */ + out_bssid = &local_addr; + } + + if (out_bssid) { + if (!wifi_wext_get_bssid(data, out_bssid)) + return FALSE; + } + if (out_quality) { + *out_quality = wifi_wext_get_qual(data); + if (*out_quality < 0) + return FALSE; + } + if (out_rate) { + *out_rate = wifi_wext_get_rate(data); + if (*out_rate == 0) + return FALSE; + } + return TRUE; +} + +/*****************************************************************************/ +/* OLPC Mesh-only functions */ + +static guint32 +wifi_wext_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + guint32 freq; + int i; + + freq = nm_wifi_utils_get_freq(data); + for (i = 0; i < wext->num_freqs; i++) { + if (freq == wext->freqs[i]) + return i + 1; + } + return 0; +} + +static gboolean +wifi_wext_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char ifname[IFNAMSIZ]; + + if (!get_ifname(data->ifindex, ifname, "set-mesh-channel")) + return FALSE; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + + if (channel > 0) { + wrq.u.freq.flags = IW_FREQ_FIXED; + wrq.u.freq.e = 0; + wrq.u.freq.m = channel; + } + + if (ioctl(wext->fd, SIOCSIWFREQ, &wrq) < 0) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC, + "(%s): error setting channel to %d: %s", + ifname, + channel, + nm_strerror_native(errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +wifi_wext_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data; + struct iwreq wrq; + char buf[IW_ESSID_MAX_SIZE + 1]; + char ifname[IFNAMSIZ]; + int errsv; + + if (!get_ifname(data->ifindex, ifname, "set-mesh-ssid")) + return FALSE; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, ssid, MIN(sizeof(buf) - 1, len)); + + wrq.u.essid.pointer = (caddr_t) buf; + wrq.u.essid.length = len; + wrq.u.essid.flags = (len > 0) ? 1 : 0; /* 1=enable SSID, 0=disable/any */ + + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWESSID, &wrq) == 0) + return TRUE; + + errsv = errno; + if (errsv != ENODEV) { + gs_free char *ssid_str = NULL; + + _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC, + "(%s): error setting SSID to %s: %s", + ifname, + (ssid_str = _nm_utils_ssid_to_string_arr(ssid, len)), + nm_strerror_native(errsv)); + } + + return FALSE; +} + +/*****************************************************************************/ + +static gboolean +wext_can_scan_ifname(NMWifiUtilsWext *wext, const char *ifname) +{ + struct iwreq wrq; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + if (ioctl(wext->fd, SIOCSIWSCAN, &wrq) < 0) { + if (errno == EOPNOTSUPP) + return FALSE; + } + return TRUE; +} + +static gboolean +wext_get_range_ifname(NMWifiUtilsWext *wext, + const char * ifname, + struct iw_range *range, + guint32 * response_len) +{ + int i = 26; + gboolean success = FALSE; + struct iwreq wrq; + int errsv; + + memset(&wrq, 0, sizeof(struct iwreq)); + nm_utils_ifname_cpy(wrq.ifr_name, ifname); + wrq.u.data.pointer = (caddr_t) range; + wrq.u.data.length = sizeof(struct iw_range); + + /* Need to give some drivers time to recover after suspend/resume + * (ex ipw3945 takes a few seconds to talk to its regulatory daemon; + * see rh bz#362421) + */ + while (i-- > 0) { + if (ioctl(wext->fd, SIOCGIWRANGE, &wrq) == 0) { + if (response_len) + *response_len = wrq.u.data.length; + success = TRUE; + break; + } else { + errsv = errno; + if (errsv != EAGAIN) { + _LOGE(LOGD_PLATFORM | LOGD_WIFI, + "(%s): couldn't get driver range information (%d).", + ifname, + errsv); + break; + } + } + + g_usleep(G_USEC_PER_SEC / 4); + } + + if (i <= 0) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver took too long to respond to IWRANGE query.", + ifname); + } + + return success; +} + +#define WPA_CAPS \ + (_NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_CIPHER_CCMP | _NM_WIFI_DEVICE_CAP_WPA \ + | _NM_WIFI_DEVICE_CAP_RSN) + +static guint32 +wext_get_caps(NMWifiUtilsWext *wext, const char *ifname, struct iw_range *range) +{ + guint32 caps = _NM_WIFI_DEVICE_CAP_NONE; + + g_return_val_if_fail(wext != NULL, _NM_WIFI_DEVICE_CAP_NONE); + g_return_val_if_fail(range != NULL, _NM_WIFI_DEVICE_CAP_NONE); + + /* All drivers should support WEP by default */ + caps |= _NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | _NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + + if (range->enc_capa & IW_ENC_CAPA_CIPHER_TKIP) + caps |= _NM_WIFI_DEVICE_CAP_CIPHER_TKIP; + + if (range->enc_capa & IW_ENC_CAPA_CIPHER_CCMP) + caps |= _NM_WIFI_DEVICE_CAP_CIPHER_CCMP; + + if (range->enc_capa & IW_ENC_CAPA_WPA) + caps |= _NM_WIFI_DEVICE_CAP_WPA; + + if (range->enc_capa & IW_ENC_CAPA_WPA2) + caps |= _NM_WIFI_DEVICE_CAP_RSN; + + /* Check for cipher support but not WPA support */ + if ((caps & (_NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + && !(caps & (_NM_WIFI_DEVICE_CAP_WPA | _NM_WIFI_DEVICE_CAP_RSN))) { + _LOGW(LOGD_WIFI, + "%s: device supports WPA ciphers but not WPA protocol; WPA unavailable.", + ifname); + caps &= ~WPA_CAPS; + } + + /* Check for WPA support but not cipher support */ + if ((caps & (_NM_WIFI_DEVICE_CAP_WPA | _NM_WIFI_DEVICE_CAP_RSN)) + && !(caps & (_NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_CIPHER_CCMP))) { + _LOGW(LOGD_WIFI, + "%s: device supports WPA protocol but not WPA ciphers; WPA unavailable.", + ifname); + caps &= ~WPA_CAPS; + } + + /* There's no way to detect Ad-Hoc/AP mode support with WEXT + * (other than actually trying to do it), so just assume that + * Ad-Hoc is supported and AP isn't. + */ + caps |= _NM_WIFI_DEVICE_CAP_ADHOC; + + return caps; +} + +/*****************************************************************************/ + +static void +nm_wifi_utils_wext_init(NMWifiUtilsWext *self) +{} + +static void +nm_wifi_utils_wext_class_init(NMWifiUtilsWextClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass); + + object_class->dispose = dispose; + + wifi_utils_class->get_mode = wifi_wext_get_mode; + wifi_utils_class->set_mode = wifi_wext_set_mode; + wifi_utils_class->set_powersave = wifi_wext_set_powersave; + wifi_utils_class->get_freq = wifi_wext_get_freq; + wifi_utils_class->find_freq = wifi_wext_find_freq; + wifi_utils_class->get_station = wifi_wext_get_station; + wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel; + wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel; + wifi_utils_class->set_mesh_ssid = wifi_wext_set_mesh_ssid; +} + +NMWifiUtils * +nm_wifi_utils_wext_new(int ifindex, gboolean check_scan) +{ + NMWifiUtilsWext * wext; + struct iw_range range; + guint32 response_len = 0; + struct iw_range_with_scan_capa *scan_capa_range; + int i; + gboolean freq_valid = FALSE, has_5ghz = FALSE, has_2ghz = FALSE; + char ifname[IFNAMSIZ]; + + if (!nmp_utils_if_indextoname(ifindex, ifname)) { + _LOGW(LOGD_PLATFORM | LOGD_WIFI, "can't determine interface name for ifindex %d", ifindex); + return NULL; + } + + wext = g_object_new(NM_TYPE_WIFI_UTILS_WEXT, NULL); + + wext->parent.ifindex = ifindex; + wext->fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (wext->fd < 0) + goto error; + + memset(&range, 0, sizeof(struct iw_range)); + if (wext_get_range_ifname(wext, ifname, &range, &response_len) == FALSE) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): driver WEXT range request failed", ifname); + goto error; + } + + if ((response_len < 300) || (range.we_version_compiled < 21)) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver WEXT version too old (got %d, expected >= 21)", + ifname, + range.we_version_compiled); + goto error; + } + + wext->max_qual.qual = range.max_qual.qual; + wext->max_qual.level = range.max_qual.level; + wext->max_qual.noise = range.max_qual.noise; + wext->max_qual.updated = range.max_qual.updated; + + wext->num_freqs = MIN(range.num_frequency, IW_MAX_FREQUENCIES); + for (i = 0; i < wext->num_freqs; i++) { + wext->freqs[i] = iw_freq_to_uint32(&range.freq[i]); + freq_valid = TRUE; + if (wext->freqs[i] > 2400 && wext->freqs[i] < 2500) + has_2ghz = TRUE; + else if (wext->freqs[i] > 4900 && wext->freqs[i] < 6000) + has_5ghz = TRUE; + } + + /* Check for scanning capability; cards that can't scan are not supported */ + if (check_scan && (wext_can_scan_ifname(wext, ifname) == FALSE)) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): drivers that cannot scan are unsupported", ifname); + goto error; + } + + /* Check for the ability to scan specific SSIDs. Until the scan_capa + * field gets added to wireless-tools, need to work around that by casting + * to the custom structure. + */ + scan_capa_range = (struct iw_range_with_scan_capa *) ⦥ + if (scan_capa_range->scan_capa & NM_IW_SCAN_CAPA_ESSID) { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver supports SSID scans (scan_capa 0x%02X).", + ifname, + scan_capa_range->scan_capa); + } else { + _LOGI(LOGD_PLATFORM | LOGD_WIFI, + "(%s): driver does not support SSID scans (scan_capa 0x%02X).", + ifname, + scan_capa_range->scan_capa); + } + + wext->parent.caps = wext_get_caps(wext, ifname, &range); + if (freq_valid) + wext->parent.caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID; + if (has_2ghz) + wext->parent.caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ; + if (has_5ghz) + wext->parent.caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ; + + _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): using WEXT for Wi-Fi device control", ifname); + + return (NMWifiUtils *) wext; + +error: + g_object_unref(wext); + return NULL; +} + +gboolean +nm_wifi_utils_wext_is_wifi(const char *iface) +{ + int fd; + struct iwreq iwr; + gboolean is_wifi = FALSE; + + /* performing an ioctl on a non-existing name may cause the automatic + * loading of kernel modules, which should be avoided. + * + * Usually, we should thus make sure that an interface with this name + * exists. + * + * Note that wifi_wext_is_wifi() has only one caller which just verified + * that an interface with this name exists. + */ + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd >= 0) { + nm_utils_ifname_cpy(iwr.ifr_ifrn.ifrn_name, iface); + if (ioctl(fd, SIOCGIWNAME, &iwr) == 0) + is_wifi = TRUE; + nm_close(fd); + } + return is_wifi; +} diff --git a/src/libnm-platform/wifi/nm-wifi-utils-wext.h b/src/libnm-platform/wifi/nm-wifi-utils-wext.h new file mode 100644 index 00000000..d6f3453c --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils-wext.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 - 2018 Red Hat, Inc. + */ + +#ifndef __WIFI_UTILS_WEXT_H__ +#define __WIFI_UTILS_WEXT_H__ + +#include "nm-wifi-utils.h" + +#define NM_TYPE_WIFI_UTILS_WEXT (nm_wifi_utils_wext_get_type()) +#define NM_WIFI_UTILS_WEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWext)) +#define NM_WIFI_UTILS_WEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass)) +#define NM_IS_WIFI_UTILS_WEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_WEXT)) +#define NM_IS_WIFI_UTILS_WEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_WEXT)) +#define NM_WIFI_UTILS_WEXT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass)) + +GType nm_wifi_utils_wext_get_type(void); + +NMWifiUtils *nm_wifi_utils_wext_new(int ifindex, gboolean check_scan); + +gboolean nm_wifi_utils_wext_is_wifi(const char *iface); + +#endif /* __WIFI_UTILS_WEXT_H__ */ diff --git a/src/libnm-platform/wifi/nm-wifi-utils.c b/src/libnm-platform/wifi/nm-wifi-utils.c new file mode 100644 index 00000000..3c952b6f --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils.c @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-wifi-utils.h" + +#include <sys/stat.h> +#include <stdio.h> +#include <fcntl.h> + +#include "nm-wifi-utils-private.h" +#include "nm-wifi-utils-nl80211.h" +#if HAVE_WEXT + #include "nm-wifi-utils-wext.h" +#endif +#include "libnm-platform/nm-platform-utils.h" + +G_DEFINE_ABSTRACT_TYPE(NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static void +nm_wifi_utils_init(NMWifiUtils *self) +{} + +static void +nm_wifi_utils_class_init(NMWifiUtilsClass *klass) +{} + +NMWifiUtils * +nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan) +{ + NMWifiUtils *ret; + + g_return_val_if_fail(ifindex > 0, NULL); + + ret = nm_wifi_utils_nl80211_new(ifindex, genl); + +#if HAVE_WEXT + if (ret == NULL) + ret = nm_wifi_utils_wext_new(ifindex, check_scan); +#endif + + return ret; +} + +_NMDeviceWifiCapabilities +nm_wifi_utils_get_caps(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, _NM_WIFI_DEVICE_CAP_NONE); + + return data->caps; +} + +_NM80211Mode +nm_wifi_utils_get_mode(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, _NM_802_11_MODE_UNKNOWN); + return NM_WIFI_UTILS_GET_CLASS(data)->get_mode(data); +} + +gboolean +nm_wifi_utils_set_mode(NMWifiUtils *data, const _NM80211Mode mode) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + g_return_val_if_fail((mode == _NM_802_11_MODE_INFRA) || (mode == _NM_802_11_MODE_AP) + || (mode == _NM_802_11_MODE_ADHOC) || (mode == _NM_802_11_MODE_MESH), + FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + + /* nl80211 probably doesn't need this */ + return klass->set_mode ? klass->set_mode(data, mode) : TRUE; +} + +gboolean +nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->set_powersave ? klass->set_powersave(data, powersave) : TRUE; +} + +_NMSettingWirelessWakeOnWLan +nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + + return klass->get_wake_on_wlan ? klass->get_wake_on_wlan(data) + : _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE; +} + +gboolean +nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->set_wake_on_wlan ? klass->set_wake_on_wlan(data, wowl) : FALSE; +} + +guint32 +nm_wifi_utils_get_freq(NMWifiUtils *data) +{ + g_return_val_if_fail(data != NULL, 0); + return NM_WIFI_UTILS_GET_CLASS(data)->get_freq(data); +} + +guint32 +nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs) +{ + g_return_val_if_fail(data != NULL, 0); + g_return_val_if_fail(freqs != NULL, 0); + return NM_WIFI_UTILS_GET_CLASS(data)->find_freq(data, freqs); +} + +gboolean +nm_wifi_utils_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate) +{ + g_return_val_if_fail(data != NULL, FALSE); + + return NM_WIFI_UTILS_GET_CLASS(data)->get_station(data, out_bssid, out_quality, out_rate); +} + +gboolean +nm_wifi_utils_is_wifi(int dirfd, const char *ifname) +{ + g_return_val_if_fail(dirfd >= 0, FALSE); + + if (faccessat(dirfd, "phy80211", F_OK, 0) == 0) + return TRUE; +#if HAVE_WEXT + if (nm_wifi_utils_wext_is_wifi(ifname)) + return TRUE; +#endif + return FALSE; +} + +/* OLPC Mesh-only functions */ + +guint32 +nm_wifi_utils_get_mesh_channel(NMWifiUtils *data) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->get_mesh_channel != NULL, FALSE); + + return klass->get_mesh_channel(data); +} + +gboolean +nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + g_return_val_if_fail(channel <= 13, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->set_mesh_channel != NULL, FALSE); + + return klass->set_mesh_channel(data, channel); +} + +gboolean +nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + g_return_val_if_fail(klass->set_mesh_ssid != NULL, FALSE); + + return klass->set_mesh_ssid(data, ssid, len); +} + +gboolean +nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running) +{ + NMWifiUtilsClass *klass; + + g_return_val_if_fail(data != NULL, FALSE); + + klass = NM_WIFI_UTILS_GET_CLASS(data); + return klass->indicate_addressing_running ? klass->indicate_addressing_running(data, running) + : FALSE; +} diff --git a/src/libnm-platform/wifi/nm-wifi-utils.h b/src/libnm-platform/wifi/nm-wifi-utils.h new file mode 100644 index 00000000..157522ee --- /dev/null +++ b/src/libnm-platform/wifi/nm-wifi-utils.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2018 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#ifndef __WIFI_UTILS_H__ +#define __WIFI_UTILS_H__ + +#include <net/ethernet.h> + +#include "libnm-platform/nm-netlink.h" +#include "libnm-base/nm-base.h" + +typedef struct NMWifiUtils NMWifiUtils; + +#define NM_TYPE_WIFI_UTILS (nm_wifi_utils_get_type()) +#define NM_WIFI_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS, NMWifiUtils)) +#define NM_WIFI_UTILS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass)) +#define NM_IS_WIFI_UTILS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS)) +#define NM_IS_WIFI_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS)) +#define NM_WIFI_UTILS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass)) + +GType nm_wifi_utils_get_type(void); + +gboolean nm_wifi_utils_is_wifi(int dirfd, const char *ifname); + +NMWifiUtils *nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan); + +_NMDeviceWifiCapabilities nm_wifi_utils_get_caps(NMWifiUtils *data); + +_NM80211Mode nm_wifi_utils_get_mode(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_mode(NMWifiUtils *data, const _NM80211Mode mode); + +/* Returns frequency in MHz */ +guint32 nm_wifi_utils_get_freq(NMWifiUtils *data); + +/* Return the first supported frequency in the zero-terminated list. + * Frequencies are specified in MHz. */ +guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs); + +/* + * @out_bssid: must be NULL or an ETH_ALEN-byte buffer + * @out_quality: receives signal quality in 0 - 100% range if not NULL + * @out_rate: receives current bitrate in Kbps if not NULL + * + * Returns %TRUE on succcess. + */ +gboolean nm_wifi_utils_get_station(NMWifiUtils *data, + NMEtherAddr *out_bssid, + int * out_quality, + guint32 * out_rate); + +/* Tells the driver DHCP or SLAAC is running */ +gboolean nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running); + +gboolean nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave); + +_NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl); + +/* OLPC Mesh-only functions */ +guint32 nm_wifi_utils_get_mesh_channel(NMWifiUtils *data); + +gboolean nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel); + +gboolean nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len); + +#endif /* __WIFI_UTILS_H__ */ diff --git a/src/libnm-platform/wpan/nm-wpan-utils.c b/src/libnm-platform/wpan/nm-wpan-utils.c new file mode 100644 index 00000000..082cc3e7 --- /dev/null +++ b/src/libnm-platform/wpan/nm-wpan-utils.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-wpan-utils.h" + +#include "libnm-std-aux/nm-linux-compat.h" + +#include <linux/if.h> + +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-netlink.h" +#include "libnm-platform/nm-platform-utils.h" + +#define _NMLOG_PREFIX_NAME "wpan-nl802154" +#define _NMLOG(level, domain, ...) \ + G_STMT_START \ + { \ + char _ifname_buf[IFNAMSIZ]; \ + const char *_ifname = self ? nmp_utils_if_indextoname(self->ifindex, _ifname_buf) : NULL; \ + \ + nm_log((level), \ + (domain), \ + _ifname ?: NULL, \ + NULL, \ + "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +struct NMWpanUtils { + GObject parent; + int ifindex; + struct nl_sock *nl_sock; + int id; +}; + +typedef struct { + GObjectClass parent; +} NMWpanUtilsClass; + +G_DEFINE_TYPE(NMWpanUtils, nm_wpan_utils, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static int +ack_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_STOP; +} + +static int +finish_handler(struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_SKIP; +} + +static int +error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *done = arg; + *done = err->error; + return NL_SKIP; +} + +static struct nl_msg * +_nl802154_alloc_msg(int id, int ifindex, guint32 cmd, guint32 flags) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc(); + genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32(msg, NL802154_ATTR_IFINDEX, ifindex); + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + +static struct nl_msg * +nl802154_alloc_msg(NMWpanUtils *self, guint32 cmd, guint32 flags) +{ + return _nl802154_alloc_msg(self->id, self->ifindex, cmd, flags); +} + +static int +nl802154_send_and_recv(NMWpanUtils * self, + struct nl_msg *msg, + int (*valid_handler)(struct nl_msg *, void *), + void *valid_data) +{ + int err; + int done = 0; + const struct nl_cb cb = { + .err_cb = error_handler, + .err_arg = &done, + .finish_cb = finish_handler, + .finish_arg = &done, + .ack_cb = ack_handler, + .ack_arg = &done, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg != NULL, -ENOMEM); + + err = nl_send_auto(self->nl_sock, msg); + if (err < 0) + return err; + + /* Loop until one of our NL callbacks says we're done; on success + * done will be 1, on error it will be < 0. + */ + while (!done) { + err = nl_recvmsgs(self->nl_sock, &cb); + if (err < 0 && err != -EAGAIN) { + _LOGW(LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s", err, nm_strerror(err)); + break; + } + } + + if (err >= 0 && done < 0) + err = done; + return err; +} + +struct nl802154_interface { + guint16 pan_id; + guint16 short_addr; + + gboolean valid; +}; + +static int +nl802154_get_interface_handler(struct nl_msg *msg, void *arg) +{ + static const struct nla_policy nl802154_policy[] = { + [NL802154_ATTR_PAN_ID] = {.type = NLA_U16}, + [NL802154_ATTR_SHORT_ADDR] = {.type = NLA_U16}, + }; + struct nlattr * tb[G_N_ELEMENTS(nl802154_policy)]; + struct nl802154_interface *info = arg; + struct genlmsghdr * gnlh = nlmsg_data(nlmsg_hdr(msg)); + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nl802154_policy) < 0) + return NL_SKIP; + + if (tb[NL802154_ATTR_PAN_ID]) + info->pan_id = le16toh(nla_get_u16(tb[NL802154_ATTR_PAN_ID])); + + if (tb[NL802154_ATTR_SHORT_ADDR]) + info->short_addr = le16toh(nla_get_u16(tb[NL802154_ATTR_SHORT_ADDR])); + + info->valid = TRUE; + + return NL_SKIP; +} + +static void +nl802154_get_interface(NMWpanUtils *self, struct nl802154_interface *interface) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + + memset(interface, 0, sizeof(*interface)); + + msg = nl802154_alloc_msg(self, NL802154_CMD_GET_INTERFACE, 0); + + nl802154_send_and_recv(self, msg, nl802154_get_interface_handler, interface); +} + +/*****************************************************************************/ + +guint16 +nm_wpan_utils_get_pan_id(NMWpanUtils *self) +{ + struct nl802154_interface interface; + + nl802154_get_interface(self, &interface); + + return interface.pan_id; +} + +gboolean +nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_PAN_ID, 0); + NLA_PUT_U16(msg, NL802154_ATTR_PAN_ID, htole16(pan_id)); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +guint16 +nm_wpan_utils_get_short_addr(NMWpanUtils *self) +{ + struct nl802154_interface interface; + + nl802154_get_interface(self, &interface); + + return interface.short_addr; +} + +gboolean +nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_SHORT_ADDR, 0); + NLA_PUT_U16(msg, NL802154_ATTR_SHORT_ADDR, htole16(short_addr)); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +gboolean +nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int err; + + g_return_val_if_fail(self != NULL, FALSE); + + msg = nl802154_alloc_msg(self, NL802154_CMD_SET_CHANNEL, 0); + NLA_PUT_U8(msg, NL802154_ATTR_PAGE, page); + NLA_PUT_U8(msg, NL802154_ATTR_CHANNEL, channel); + err = nl802154_send_and_recv(self, msg, NULL, NULL); + return err >= 0; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +/*****************************************************************************/ + +static void +nm_wpan_utils_init(NMWpanUtils *self) +{} + +static void +nm_wpan_utils_class_init(NMWpanUtilsClass *klass) +{} + +NMWpanUtils * +nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan) +{ + NMWpanUtils *self; + + g_return_val_if_fail(ifindex > 0, NULL); + + if (!genl) + return NULL; + + self = g_object_new(NM_TYPE_WPAN_UTILS, NULL); + self->ifindex = ifindex; + self->nl_sock = genl; + self->id = genl_ctrl_resolve(genl, "nl802154"); + + if (self->id < 0) { + _LOGD(LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\""); + g_object_unref(self); + return NULL; + } + + return self; +} diff --git a/src/libnm-platform/wpan/nm-wpan-utils.h b/src/libnm-platform/wpan/nm-wpan-utils.h new file mode 100644 index 00000000..6130c41e --- /dev/null +++ b/src/libnm-platform/wpan/nm-wpan-utils.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __WPAN_UTILS_H__ +#define __WPAN_UTILS_H__ + +#include <net/ethernet.h> + +#include "libnm-platform/nm-netlink.h" + +typedef struct NMWpanUtils NMWpanUtils; + +#define NM_TYPE_WPAN_UTILS (nm_wpan_utils_get_type()) +#define NM_WPAN_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WPAN_UTILS, NMWpanUtils)) +#define NM_WPAN_UTILS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass)) +#define NM_IS_WPAN_UTILS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WPAN_UTILS)) +#define NM_IS_WPAN_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WPAN_UTILS)) +#define NM_WPAN_UTILS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass)) + +GType nm_wpan_utils_get_type(void); + +NMWpanUtils *nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan); + +guint16 nm_wpan_utils_get_pan_id(NMWpanUtils *self); +gboolean nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id); + +guint16 nm_wpan_utils_get_short_addr(NMWpanUtils *self); +gboolean nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr); + +gboolean nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel); + +#endif /* __WPAN_UTILS_H__ */ |