diff options
| author | Michael Biebl <biebl@debian.org> | 2016-01-20 16:26:51 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2016-01-20 16:26:51 +0100 |
| commit | 494f296a3baab08522617b24b1f126d8f9a17502 (patch) | |
| tree | c8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/platform/nm-linux-platform.c | |
| parent | 54f6333410ffd570e62717d9e77c5c987175e397 (diff) | |
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/platform/nm-linux-platform.c')
| -rw-r--r-- | src/platform/nm-linux-platform.c | 6473 |
1 files changed, 3766 insertions, 2707 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index ee6ba421..f73c228b 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -22,8 +22,10 @@ #include <errno.h> #include <unistd.h> #include <sys/socket.h> +#include <sys/ioctl.h> #include <fcntl.h> #include <dlfcn.h> +#include <arpa/inet.h> #include <netinet/icmp6.h> #include <netinet/in.h> #include <linux/ip.h> @@ -40,24 +42,13 @@ #include <netlink/route/route.h> #include <gudev/gudev.h> -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE || HAVE_LIBNL_INET6_TOKEN -#include <netlink/route/link/inet6.h> -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE && HAVE_KERNEL_INET6_ADDR_GEN_MODE -#include <linux/if_link.h> -#else -#define IN6_ADDR_GEN_MODE_EUI64 0 -#define IN6_ADDR_GEN_MODE_NONE 1 -#endif -#endif - -#include "gsystem-local-alloc.h" #include "nm-core-internal.h" #include "NetworkManagerUtils.h" #include "nm-linux-platform.h" #include "nm-platform-utils.h" #include "NetworkManagerUtils.h" #include "nm-utils.h" -#include "nm-logging.h" +#include "nm-default.h" #include "wifi/wifi-utils.h" #include "wifi/wifi-utils-wext.h" #include "nmp-object.h" @@ -65,11 +56,67 @@ /* This is only included for the translation of VLAN flags */ #include "nm-setting-vlan.h" +#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 + +#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 _NMLOG_DOMAIN LOGD_PLATFORM #define _NMLOG_PREFIX_NAME "platform-linux" -#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG2_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) +#define _NMLOG2(level, ...) _LOG(level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__) #define _LOG(level, domain, self, ...) \ G_STMT_START { \ @@ -91,11 +138,7 @@ } \ } G_STMT_END -#define trace(...) _LOG (LOGL_TRACE, _NMLOG_DOMAIN, NULL, __VA_ARGS__) -#define debug(...) _LOG (LOGL_DEBUG, _NMLOG_DOMAIN, NULL, __VA_ARGS__) -#define info(...) _LOG (LOGL_INFO, _NMLOG_DOMAIN, NULL, __VA_ARGS__) -#define warning(...) _LOG (LOGL_WARN , _NMLOG_DOMAIN, NULL, __VA_ARGS__) -#define error(...) _LOG (LOGL_ERR , _NMLOG_DOMAIN, NULL, __VA_ARGS__) +#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s" /****************************************************************** * Forward declarations and enums @@ -111,6 +154,7 @@ typedef enum { DELAYED_ACTION_TYPE_REFRESH_LINK = (1LL << 5), DELAYED_ACTION_TYPE_MASTER_CONNECTED = (1LL << 6), DELAYED_ACTION_TYPE_READ_NETLINK = (1LL << 7), + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE = (1LL << 8), __DELAYED_ACTION_TYPE_MAX, DELAYED_ACTION_TYPE_REFRESH_ALL = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | @@ -122,494 +166,108 @@ typedef enum { DELAYED_ACTION_TYPE_MAX = __DELAYED_ACTION_TYPE_MAX -1, } DelayedActionType; -static gboolean tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props); +typedef enum { + /* Negative values are errors from kernel. Add dummy member to + * make enum signed. */ + _WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = -1, + + 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, +} WaitForNlResponseResult; + +typedef void (*WaitForNlResponseCallback) (NMPlatform *platform, + guint32 seq_number, + WaitForNlResponseResult seq_result, + gpointer user_data); + 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 (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action); -static void do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action); +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_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data); -static gboolean event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks); -static NMPCacheOpsType cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason); - -/****************************************************************** - * libnl unility functions and wrappers - ******************************************************************/ - -struct libnl_vtable -{ - void *handle; - void *handle_route; - - int (*f_nl_has_capability) (int capability); - int (*f_rtnl_link_get_link_netnsid) (const struct rtnl_link *link, gint32 *out_link_netnsid); -}; - -static int -_nl_f_nl_has_capability (int capability) -{ - return FALSE; -} - -static const struct libnl_vtable * -_nl_get_vtable (void) -{ - static struct libnl_vtable vtable; - - if (G_UNLIKELY (!vtable.f_nl_has_capability)) { - vtable.handle = dlopen ("libnl-3.so.200", RTLD_LAZY | RTLD_NOLOAD); - if (vtable.handle) { - vtable.f_nl_has_capability = dlsym (vtable.handle, "nl_has_capability"); - } - vtable.handle_route = dlopen ("libnl-route-3.so.200", RTLD_LAZY | RTLD_NOLOAD); - if (vtable.handle_route) { - vtable.f_rtnl_link_get_link_netnsid = dlsym (vtable.handle_route, "rtnl_link_get_link_netnsid"); - } - - if (!vtable.f_nl_has_capability) - vtable.f_nl_has_capability = &_nl_f_nl_has_capability; - - trace ("libnl: rtnl_link_get_link_netnsid() %s", vtable.f_rtnl_link_get_link_netnsid ? "supported" : "not supported"); - - g_return_val_if_fail (vtable.handle, &vtable); - g_return_val_if_fail (vtable.handle_route, &vtable); - } - - return &vtable; -} - -static gboolean -_nl_has_capability (int capability) -{ - return (_nl_get_vtable ()->f_nl_has_capability) (capability); -} - -static int -_rtnl_link_get_link_netnsid (const struct rtnl_link *link, gint32 *out_link_netnsid) -{ - const struct libnl_vtable *vtable; - - g_return_val_if_fail (link, -NLE_INVAL); - g_return_val_if_fail (out_link_netnsid, -NLE_INVAL); - - vtable = _nl_get_vtable (); - return vtable->f_rtnl_link_get_link_netnsid - ? vtable->f_rtnl_link_get_link_netnsid (link, out_link_netnsid) - : -NLE_OPNOTSUPP; -} - -gboolean -nm_platform_check_support_libnl_link_netnsid (void) -{ - return !!(_nl_get_vtable ()->f_rtnl_link_get_link_netnsid); -} - -/* Automatic deallocation of local variables */ -#define auto_nl_object __attribute__((cleanup(_nl_auto_nl_object))) -static void -_nl_auto_nl_object (void *ptr) -{ - struct nl_object **object = ptr; - - if (object && *object) { - nl_object_put (*object); - *object = NULL; - } -} - -#define auto_nl_addr __attribute__((cleanup(_nl_auto_nl_addr))) -static void -_nl_auto_nl_addr (void *ptr) -{ - struct nl_addr **object = ptr; - - if (object && *object) { - nl_addr_put (*object); - *object = NULL; - } -} - -/* wrap the libnl alloc functions and abort on out-of-memory*/ - -static struct nl_addr * -_nl_addr_build (int family, const void *buf, size_t size) -{ - struct nl_addr *addr; - - addr = nl_addr_build (family, (void *) buf, size); - if (!addr) - g_error ("nl_addr_build() failed with out of memory"); - - return addr; -} +static void cache_prune_candidates_prune (NMPlatform *platform); +static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks); -static struct rtnl_link * -_nl_rtnl_link_alloc (int ifindex, const char*name) -{ - struct rtnl_link *rtnllink; - - rtnllink = rtnl_link_alloc (); - if (!rtnllink) - g_error ("rtnl_link_alloc() failed with out of memory"); - - if (ifindex > 0) - rtnl_link_set_ifindex (rtnllink, ifindex); - if (name) - rtnl_link_set_name (rtnllink, name); - return rtnllink; -} - -static struct rtnl_addr * -_nl_rtnl_addr_alloc (int ifindex) -{ - struct rtnl_addr *rtnladdr; - - rtnladdr = rtnl_addr_alloc (); - if (!rtnladdr) - g_error ("rtnl_addr_alloc() failed with out of memory"); - if (ifindex > 0) - rtnl_addr_set_ifindex (rtnladdr, ifindex); - return rtnladdr; -} - -static struct rtnl_route * -_nl_rtnl_route_alloc (void) -{ - struct rtnl_route *rtnlroute = rtnl_route_alloc (); - - if (!rtnlroute) - g_error ("rtnl_route_alloc() failed with out of memory"); - return rtnlroute; -} - -static struct rtnl_nexthop * -_nl_rtnl_route_nh_alloc (void) -{ - struct rtnl_nexthop *nexthop; - - nexthop = rtnl_route_nh_alloc (); - if (!nexthop) - g_error ("rtnl_route_nh_alloc () failed with out of memory"); - return nexthop; -} - -/* rtnl_addr_set_prefixlen fails to update the nl_addr prefixlen */ -static void -_nl_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen) -{ - struct nl_addr *nladdr; - - rtnl_addr_set_prefixlen (rtnladdr, plen); - - nladdr = rtnl_addr_get_local (rtnladdr); - if (nladdr) - nl_addr_set_prefixlen (nladdr, plen); -} +/*****************************************************************************/ static const char * -_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len) +wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size) { - const char *str_type = NULL; + char *buf0 = buf; - switch (type) { - case RTM_NEWLINK: str_type = "NEWLINK"; break; - case RTM_DELLINK: str_type = "DELLINK"; break; - case RTM_NEWADDR: str_type = "NEWADDR"; break; - case RTM_DELADDR: str_type = "DELADDR"; break; - case RTM_NEWROUTE: str_type = "NEWROUTE"; break; - case RTM_DELROUTE: str_type = "DELROUTE"; break; + 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)", -((int) seq_result), g_strerror (-((int) seq_result))); + else + nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result); + break; } - if (str_type) - g_strlcpy (buf, str_type, len); - else - g_snprintf (buf, len, "(%d)", type); - return buf; + return buf0; } -/******************************************************************/ - -/* _nl_link_parse_info_data(): Re-fetches a link from the kernel - * and parses its IFLA_INFO_DATA using a caller-provided parser. - * - * Code is stolen from rtnl_link_get_kernel(), nl_pickup(), and link_msg_parser(). - */ - -typedef int (*NMNLInfoDataParser) (struct nlattr *info_data, gpointer parser_data); - -typedef struct { - NMNLInfoDataParser parser; - gpointer parser_data; -} NMNLInfoDataClosure; - -static struct nla_policy info_data_link_policy[IFLA_MAX + 1] = { - [IFLA_LINKINFO] = { .type = NLA_NESTED }, -}; - -static struct nla_policy info_data_link_info_policy[IFLA_INFO_MAX + 1] = { - [IFLA_INFO_DATA] = { .type = NLA_NESTED }, -}; - -static int -_nl_link_parse_info_data_cb (struct nl_msg *msg, void *arg) -{ - NMNLInfoDataClosure *closure = arg; - struct nlmsghdr *n = nlmsg_hdr (msg); - struct nlattr *tb[IFLA_MAX + 1]; - struct nlattr *li[IFLA_INFO_MAX + 1]; - int err; - - if (!nlmsg_valid_hdr (n, sizeof (struct ifinfomsg))) - return -NLE_MSG_TOOSHORT; - - err = nlmsg_parse (n, sizeof (struct ifinfomsg), tb, IFLA_MAX, info_data_link_policy); - if (err < 0) - return err; - - if (!tb[IFLA_LINKINFO]) - return -NLE_MISSING_ATTR; - - err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], info_data_link_info_policy); - if (err < 0) - return err; - - if (!li[IFLA_INFO_DATA]) - return -NLE_MISSING_ATTR; - - return closure->parser (li[IFLA_INFO_DATA], closure->parser_data); -} - -static int -_nl_link_parse_info_data (struct nl_sock *sk, int ifindex, - NMNLInfoDataParser parser, gpointer parser_data) -{ - NMNLInfoDataClosure data = { .parser = parser, .parser_data = parser_data }; - struct nl_msg *msg = NULL; - struct nl_cb *cb; - struct nl_cb *cb0; - int err; - - err = rtnl_link_build_get_request (ifindex, NULL, &msg); - if (err < 0) - return err; - - err = nl_send_auto (sk, msg); - nlmsg_free (msg); - if (err < 0) - return err; - - cb0 = nl_socket_get_cb (sk); - cb = nl_cb_clone (cb0); - nl_cb_put (cb0); - if (cb == NULL) - return -NLE_NOMEM; - nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, _nl_link_parse_info_data_cb, &data); - - err = nl_recvmsgs (sk, cb); - nl_cb_put (cb); - if (err < 0) - return err; - - nl_wait_for_ack (sk); - return 0; -} - -/******************************************************************/ - -static int -_nl_sock_flush_data (struct nl_sock *sk) -{ - int nle; - struct nl_cb *cb; - struct nl_cb *cb0; - - cb0 = nl_socket_get_cb (sk); - cb = nl_cb_clone (cb0); - nl_cb_put (cb0); - if (cb == NULL) - return -NLE_NOMEM; - - nl_cb_set (cb, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL); - nl_cb_set (cb, NL_CB_SEQ_CHECK, NL_CB_DEFAULT, NULL, NULL); - nl_cb_err (cb, NL_CB_DEFAULT, NULL, NULL); - do { - errno = 0; - - nle = nl_recvmsgs (sk, cb); - - /* Work around a libnl bug fixed in 3.2.22 (375a6294) */ - if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) - nle = -NLE_AGAIN; - } while (nle != -NLE_AGAIN); - - nl_cb_put (cb); - return nle; -} - -static void -_nl_msg_set_seq (struct nl_sock *sk, struct nl_msg *msg, guint32 *out_seq) -{ - guint32 seq; - - /* choose our own sequence number, because libnl does not ensure that - * it isn't zero -- which would confuse our checking for outstanding - * messages. */ - seq = nl_socket_use_seq (sk); - if (seq == 0) - seq = nl_socket_use_seq (sk); - - nlmsg_hdr (msg)->nlmsg_seq = seq; - if (out_seq) - *out_seq = seq; -} - -static int -_nl_sock_request_link (NMPlatform *platform, struct nl_sock *sk, int ifindex, const char *name, guint32 *out_seq) -{ - struct nl_msg *msg = NULL; - int err; - - if (name && !name[0]) - name = NULL; - - g_return_val_if_fail (ifindex > 0 || name, -NLE_INVAL); - - _LOGt ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : ""); - - if ((err = rtnl_link_build_get_request (ifindex, name, &msg)) < 0) - return err; - - _nl_msg_set_seq (sk, msg, out_seq); - - err = nl_send_auto (sk, msg); - nlmsg_free(msg); - if (err < 0) - return err; - - return 0; -} - -static int -_nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType obj_type, guint32 *out_seq) -{ - const NMPClass *klass; - struct rtgenmsg gmsg = { 0 }; - struct nl_msg *msg; - int err; - - klass = nmp_class_from_type (obj_type); - - _LOGt ("sock: request-all-%s", klass->obj_type_name); - - /* reimplement - * nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP); - * because we need the sequence number. - */ - msg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP); - if (!msg) - return -NLE_NOMEM; - - gmsg.rtgen_family = klass->addr_family; - err = nlmsg_append (msg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO); - if (err < 0) - goto errout; - - _nl_msg_set_seq (sk, msg, out_seq); - - err = nl_send_auto (sk, msg); -errout: - nlmsg_free(msg); - - return err >= 0 ? 0 : err; -} - -/******************************************************************/ +/****************************************************************** + * Support IFLA_INET6_ADDR_GEN_MODE + ******************************************************************/ -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE static int _support_user_ipv6ll = 0; #define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0)) -#else -#define _support_user_ipv6ll_still_undecided() (FALSE) -#endif static gboolean _support_user_ipv6ll_get (void) { -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE if (_support_user_ipv6ll_still_undecided ()) { _support_user_ipv6ll = -1; - nm_log_warn (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support"); - } else - return _support_user_ipv6ll > 0; -#endif + _LOG2W ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support"); + return FALSE; + } + return _support_user_ipv6ll > 0; - return FALSE; } static void -_support_user_ipv6ll_detect (const struct rtnl_link *rtnl_link) +_support_user_ipv6ll_detect (struct nlattr **tb) { -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE - /* If we ever see a link with valid IPv6 link-local address - * generation modes, the kernel supports it. - */ if (_support_user_ipv6ll_still_undecided ()) { - uint8_t mode; - - if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) rtnl_link, &mode) == 0) { + if (tb[IFLA_INET6_ADDR_GEN_MODE]) { _support_user_ipv6ll = 1; - nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected"); + _LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected"); } else { _support_user_ipv6ll = -1; - nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected"); + _LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected"); } } -#endif } -/******************************************************************/ - -static int _support_kernel_extended_ifa_flags = 0; - -#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0)) - -static void -_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg) -{ - struct nlmsghdr *msg_hdr; +/****************************************************************** + * Various utilities + ******************************************************************/ - if (!_support_kernel_extended_ifa_flags_still_undecided ()) - return; +const NMIPAddr nm_ip_addr_zero = NMIPAddrInit; - msg_hdr = nlmsg_hdr (msg); - if (msg_hdr->nlmsg_type != RTM_NEWADDR) - return; - - /* the extended address flags are only set for AF_INET6 */ - if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6) - return; - - /* 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 (they were added together). - **/ - _support_kernel_extended_ifa_flags = - nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */) - ? 1 : -1; -} +#define IPV4LL_NETWORK (htonl (0xA9FE0000L)) +#define IPV4LL_NETMASK (htonl (0xFFFF0000L)) static gboolean -_support_kernel_extended_ifa_flags_get (void) +ip4_address_is_link_local (in_addr_t addr) { - if (_support_kernel_extended_ifa_flags_still_undecided ()) { - nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support."); - _support_kernel_extended_ifa_flags = -1; - } - return _support_kernel_extended_ifa_flags > 0; + return (addr & IPV4LL_NETMASK) == IPV4LL_NETWORK; } -/****************************************************************** - * Object type specific utilities - ******************************************************************/ - static guint _nm_ip_config_source_to_rtprot (NMIPConfigSource source) { @@ -649,7 +307,46 @@ _nm_ip_config_source_from_rtprot (guint rtprot) } } -/******************************************************************/ +static void +clear_host_address (int family, const void *network, int plen, void *dst) +{ + g_return_if_fail (plen == (guint8)plen); + g_return_if_fail (network); + + switch (family) { + case AF_INET: + *((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen); + break; + case AF_INET6: + nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen); + break; + default: + g_assert_not_reached (); + } +} + +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 NMLinkType nm_type; @@ -687,10 +384,13 @@ static const LinkDesc linktypes[] = { { 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_IPIP, "ipip", "ipip", NULL }, { NM_LINK_TYPE_LOOPBACK, "loopback", NULL, 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_SIT, "sit", "sit", NULL }, { NM_LINK_TYPE_TAP, "tap", NULL, NULL }, { NM_LINK_TYPE_TUN, "tun", NULL, NULL }, { NM_LINK_TYPE_VETH, "veth", "veth", NULL }, @@ -728,125 +428,172 @@ nm_link_type_to_string (NMLinkType type) } /****************************************************************** - * NMPlatform types and functions + * Utilities ******************************************************************/ -typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate; - -struct _NMLinuxPlatformPrivate { - struct nl_sock *nlh; - struct nl_sock *nlh_event; - guint32 nlh_seq_expect; - guint32 nlh_seq_last; - NMPCache *cache; - GIOChannel *event_channel; - guint event_id; - - GUdevClient *udev_client; - - struct { - DelayedActionType flags; - GPtrArray *list_master_connected; - GPtrArray *list_refresh_link; - gint is_handling; - guint idle_id; - } delayed_action; - - GHashTable *prune_candidates; +/* _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; - GHashTable *wifi_data; -}; + /* convert timestamp from 1/100th of a second to msec. */ + timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100); -static inline NMLinuxPlatformPrivate * -NM_LINUX_PLATFORM_GET_PRIVATE (const void *self) -{ - nm_assert (NM_IS_LINUX_PLATFORM (self)); + /* 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 ((NMLinuxPlatform *) self)->priv; + return timestamp_nl_ms; } -G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM) - -void -nm_linux_platform_setup (void) +static guint32 +_addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm) { - g_object_new (NM_TYPE_LINUX_PLATFORM, - NM_PLATFORM_REGISTER_SINGLETON, TRUE, - NULL); -} + struct timespec tp; + gint64 now_nl, now_nm, result; + int err; -/******************************************************************/ + /* timestamp is unset. Default to 1. */ + if (!timestamp) { + if (out_now_nm) + *out_now_nm = 0; + return 1; + } -NMPObjectType -_nlo_get_object_type (const struct nl_object *object) -{ - const char *type_str; + /* do all the calculations in milliseconds scale */ - if (!object || !(type_str = nl_object_get_type (object))) - return NMP_OBJECT_TYPE_UNKNOWN; + err = clock_gettime (CLOCK_MONOTONIC, &tp); + g_assert (err == 0); + now_nm = nm_utils_get_monotonic_timestamp_ms (); + now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) + + (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); - if (!strcmp (type_str, "route/link")) - return NMP_OBJECT_TYPE_LINK; - else if (!strcmp (type_str, "route/addr")) { - switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) { - case AF_INET: - return NMP_OBJECT_TYPE_IP4_ADDRESS; - case AF_INET6: - return NMP_OBJECT_TYPE_IP6_ADDRESS; - default: - return NMP_OBJECT_TYPE_UNKNOWN; - } - } else if (!strcmp (type_str, "route/route")) { - switch (rtnl_route_get_family ((struct rtnl_route *) object)) { - case AF_INET: - return NMP_OBJECT_TYPE_IP4_ROUTE; - case AF_INET6: - return NMP_OBJECT_TYPE_IP6_ROUTE; - default: - return NMP_OBJECT_TYPE_UNKNOWN; - } - } else - return NMP_OBJECT_TYPE_UNKNOWN; -} + result = now_nm - (now_nl - _timestamp_nl_to_ms (timestamp, now_nl)); -/******************************************************************/ + if (out_now_nm) + *out_now_nm = now_nm / 1000; -static gboolean -check_support_kernel_extended_ifa_flags (NMPlatform *platform) -{ - g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE); + /* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() 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_ms() scale. + */ + if (result <= 1000) + return 1; - return _support_kernel_extended_ifa_flags_get (); + if (result > now_nm) + return now_nm / 1000; + + return result / 1000; } -static gboolean -check_support_user_ipv6ll (NMPlatform *platform) +static guint32 +_addrtime_extend_lifetime (guint32 lifetime, guint32 seconds) { - g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE); + guint64 v; - return _support_user_ipv6ll_get (); + 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 -process_events (NMPlatform *platform) +_addrtime_get_lifetimes (guint32 timestamp, + guint32 lifetime, + guint32 preferred, + guint32 *out_timestamp, + guint32 *out_lifetime, + guint32 *out_preferred) { - delayed_action_handle_all (platform, TRUE); + 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_s (); + } + 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; } /******************************************************************/ -#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \ - ((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \ - nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \ - NULL)) +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; + + if (obj && !obj->_link.netlink.is_in_netlink) + *link_cached = obj; + else + *link_cached = NULL; + *completed_from_cache = TRUE; + } + return *link_cached; +} /******************************************************************/ #define DEVTYPE_PREFIX "DEVTYPE=" static char * -read_devtype (const char *sysfs_path) +_linktype_read_devtype (const char *sysfs_path) { gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path); char *contents = NULL; @@ -868,69 +615,63 @@ read_devtype (const char *sysfs_path) return NULL; } -static const NMPObject * -_lookup_link_cached (NMPlatform *platform, 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 ? nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex) : NULL; - - if (obj && !obj->_link.netlink.is_in_netlink) - *link_cached = obj; - else - *link_cached = NULL; - *completed_from_cache = TRUE; - } - return *link_cached; -} - static NMLinkType -link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *completed_from_cache, const NMPObject **link_cached, const char **out_kind) -{ - const char *rtnl_type, *ifname; - int i, arptype; - - if (!rtnllink) { - if (out_kind) - *out_kind = NULL; - return NM_LINK_TYPE_NONE; - } - - rtnl_type = rtnl_link_get_type (rtnllink); - if (!rtnl_type && completed_from_cache) { +_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) +{ + guint i; + + if (completed_from_cache) { const NMPObject *obj; - obj = _lookup_link_cached (platform, rtnl_link_get_ifindex (rtnllink), completed_from_cache, link_cached); - if (obj && obj->link.kind) { - rtnl_type = obj->link.kind; - _LOGt ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type); + 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" changed. + * + * This way, we save edditional 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 interfce to other netns). Thus here there is a tiny potential + * of messing stuff up. */ + if ( obj + && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE) + && ( !kind + || !g_strcmp0 (kind, obj->link.kind))) { + nm_assert (obj->link.kind == g_intern_string (obj->link.kind)); + *out_kind = obj->link.kind; + return obj->link.type; } } - if (out_kind) - *out_kind = rtnl_type; - if (rtnl_type) { + + *out_kind = g_intern_string (kind); + + if (kind) { for (i = 0; i < G_N_ELEMENTS (linktypes); i++) { - if (g_strcmp0 (rtnl_type, linktypes[i].rtnl_type) == 0) + if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0) return linktypes[i].nm_type; } - if (!strcmp (rtnl_type, "tun")) { + if (!strcmp (kind, "tun")) { NMPlatformTunProperties props; - guint flags; - if (tun_get_properties_ifname (platform, rtnl_link_get_name (rtnllink), &props)) { + if ( platform + && nm_platform_link_tun_get_properties_ifname (platform, ifname, &props)) { if (!g_strcmp0 (props.mode, "tap")) return NM_LINK_TYPE_TAP; if (!g_strcmp0 (props.mode, "tun")) return NM_LINK_TYPE_TUN; } - flags = rtnl_link_get_flags (rtnllink); - - nm_log_dbg (LOGD_PLATFORM, "Failed to read tun properties for interface %d (link flags: %X)", - rtnl_link_get_ifindex (rtnllink), flags); /* try guessing the type using the link flags instead... */ if (flags & IFF_POINTOPOINT) @@ -939,13 +680,15 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c } } - arptype = rtnl_link_get_arptype (rtnllink); 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; - ifname = rtnl_link_get_name (rtnllink); if (ifname) { gs_free char *driver = NULL; gs_free char *sysfs_path = NULL; @@ -971,7 +714,7 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS)) return NM_LINK_TYPE_OLPC_MESH; - devtype = read_devtype (sysfs_path); + devtype = _linktype_read_devtype (sysfs_path); for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) { if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) { if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) { @@ -994,366 +737,1138 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c * prevent future virtual network drivers from being treated as Ethernet * when they should be Generic instead. */ - if (arptype == ARPHRD_ETHER && !rtnl_type && !devtype) + if (arptype == ARPHRD_ETHER && !kind && !devtype) return NM_LINK_TYPE_ETHERNET; } return NM_LINK_TYPE_UNKNOWN; } -gboolean -_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +/****************************************************************** + * libnl unility functions and wrappers + ******************************************************************/ + +#define nm_auto_nlmsg __attribute__((cleanup(_nm_auto_nl_msg_cleanup))) +static void +_nm_auto_nl_msg_cleanup (void *ptr) { - NMPlatformLink *obj = (NMPlatformLink *) _obj; - NMPObjectLink *obj_priv = (NMPObjectLink *) _obj; - struct rtnl_link *nlo = (struct rtnl_link *) _nlo; - const char *name; - struct nl_addr *nladdr; - const char *kind; - gboolean completed_from_cache_val = FALSE; - gboolean *completed_from_cache = complete_from_cache ? &completed_from_cache_val : NULL; - const NMPObject *link_cached = NULL; - int parent; + nlmsg_free (*((struct nl_msg **) ptr)); +} - nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0); +static const char * +_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len) +{ + const char *str_type = NULL; - if (_LOGt_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE)) - _LOGt ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo)); + switch (type) { + case RTM_NEWLINK: str_type = "NEWLINK"; break; + case RTM_DELLINK: str_type = "DELLINK"; break; + case RTM_NEWADDR: str_type = "NEWADDR"; break; + case RTM_DELADDR: str_type = "DELADDR"; break; + case RTM_NEWROUTE: str_type = "NEWROUTE"; break; + case RTM_DELROUTE: str_type = "DELROUTE"; break; + } + if (str_type) + g_strlcpy (buf, str_type, len); + else + g_snprintf (buf, len, "(%d)", type); + return buf; +} - obj->ifindex = rtnl_link_get_ifindex (nlo); +/****************************************************************** + * NMPObject/netlink functions + ******************************************************************/ - if (id_only) - return TRUE; +#define _check_addr_or_errout(tb, attr, addr_len) \ + ({ \ + const struct nlattr *__t = (tb)[(attr)]; \ + \ + if (__t) { \ + if (nla_len (__t) != (addr_len)) { \ + goto errout; \ + } \ + } \ + !!__t; \ + }) - name = rtnl_link_get_name (nlo); - if (name) - g_strlcpy (obj->name, name, sizeof (obj->name)); - obj->type = link_extract_type (platform, nlo, completed_from_cache, &link_cached, &kind); - obj->kind = g_intern_string (kind); - obj->flags = rtnl_link_get_flags (nlo); - obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP); - obj->master = rtnl_link_get_master (nlo); - parent = rtnl_link_get_link (nlo); - if (parent > 0) { - gint32 link_netnsid; - - if (_rtnl_link_get_link_netnsid (nlo, &link_netnsid) == 0) - obj->parent = NM_PLATFORM_LINK_OTHER_NETNS; - else - obj->parent = parent; - } - obj->mtu = rtnl_link_get_mtu (nlo); - obj->arptype = rtnl_link_get_arptype (nlo); - - if (obj->type == NM_LINK_TYPE_VLAN) { - if (!g_strcmp0 (rtnl_link_get_type (nlo), "vlan")) - obj->vlan_id = rtnl_link_vlan_get_id (nlo); - else if (completed_from_cache) { - _lookup_link_cached (platform, obj->ifindex, completed_from_cache, &link_cached); - if (link_cached) - obj->vlan_id = link_cached->link.vlan_id; - } - } +/*****************************************************************************/ - if ((nladdr = rtnl_link_get_addr (nlo))) { - unsigned int l = 0; +/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */ +static gboolean +_parse_af_inet6 (NMPlatform *platform, + struct nlattr *attr, + NMUtilsIPv6IfaceId *out_iid, + guint8 *out_iid_is_valid, + guint8 *out_addr_gen_mode_inv) +{ + static struct nla_policy policy[IFLA_INET6_MAX+1] = { + [IFLA_INET6_FLAGS] = { .type = NLA_U32 }, + [IFLA_INET6_CACHEINFO] = { .minlen = sizeof(struct ifla_cacheinfo) }, + [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[IFLA_INET6_MAX+1]; + int err; + struct in6_addr i6_token; + gboolean iid_is_valid = FALSE; + guint8 i6_addr_gen_mode_inv = 0; + gboolean success = FALSE; - l = nl_addr_get_len (nladdr); - if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) { - G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data)); - memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l); - obj->addr.len = l; - } - } + err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy); + if (err < 0) + goto errout; -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE - if (_support_user_ipv6ll_get ()) { - guint8 mode = 0; + if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4) + goto errout; + if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8) + goto errout; + if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8) + goto errout; - if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0) - obj->inet6_addr_gen_mode_inv = _nm_platform_uint8_inv (mode); + if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) { + nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr)); + if (!IN6_IS_ADDR_UNSPECIFIED (&i6_token)) + iid_is_valid = TRUE; } -#endif -#if HAVE_LIBNL_INET6_TOKEN - if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) { - if ( nl_addr_get_family (nladdr) == AF_INET6 - && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) { - struct in6_addr *addr; - NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid; - - addr = nl_addr_get_binary_addr (nladdr); - iid->id_u8[7] = addr->s6_addr[15]; - iid->id_u8[6] = addr->s6_addr[14]; - iid->id_u8[5] = addr->s6_addr[13]; - iid->id_u8[4] = addr->s6_addr[12]; - iid->id_u8[3] = addr->s6_addr[11]; - iid->id_u8[2] = addr->s6_addr[10]; - iid->id_u8[1] = addr->s6_addr[9]; - iid->id_u8[0] = addr->s6_addr[8]; - obj->inet6_token.is_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. */ + _support_user_ipv6ll_detect (tb); + + 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". */ + goto errout; } - nl_addr_put (nladdr); } -#endif - obj_priv->netlink.is_in_netlink = TRUE; + success = TRUE; + if (iid_is_valid) { + out_iid->id_u8[7] = i6_token.s6_addr[15]; + out_iid->id_u8[6] = i6_token.s6_addr[14]; + out_iid->id_u8[5] = i6_token.s6_addr[13]; + out_iid->id_u8[4] = i6_token.s6_addr[12]; + out_iid->id_u8[3] = i6_token.s6_addr[11]; + out_iid->id_u8[2] = i6_token.s6_addr[10]; + out_iid->id_u8[1] = i6_token.s6_addr[9]; + out_iid->id_u8[0] = i6_token.s6_addr[8]; + *out_iid_is_valid = TRUE; + } + *out_addr_gen_mode_inv = i6_addr_gen_mode_inv; +errout: + return success; +} - return TRUE; +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_gre (const char *kind, struct nlattr *info_data) +{ + static struct nla_policy policy[IFLA_GRE_MAX + 1] = { + [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[IFLA_GRE_MAX + 1]; + int err; + NMPObject *obj; + NMPlatformLnkGre *props; + + if (!info_data || g_strcmp0 (kind, "gre")) + return NULL; + + err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy); + if (err < 0) + return NULL; + + obj = nmp_object_new (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]); + + return obj; } -/* _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) +/*****************************************************************************/ + +/* 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) { - const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100); - gint64 timestamp_nl_ms; + static struct nla_policy policy[IFLA_IPOIB_MAX + 1] = { + [IFLA_IPOIB_PKEY] = { .type = NLA_U16 }, + [IFLA_IPOIB_MODE] = { .type = NLA_U16 }, + [IFLA_IPOIB_UMCAST] = { .type = NLA_U16 }, + }; + struct nlattr *tb[IFLA_IPOIB_MAX + 1]; + NMPlatformLnkInfiniband *info; + NMPObject *obj; + int err; + const char *mode; - /* convert timestamp from 1/100th of a second to msec. */ - timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100); + if (!info_data || g_strcmp0 (kind, "ipoib")) + return NULL; - /* 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; + err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, policy); + if (err < 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; } - return timestamp_nl_ms; + 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 guint32 -_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out_now_nm) +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data) { - guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr); - struct timespec tp; - gint64 now_nl, now_nm, result; + static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = { + [IFLA_IPTUN_LINK] = { .type = NLA_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NLA_UNSPEC, + .minlen = sizeof (struct in6_addr)}, + [IFLA_IPTUN_REMOTE] = { .type = NLA_UNSPEC, + .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 }, + }; + struct nlattr *tb[IFLA_IPTUN_MAX + 1]; int err; + NMPObject *obj; + NMPlatformLnkIp6Tnl *props; + guint32 flowinfo; - /* timestamp is unset. Default to 1. */ - if (!last_update_time) { - if (out_now_nm) - *out_now_nm = 0; - return 1; + if (!info_data || g_strcmp0 (kind, "ip6tnl")) + return NULL; + + err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy); + if (err < 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]) + memcpy (&props->local, nla_data (tb[IFLA_IPTUN_LOCAL]), sizeof (props->local)); + if (tb[IFLA_IPTUN_REMOTE]) + memcpy (&props->remote, nla_data (tb[IFLA_IPTUN_REMOTE]), sizeof (props->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]); - /* do all the calculations in milliseconds scale */ + return obj; +} - err = clock_gettime (CLOCK_MONOTONIC, &tp); - g_assert (err == 0); - now_nm = nm_utils_get_monotonic_timestamp_ms (); - now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) + - (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); +/*****************************************************************************/ - result = now_nm - (now_nl - _timestamp_nl_to_ms (last_update_time, now_nl)); +static NMPObject * +_parse_lnk_ipip (const char *kind, struct nlattr *info_data) +{ + static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = { + [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[IFLA_IPTUN_MAX + 1]; + int err; + NMPObject *obj; + NMPlatformLnkIpIp *props; - if (out_now_nm) - *out_now_nm = now_nm / 1000; + if (!info_data || g_strcmp0 (kind, "ipip")) + return NULL; - /* converting the last_update_time into nm_utils_get_monotonic_timestamp_ms() 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/last_update_time drifts from - * nm_utils_get_monotonic_timestamp_ms() scale. - */ - if (result <= 1000) - return 1; + err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy); + if (err < 0) + return NULL; - if (result > now_nm) - return now_nm / 1000; + obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IPIP, NULL); + props = &obj->lnk_ipip; - return result / 1000; + 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 guint32 -_extend_lifetime (guint32 lifetime, guint32 seconds) +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_macvlan (const char *kind, struct nlattr *info_data) { - guint64 v; + static struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = { + [IFLA_MACVLAN_MODE] = { .type = NLA_U32 }, + [IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 }, + }; + NMPlatformLnkMacvlan *props; + struct nlattr *tb[IFLA_MACVLAN_MAX + 1]; + int err; + NMPObject *obj; + gboolean tap; - if ( lifetime == NM_PLATFORM_LIFETIME_PERMANENT - || seconds == 0) - return lifetime; + if (!info_data) + return NULL; - v = (guint64) lifetime + (guint64) seconds; - return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1); + if (!g_strcmp0 (kind, "macvlan")) + tap = FALSE; + else if (!g_strcmp0 (kind, "macvtap")) + tap = TRUE; + else + return NULL; + + err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, policy); + if (err < 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; } -/* 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 -_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr, - guint32 *out_timestamp, - guint32 *out_lifetime, - guint32 *out_preferred) +/*****************************************************************************/ + +static NMPObject * +_parse_lnk_sit (const char *kind, struct nlattr *info_data) { - guint32 timestamp = 0; - gint32 now; - guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr); - guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr); + static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = { + [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[IFLA_IPTUN_MAX + 1]; + int err; + NMPObject *obj; + NMPlatformLnkSit *props; - if ( lifetime != NM_PLATFORM_LIFETIME_PERMANENT - || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { - if (preferred > lifetime) - preferred = lifetime; - timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now); + if (!info_data || g_strcmp0 (kind, "sit")) + return NULL; - 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_s (); - } - if (timestamp < now) { - guint32 diff = now - timestamp; + err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy); + if (err < 0) + return NULL; - lifetime = _extend_lifetime (lifetime, diff); - preferred = _extend_lifetime (preferred, diff); - } else - nm_assert (timestamp == now); - } - *out_timestamp = timestamp; - *out_lifetime = lifetime; - *out_preferred = preferred; + 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; } -gboolean -_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +/*****************************************************************************/ + +static gboolean +_vlan_qos_mapping_from_nla (struct nlattr *nlattr, + const NMVlanQosMapping **out_map, + guint *out_n_map) { - NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj; - struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo; - struct nl_addr *nladdr = rtnl_addr_get_local (nlo); - struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo); - const char *label; + struct nlattr *nla; + int remaining; + gs_unref_ptrarray GPtrArray *array = NULL; - if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address)) - g_return_val_if_reached (FALSE); + 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)); - obj->ifindex = rtnl_addr_get_ifindex (nlo); - obj->plen = rtnl_addr_get_prefixlen (nlo); - memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address)); + nm_assert (out_map && !*out_map); + nm_assert (out_n_map && !*out_n_map); - if (id_only) + if (!nlattr) return TRUE; - obj->source = NM_IP_CONFIG_SOURCE_KERNEL; - _nlo_rtnl_addr_get_lifetimes (nlo, - &obj->timestamp, - &obj->lifetime, - &obj->preferred); - if (nlpeer) { - if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address)) - g_warn_if_reached (); - else - memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address)); + 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; } - label = rtnl_addr_get_label (nlo); - /* Check for ':'; we're only interested in labels used as interface aliases */ - if (label && strchr (label, ':')) - g_strlcpy (obj->label, label, sizeof (obj->label)); return TRUE; } -gboolean -_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +/* Copied and heavily modified from libnl3's vlan_parse() */ +static NMPObject * +_parse_lnk_vlan (const char *kind, struct nlattr *info_data) { - NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj; - struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo; - struct nl_addr *nladdr = rtnl_addr_get_local (nlo); - struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo); + static struct nla_policy policy[IFLA_VLAN_MAX+1] = { + [IFLA_VLAN_ID] = { .type = NLA_U16 }, + [IFLA_VLAN_FLAGS] = { .minlen = sizeof(struct ifla_vlan_flags) }, + [IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED }, + [IFLA_VLAN_EGRESS_QOS] = { .type = NLA_NESTED }, + [IFLA_VLAN_PROTOCOL] = { .type = NLA_U16 }, + }; + struct nlattr *tb[IFLA_VLAN_MAX+1]; + int err; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject *obj_result; - if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address)) - g_return_val_if_reached (FALSE); + if (!info_data || g_strcmp0 (kind, "vlan")) + return NULL; + + if ((err = nla_parse_nested (tb, IFLA_VLAN_MAX, 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->ifindex = rtnl_addr_get_ifindex (nlo); - obj->plen = rtnl_addr_get_prefixlen (nlo); - memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address)); + + 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 + +/* 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 struct nla_policy policy[IFLA_VXLAN_MAX + 1] = { + [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[IFLA_VXLAN_MAX + 1]; + struct nm_ifla_vxlan_port_range *range; + int err; + NMPObject *obj; + + if (!info_data || g_strcmp0 (kind, "vxlan")) + return NULL; + + err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, policy); + if (err < 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_GROUP6]) + memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6)); + if (tb[IFLA_VXLAN_LOCAL6]) + memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6)); + + 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]) { + range = nla_data (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; +} + +/*****************************************************************************/ + +/* 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 struct nla_policy policy[IFLA_MAX+1] = { + [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 = sizeof(struct rtnl_link_stats) }, + [IFLA_STATS64] = { .minlen = sizeof(struct rtnl_link_stats64)}, + [IFLA_MAP] = { .minlen = sizeof(struct rtnl_link_ifmap) }, + [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 }, + }; + static struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = { + [IFLA_INFO_KIND] = { .type = NLA_STRING }, + [IFLA_INFO_DATA] = { .type = NLA_NESTED }, + [IFLA_INFO_XSTATS] = { .type = NLA_NESTED }, + }; + const struct ifinfomsg *ifi; + struct nlattr *tb[IFLA_MAX+1]; + struct nlattr *li[IFLA_INFO_MAX+1]; + struct nlattr *nl_info_data = NULL; + const char *nl_info_kind = NULL; + int err; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject *obj_result = NULL; + gboolean completed_from_cache_val = FALSE; + gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL; + const NMPObject *link_cached = NULL; + NMPObject *lnk_data = NULL; + + if (!nlmsg_valid_hdr (nlh, sizeof (*ifi))) + return NULL; + ifi = nlmsg_data(nlh); + + obj = nmp_object_new_link (ifi->ifi_index); if (id_only) - return TRUE; + goto id_only_handled; + + err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy); + if (err < 0) + goto errout; + + if (!tb[IFLA_IFNAME]) + goto errout; + nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ); + if (!obj->link.name[0]) + goto errout; - obj->source = NM_IP_CONFIG_SOURCE_KERNEL; - _nlo_rtnl_addr_get_lifetimes (nlo, - &obj->timestamp, - &obj->lifetime, - &obj->preferred); - obj->flags = rtnl_addr_get_flags (nlo); + if (tb[IFLA_LINKINFO]) { + err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info); + if (err < 0) + goto errout; - if (nlpeer) { - if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address)) - g_warn_if_reached (); + if (li[IFLA_INFO_KIND]) + nl_info_kind = nla_get_string (li[IFLA_INFO_KIND]); + + nl_info_data = li[IFLA_INFO_DATA]; + } + + obj->link.flags = ifi->ifi_flags; + obj->link.connected = NM_FLAGS_HAS (obj->link.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.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 - memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address)); + obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS; } - return TRUE; + if (tb[IFLA_ADDRESS]) { + int l = nla_len (tb[IFLA_ADDRESS]); + + if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) { + G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->link.addr.data)); + memcpy (obj->link.addr.data, nla_data (tb[IFLA_ADDRESS]), l); + obj->link.addr.len = l; + } + } + + 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.iid, + &obj->link.inet6_token.is_valid, + &obj->link.inet6_addr_gen_mode_inv); + break; + } + } + } + + if (tb[IFLA_MTU]) + obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]); + + switch (obj->link.type) { + case NM_LINK_TYPE_GRE: + 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_IPIP: + lnk_data = _parse_lnk_ipip (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_VLAN: + lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data); + break; + case NM_LINK_TYPE_VXLAN: + lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data); + break; + default: + goto lnk_data_handled; + } + + /* 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. */ + if (completed_from_cache) { + _lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached); + if ( link_cached + && link_cached->link.type == obj->link.type + && ( !lnk_data + || nmp_object_equal (lnk_data, link_cached->_link.netlink.lnk))) { + nmp_object_unref (lnk_data); + lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk); + } + } + +lnk_data_handled: + obj->_link.netlink.lnk = lnk_data; + + obj->_link.netlink.is_in_netlink = TRUE; +id_only_handled: + obj_result = obj; + obj = NULL; +errout: + return obj_result; } -gboolean -_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +/* Copied and heavily modified from libnl3's addr_msg_parser(). */ +static NMPObject * +_new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only) { - NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj; - struct rtnl_route *nlo = (struct rtnl_route *) _nlo; - struct nl_addr *dst, *gw; - struct rtnl_nexthop *nexthop; - struct nl_addr *pref_src; + static struct nla_policy policy[IFA_MAX+1] = { + [IFA_LABEL] = { .type = NLA_STRING, + .maxlen = IFNAMSIZ }, + [IFA_CACHEINFO] = { .minlen = sizeof(struct ifa_cacheinfo) }, + }; + const struct ifaddrmsg *ifa; + struct nlattr *tb[IFA_MAX+1]; + int err; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject *obj_result = NULL; + int addr_len; + guint32 lifetime, preferred, timestamp; - if (rtnl_route_get_type (nlo) != RTN_UNICAST || - rtnl_route_get_table (nlo) != RT_TABLE_MAIN || - rtnl_route_get_tos (nlo) != 0 || - rtnl_route_get_nnexthops (nlo) != 1) - return FALSE; + if (!nlmsg_valid_hdr (nlh, sizeof (*ifa))) + return NULL; + ifa = nlmsg_data(nlh); - nexthop = rtnl_route_nexthop_n (nlo, 0); - if (!nexthop) - g_return_val_if_reached (FALSE); + if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6)) + goto errout; + is_v4 = ifa->ifa_family == AF_INET; - dst = rtnl_route_get_dst (nlo); - if (!dst) - g_return_val_if_reached (FALSE); + err = nlmsg_parse(nlh, sizeof(*ifa), tb, IFA_MAX, policy); + if (err < 0) + goto errout; + + addr_len = is_v4 + ? sizeof (in_addr_t) + : sizeof (struct in6_addr); + + /*****************************************************************/ + + 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_errout (tb, IFA_ADDRESS, addr_len); + _check_addr_or_errout (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); + } else { + /* For IPv6, IFA_ADDRESS is always present. + * + * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address + * is :: (all-zero). + * + * If unexpectely 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); + } + } - if (nl_addr_get_len (dst)) { - if (nl_addr_get_len (dst) != sizeof (obj->network)) - g_return_val_if_reached (FALSE); - memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network)); + obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL; + + if (!is_v4) { + obj->ip6_address.flags = tb[IFA_FLAGS] + ? nla_get_u32 (tb[IFA_FLAGS]) + : ifa->ifa_flags; } - obj->ifindex = rtnl_route_nh_get_ifindex (nexthop); - obj->plen = nl_addr_get_prefixlen (dst); - obj->metric = rtnl_route_get_priority (nlo); - obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo)); - - gw = rtnl_route_nh_get_gateway (nexthop); - if (gw) { - if (nl_addr_get_len (gw) != sizeof (obj->gateway)) - g_warn_if_reached (); - else - memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway)); + + 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)); + } } - rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss); - if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) { + + lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + preferred = NM_PLATFORM_LIFETIME_PERMANENT; + timestamp = 0; + /* IPv6 only */ + if (tb[IFA_CACHEINFO]) { + const struct ifa_cacheinfo *ca = nla_data(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); + + obj_result = obj; + obj = NULL; +errout: + return obj_result; +} + +/* 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 struct nla_policy policy[RTA_MAX+1] = { + [RTA_IIF] = { .type = NLA_U32 }, + [RTA_OIF] = { .type = NLA_U32 }, + [RTA_PRIORITY] = { .type = NLA_U32 }, + [RTA_FLOW] = { .type = NLA_U32 }, + [RTA_CACHEINFO] = { .minlen = sizeof(struct rta_cacheinfo) }, + [RTA_METRICS] = { .type = NLA_NESTED }, + [RTA_MULTIPATH] = { .type = NLA_NESTED }, + }; + const struct rtmsg *rtm; + struct nlattr *tb[RTA_MAX + 1]; + int err; + gboolean is_v4; + nm_auto_nmpobj NMPObject *obj = NULL; + NMPObject *obj_result = NULL; + int addr_len; + struct { + gboolean is_present; + int ifindex; + NMIPAddr gateway; + } nh; + guint32 mss; + guint32 table; + + if (!nlmsg_valid_hdr (nlh, sizeof (*rtm))) + return NULL; + rtm = nlmsg_data(nlh); + + /***************************************************************** + * only handle ~normal~ routes. + *****************************************************************/ + + if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6)) + goto errout; + + if ( rtm->rtm_type != RTN_UNICAST + || rtm->rtm_tos != 0) + goto errout; + + err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy); + if (err < 0) + goto errout; + + table = tb[RTA_TABLE] + ? nla_get_u32 (tb[RTA_TABLE]) + : (guint32) rtm->rtm_table; + if (table != RT_TABLE_MAIN) + goto errout; + + /*****************************************************************/ + + is_v4 = rtm->rtm_family == AF_INET; + addr_len = is_v4 + ? sizeof (in_addr_t) + : sizeof (struct in6_addr); + + /***************************************************************** + * parse nexthops. Only handle routes with one nh. + *****************************************************************/ + + memset (&nh, 0, sizeof (nh)); + + if (tb[RTA_MULTIPATH]) { + struct rtnexthop *rtnh = nla_data (tb[RTA_MULTIPATH]); + size_t tlen = nla_len(tb[RTA_MULTIPATH]); + + while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) { + + if (nh.is_present) { + /* we don't support multipath routes. */ + goto errout; + } + nh.is_present = TRUE; + + nh.ifindex = rtnh->rtnh_ifindex; + + if (rtnh->rtnh_len > sizeof(*rtnh)) { + struct nlattr *ntb[RTA_MAX + 1]; + + err = nla_parse (ntb, RTA_MAX, (struct nlattr *) + RTNH_DATA(rtnh), + rtnh->rtnh_len - sizeof (*rtnh), + policy); + if (err < 0) + goto errout; + + if (_check_addr_or_errout (ntb, RTA_GATEWAY, addr_len)) + memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len); + } + + tlen -= RTNH_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + } + + if ( tb[RTA_OIF] + || tb[RTA_GATEWAY] + || tb[RTA_FLOW]) { + int ifindex = 0; + NMIPAddr gateway = NMIPAddrInit; + + if (tb[RTA_OIF]) + ifindex = nla_get_u32 (tb[RTA_OIF]); + if (_check_addr_or_errout (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) + goto errout; + } + } else if (!nh.is_present) + goto errout; + + /*****************************************************************/ + + mss = 0; + if (tb[RTA_METRICS]) { + struct nlattr *mtb[RTAX_MAX + 1]; + int i; + + err = nla_parse_nested(mtb, RTAX_MAX, tb[RTA_METRICS], NULL); + if (err < 0) + goto errout; + + for (i = 1; i <= RTAX_MAX; i++) { + if (mtb[i]) { + if (i == RTAX_ADVMSS) { + if (nla_len (mtb[i]) >= sizeof (uint32_t)) + mss = nla_get_u32(mtb[i]); + break; + } + } + } + } + + /*****************************************************************/ + + obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + + obj->ip_route.ifindex = nh.ifindex; + + if (_check_addr_or_errout (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 (is_v4) { + if (_check_addr_or_errout (tb, RTA_PREFSRC, addr_len)) + memcpy (&obj->ip4_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len); + } + + obj->ip_route.mss = mss; + + if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) { /* we must not straight way reject cloned routes, because we might have cached * a non-cloned route. If we now receive an update of the route with the route * being cloned, we must still return the object, so that we can remove the old @@ -1361,75 +1876,739 @@ _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject * * This happens, because this route is not nmp_object_is_alive(). * */ - obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED; + obj->ip_route.source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED; } else - obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo)); + obj->ip_route.source = _nm_ip_config_source_from_rtprot (rtm->rtm_protocol); - pref_src = rtnl_route_get_pref_src (nlo); - if (pref_src) { - if (nl_addr_get_len (pref_src) != sizeof (obj->pref_src)) - g_warn_if_reached (); - else - memcpy (&obj->pref_src, nl_addr_get_binary_addr (pref_src), sizeof (obj->pref_src)); + obj_result = obj; + obj = NULL; +errout: + return obj_result; +} + +/** + * 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); + default: + return NULL; + } +} + +/******************************************************************/ + +static gboolean +_nl_msg_new_link_set_afspec (struct nl_msg *msg, + int addr_gen_mode) +{ + 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) { + if (!(af_attr = nla_nest_start (msg, AF_INET6))) + goto nla_put_failure; + + NLA_PUT_U8 (msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode); + + nla_nest_end (msg, af_attr); } + nla_nest_end (msg, af_spec); + return TRUE; +nla_put_failure: + return FALSE; } -gboolean -_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +static gboolean +_nl_msg_new_link_set_linkinfo (struct nl_msg *msg, + NMLinkType link_type) { - NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj; - struct rtnl_route *nlo = (struct rtnl_route *) _nlo; - struct nl_addr *dst, *gw; - struct rtnl_nexthop *nexthop; + struct nlattr *info; + 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); + + nla_nest_end (msg, info); + + return TRUE; +nla_put_failure: + return 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; + +#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: + return FALSE; +} + +static struct nl_msg * +_nl_msg_new_link (int nlmsg_type, + int nlmsg_flags, + int ifindex, + const char *ifname, + unsigned flags_mask, + unsigned flags_set) +{ + struct nl_msg *msg; + struct ifinfomsg ifi = { + .ifi_change = flags_mask, + .ifi_flags = flags_set, + .ifi_index = ifindex, + }; + + nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK)); + + if (!(msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags))) + g_return_val_if_reached (NULL); + + if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0) + goto nla_put_failure; + + if (ifname) + NLA_PUT_STRING (msg, IFLA_IFNAME, ifname); + + return msg; +nla_put_failure: + nlmsg_free (msg); + g_return_val_if_reached (NULL); +} + +/* 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, + int plen, + gconstpointer peer_address, + guint32 flags, + int scope, + guint32 lifetime, + guint32 preferred, + const char *label) +{ + struct nl_msg *msg; + 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 (!msg) + g_return_val_if_reached (NULL); + + 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 (msg, &am, sizeof (am), NLMSG_ALIGNTO) < 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 ( family == AF_INET + && nlmsg_type != RTM_DELADDR + && address + && *((in_addr_t *) address) != 0) { + in_addr_t broadcast; + + broadcast = *((in_addr_t *) address) | ~nm_utils_ip4_prefix_to_netmask (plen); + NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast); + } + + 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 & ~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 msg; + +nla_put_failure: + nlmsg_free (msg); + g_return_val_if_reached (NULL); +} + +/* 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, + int nlmsg_flags, + int family, + int ifindex, + NMIPConfigSource source, + unsigned char scope, + gconstpointer network, + int plen, + gconstpointer gateway, + guint32 metric, + guint32 mss, + gconstpointer pref_src) +{ + struct nl_msg *msg; + struct rtmsg rtmsg = { + .rtm_family = family, + .rtm_tos = 0, + .rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */ + .rtm_protocol = _nm_ip_config_source_to_rtprot (source), + .rtm_scope = scope, + .rtm_type = RTN_UNICAST, + .rtm_flags = 0, + .rtm_dst_len = plen, + .rtm_src_len = 0, + }; + NMIPAddr network_clean; + + gsize addr_len; + + nm_assert (NM_IN_SET (family, AF_INET, AF_INET6)); + nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE)); + nm_assert (network); + + msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags); + if (!msg) + g_return_val_if_reached (NULL); + + if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0) + goto nla_put_failure; + + addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr); + + clear_host_address (family, network, plen, &network_clean); + NLA_PUT (msg, RTA_DST, addr_len, &network_clean); + + NLA_PUT_U32 (msg, RTA_PRIORITY, metric); + + if (pref_src) + NLA_PUT (msg, RTA_PREFSRC, addr_len, pref_src); + + if (mss > 0) { + struct nlattr *metrics; + + metrics = nla_nest_start (msg, RTA_METRICS); + if (!metrics) + goto nla_put_failure; + + NLA_PUT_U32 (msg, RTAX_ADVMSS, mss); + + nla_nest_end(msg, metrics); + } + + /* We currently don't have need for multi-hop routes... */ + if ( gateway + && memcmp (gateway, &nm_ip_addr_zero, addr_len) != 0) + NLA_PUT (msg, RTA_GATEWAY, addr_len, gateway); + NLA_PUT_U32 (msg, RTA_OIF, ifindex); + + return msg; + +nla_put_failure: + nlmsg_free (msg); + g_return_val_if_reached (NULL); +} + +/******************************************************************/ + +static int _support_kernel_extended_ifa_flags = -1; + +#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == -1)) + +static void +_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg) +{ + struct nlmsghdr *msg_hdr; + + if (!_support_kernel_extended_ifa_flags_still_undecided ()) + return; + + msg_hdr = nlmsg_hdr (msg); + if (msg_hdr->nlmsg_type != RTM_NEWADDR) + return; + + /* the extended address flags are only set for AF_INET6 */ + if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6) + return; + + /* 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 (they were added together). + **/ + _support_kernel_extended_ifa_flags = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), 8 /* IFA_FLAGS */); + _LOG2D ("support: kernel-extended-ifa-flags: %ssupported", _support_kernel_extended_ifa_flags ? "" : "not "); +} + +static gboolean +_support_kernel_extended_ifa_flags_get (void) +{ + if (_support_kernel_extended_ifa_flags_still_undecided ()) { + _LOG2W ("support: kernel-extended-ifa-flags: unable to detect kernel support for handling IPv6 temporary addresses. Assume none"); + _support_kernel_extended_ifa_flags = 0; + } + return _support_kernel_extended_ifa_flags; +} + +/****************************************************************** + * NMPlatform types and functions + ******************************************************************/ + +typedef struct { + guint32 seq_number; + gint64 timeout_abs_ns; + WaitForNlResponseResult seq_result; + WaitForNlResponseResult *out_seq_result; +} DelayedActionWaitForNlResponseData; + +typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate; + +struct _NMLinuxPlatformPrivate { + struct nl_sock *nlh; + guint32 nlh_seq_next; + guint32 nlh_seq_last_handled; + NMPCache *cache; + GIOChannel *event_channel; + guint event_id; + + gboolean sysctl_get_warned; + GHashTable *sysctl_get_prev_values; + + GUdevClient *udev_client; + + struct { + DelayedActionType flags; + GPtrArray *list_master_connected; + GPtrArray *list_refresh_link; + GArray *list_wait_for_nl_response; + gint is_handling; + } delayed_action; + + GHashTable *prune_candidates; + + GHashTable *wifi_data; +}; + +static inline NMLinuxPlatformPrivate * +NM_LINUX_PLATFORM_GET_PRIVATE (const void *self) +{ + nm_assert (NM_IS_LINUX_PLATFORM (self)); + + return ((NMLinuxPlatform *) self)->priv; +} + +G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM) + +void +nm_linux_platform_setup (void) +{ + g_object_new (NM_TYPE_LINUX_PLATFORM, + NM_PLATFORM_REGISTER_SINGLETON, TRUE, + NULL); +} - if (rtnl_route_get_type (nlo) != RTN_UNICAST || - rtnl_route_get_table (nlo) != RT_TABLE_MAIN || - rtnl_route_get_tos (nlo) != 0 || - rtnl_route_get_nnexthops (nlo) != 1) +/******************************************************************/ + +static void +_log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *value) +{ + GError *error = NULL; + char *contents, *contents_escaped; + char *value_escaped = g_strescape (value, NULL); + + if (!g_file_get_contents (path, &contents, NULL, &error)) { + _LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message); + g_clear_error (&error); + } else { + g_strstrip (contents); + contents_escaped = g_strescape (contents, NULL); + if (strcmp (contents, value) == 0) + _LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped); + else + _LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped); + g_free (contents); + g_free (contents_escaped); + } + g_free (value_escaped); +} + +#define _log_dbg_sysctl_set(platform, path, value) \ + G_STMT_START { \ + if (_LOGD_ENABLED ()) { \ + _log_dbg_sysctl_set_impl (platform, path, value); \ + } \ + } G_STMT_END + +static gboolean +sysctl_set (NMPlatform *platform, const char *path, const char *value) +{ + int fd, len, nwrote, tries; + char *actual; + + g_return_val_if_fail (path != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + /* Don't write outside known locations */ + g_assert (g_str_has_prefix (path, "/proc/sys/") + || g_str_has_prefix (path, "/sys/")); + /* Don't write to suspicious locations */ + g_assert (!strstr (path, "/../")); + + fd = open (path, O_WRONLY | O_TRUNC); + if (fd == -1) { + if (errno == ENOENT) { + _LOGD ("sysctl: failed to open '%s': (%d) %s", + path, errno, strerror (errno)); + } else { + _LOGE ("sysctl: failed to open '%s': (%d) %s", + path, errno, strerror (errno)); + } return FALSE; + } - nexthop = rtnl_route_nexthop_n (nlo, 0); - if (!nexthop) - g_return_val_if_reached (FALSE); + _log_dbg_sysctl_set (platform, path, value); - dst = rtnl_route_get_dst (nlo); - if (!dst) - g_return_val_if_reached (FALSE); + /* 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. + */ + actual = g_strdup_printf ("%s\n", value); - if (nl_addr_get_len (dst)) { - if (nl_addr_get_len (dst) != sizeof (obj->network)) - g_return_val_if_reached (FALSE); - memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network)); + /* Try to write the entire value three times if a partial write occurs */ + len = strlen (actual); + for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) { + nwrote = write (fd, actual, len); + if (nwrote == -1) { + if (errno == EINTR) { + _LOGD ("sysctl: interrupted, will try again"); + continue; + } + break; + } + } + if (nwrote == -1 && errno != EEXIST) { + _LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s", + path, value, errno, strerror (errno)); + } else if (nwrote < len) { + _LOGE ("sysctl: failed to set '%s' to '%s' after three attempts", + path, value); } - obj->ifindex = rtnl_route_nh_get_ifindex (nexthop); - obj->plen = nl_addr_get_prefixlen (dst); - obj->metric = rtnl_route_get_priority (nlo); - if (id_only) - return TRUE; + g_free (actual); + close (fd); + return (nwrote == len); +} + +static GSList *sysctl_clear_cache_list; + +void +_nm_linux_platform_sysctl_clear_cache (void) +{ + while (sysctl_clear_cache_list) { + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data); + + sysctl_clear_cache_list = g_slist_delete_link (sysctl_clear_cache_list, sysctl_clear_cache_list); + + g_hash_table_destroy (priv->sysctl_get_prev_values); + priv->sysctl_get_prev_values = NULL; + priv->sysctl_get_warned = FALSE; + } +} + +static void +_log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *contents) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + const char *prev_value = NULL; + + if (!priv->sysctl_get_prev_values) { + sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform); + priv->sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + } else + prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, path); + + if (prev_value) { + if (strcmp (prev_value, contents) != 0) { + char *contents_escaped = g_strescape (contents, NULL); + char *prev_value_escaped = g_strescape (prev_value, NULL); + + _LOGD ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped); + g_free (contents_escaped); + g_free (prev_value_escaped); + g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents)); + } + } else { + char *contents_escaped = g_strescape (contents, NULL); - gw = rtnl_route_nh_get_gateway (nexthop); - if (gw) { - if (nl_addr_get_len (gw) != sizeof (obj->gateway)) - g_warn_if_reached (); + _LOGD ("sysctl: reading '%s': '%s'", path, contents_escaped); + g_free (contents_escaped); + g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents)); + } + + if ( !priv->sysctl_get_warned + && g_hash_table_size (priv->sysctl_get_prev_values) > 50000) { + _LOGW ("sysctl: the internal cache for debug-logging of sysctl values grew pretty large. You can clear it by disabling debug-logging: `nmcli general logging level KEEP domains PLATFORM:INFO`."); + priv->sysctl_get_warned = TRUE; + } +} + +#define _log_dbg_sysctl_get(platform, path, contents) \ + G_STMT_START { \ + if (_LOGD_ENABLED ()) \ + _log_dbg_sysctl_get_impl (platform, path, contents); \ + } G_STMT_END + +static char * +sysctl_get (NMPlatform *platform, const char *path) +{ + GError *error = NULL; + char *contents; + + /* Don't write outside known locations */ + g_assert (g_str_has_prefix (path, "/proc/sys/") + || g_str_has_prefix (path, "/sys/")); + /* Don't write to suspicious locations */ + g_assert (!strstr (path, "/../")); + + if (!g_file_get_contents (path, &contents, NULL, &error)) { + /* We assume FAILED means EOPNOTSUP */ + if ( g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) + || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) + _LOGD ("error reading %s: %s", path, error->message); else - memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway)); + _LOGE ("error reading %s: %s", path, error->message); + g_clear_error (&error); + return NULL; } - rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss); - if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) - obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED; - else - obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo)); - return TRUE; + g_strstrip (contents); + + _log_dbg_sysctl_get (platform, path, contents); + + return contents; } /******************************************************************/ +static gboolean +check_support_kernel_extended_ifa_flags (NMPlatform *platform) +{ + g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE); + + return _support_kernel_extended_ifa_flags_get (); +} + +static gboolean +check_support_user_ipv6ll (NMPlatform *platform) +{ + g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE); + + return _support_user_ipv6ll_get (); +} + static void -do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible, NMPlatformReason reason) +process_events (NMPlatform *platform) +{ + delayed_action_handle_all (platform, TRUE); +} + +/******************************************************************/ + +#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \ + ((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \ + nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \ + NULL)) + +/******************************************************************/ + +static void +do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible) { gboolean is_visible; NMPObject obj_clone; @@ -1476,16 +2655,21 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach klass = NMP_OBJECT_GET_CLASS (obj); - _LOGt ("emit signal %s %s: %s (%ld)", + _LOGt ("emit signal %s %s: %s", klass->signal_type, nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op), - nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0), - (long) reason); + nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); /* don't expose @obj directly, but clone the public fields. A signal handler might * call back into NMPlatform which could invalidate (or modify) @obj. */ memcpy (&obj_clone.object, &obj->object, klass->sizeof_public); - g_signal_emit_by_name (platform, klass->signal_type, klass->obj_type, obj_clone.object.ifindex, &obj_clone.object, (NMPlatformSignalChangeType) cache_op, reason); + g_signal_emit (platform, + _nm_platform_signal_id_get (klass->signal_type_id), + 0, + klass->obj_type, + obj_clone.object.ifindex, + &obj_clone.object, + (NMPlatformSignalChangeType) cache_op); } /******************************************************************/ @@ -1528,42 +2712,140 @@ delayed_action_to_string (DelayedActionType action_type) case DELAYED_ACTION_TYPE_REFRESH_LINK : return "refresh-link"; case DELAYED_ACTION_TYPE_MASTER_CONNECTED : return "master-connected"; case DELAYED_ACTION_TYPE_READ_NETLINK : return "read-netlink"; + case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE : return "wait-for-nl-response"; default: return "unknown"; } } -#define _LOGt_delayed_action(action_type, arg, operation) \ - _LOGt ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg)) +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_ns (); + char b[255]; + + nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT"%s%s)", + data->seq_number, + timeout < 0 ? "-" : "", + (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND, + (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND, + data->seq_result ? ", " : "", + data->seq_result ? wait_for_nl_response_to_string (data->seq_result, 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 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; + WaitForNlResponseResult *out_seq_result; + + 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"); + + out_seq_result = data->out_seq_result; + + g_array_remove_index_fast (priv->delayed_action.list_wait_for_nl_response, idx); + /* Note: @data is invalidated at this point */ + + if (priv->delayed_action.list_wait_for_nl_response->len <= 0) + priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE; + + if (out_seq_result) + *out_seq_result = seq_result; +} + +static void +delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform, + WaitForNlResponseResult result) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) { + while (priv->delayed_action.list_wait_for_nl_response->len > 0) + delayed_action_wait_for_nl_response_complete (platform, priv->delayed_action.list_wait_for_nl_response->len - 1, result); + } + 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); +} + +/*****************************************************************************/ static void delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - auto_nmp_obj NMPObject *obj_cache = NULL; + nm_auto_nmpobj NMPObject *obj_cache = NULL; gboolean was_visible; NMPCacheOpsType cache_op; cache_op = nmp_cache_update_link_master_connected (priv->cache, master_ifindex, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL); + do_emit_signal (platform, obj_cache, cache_op, was_visible); } static void delayed_action_handle_REFRESH_LINK (NMPlatform *platform, int ifindex) { - do_request_link (platform, ifindex, NULL, FALSE); + do_request_link_no_delayed_actions (platform, ifindex, NULL); } static void delayed_action_handle_REFRESH_ALL (NMPlatform *platform, DelayedActionType flags) { - do_request_all (platform, flags, FALSE); + do_request_all_no_delayed_actions (platform, flags); } static void delayed_action_handle_READ_NETLINK (NMPlatform *platform) { - event_handler_read_netlink_all (platform, TRUE); + 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 @@ -1572,10 +2854,8 @@ 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) { - nm_clear_g_source (&priv->delayed_action.idle_id); + 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 @@ -1622,20 +2902,30 @@ delayed_action_handle_one (NMPlatform *platform) return TRUE; } - nm_assert (priv->delayed_action.flags == DELAYED_ACTION_TYPE_REFRESH_LINK); - nm_assert (priv->delayed_action.list_refresh_link->len > 0); + 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 (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 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 (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"); + _LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle"); - delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data)); + delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data)); - return TRUE; + 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 @@ -1644,22 +2934,18 @@ delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink) NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); gboolean any = FALSE; - nm_clear_g_source (&priv->delayed_action.idle_id); + 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--; - return any; -} -static gboolean -delayed_action_handle_idle (gpointer user_data) -{ - NM_LINUX_PLATFORM_GET_PRIVATE (user_data)->delayed_action.idle_id = 0; - delayed_action_handle_all (user_data, FALSE); - return G_SOURCE_REMOVE; + cache_prune_candidates_prune (platform); + + return any; } static void @@ -1670,16 +2956,25 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp nm_assert (action_type != DELAYED_ACTION_TYPE_NONE); - if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)) { - nm_assert (nm_utils_is_power_of_two (action_type)); + switch (action_type) { + case DELAYED_ACTION_TYPE_REFRESH_LINK: if (_nm_utils_ptrarray_find_first (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); - } else if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) { - nm_assert (nm_utils_is_power_of_two (action_type)); + break; + case DELAYED_ACTION_TYPE_MASTER_CONNECTED: if (_nm_utils_ptrarray_find_first (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); - } else + 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; @@ -1689,9 +2984,22 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp _LOGt_delayed_action (iflags, user_data, "schedule"); } } +} - if (priv->delayed_action.is_handling == 0 && priv->delayed_action.idle_id == 0) - priv->delayed_action.idle_id = g_idle_add (delayed_action_handle_idle, platform); +static void +delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform, + guint32 seq_number, + WaitForNlResponseResult *out_seq_result) +{ + DelayedActionWaitForNlResponseData data = { + .seq_number = seq_number, + .timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)), + .out_seq_result = out_seq_result, + }; + + delayed_action_schedule (platform, + DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, + &data); } /******************************************************************/ @@ -1760,11 +3068,11 @@ cache_prune_candidates_prune (NMPlatform *platform) g_hash_table_iter_init (&iter, prune_candidates); while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) { - auto_nmp_obj NMPObject *obj_cache = NULL; + nm_auto_nmpobj NMPObject *obj_cache = NULL; _LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL); + do_emit_signal (platform, obj_cache, cache_op, was_visible); } g_hash_table_unref (prune_candidates); @@ -1776,8 +3084,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP NMPlatform *platform = NM_PLATFORM (user_data); NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); const NMPClass *klass; - char str_buf[sizeof (_nm_platform_to_string_buffer)]; - char str_buf2[sizeof (_nm_platform_to_string_buffer)]; + char str_buf[sizeof (_nm_utils_to_string_buffer)]; + char str_buf2[sizeof (_nm_utils_to_string_buffer)]; nm_assert (old || new); nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED)); @@ -1850,7 +3158,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP /* 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 (rh #1262908). */ + * Currently, kernel misses to sent us a notification in this case + * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */ if ( ops_type == NMP_CACHE_OPS_REMOVED && old /* <-- nonsensical, make coverity happy */ @@ -1894,6 +3203,29 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP && (new && new->_link.netlink.is_in_netlink) && (!old || !old->_link.netlink.is_in_netlink)) { + if (!new->_link.netlink.lnk) { + /* 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 */ + switch (new->link.type) { + case NM_LINK_TYPE_GRE: + case NM_LINK_TYPE_IP6TNL: + case NM_LINK_TYPE_INFINIBAND: + case NM_LINK_TYPE_MACVLAN: + case NM_LINK_TYPE_MACVTAP: + case NM_LINK_TYPE_SIT: + case NM_LINK_TYPE_VLAN: + case NM_LINK_TYPE_VXLAN: + delayed_action_schedule (platform, + DELAYED_ACTION_TYPE_REFRESH_LINK, + GINT_TO_POINTER (new->link.ifindex)); + break; + default: + break; + } + } if ( new->link.type == NM_LINK_TYPE_VETH && new->link.parent == 0) { /* the initial notification when adding a veth pair can lack the parent/IFLA_LINK @@ -1959,100 +3291,74 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP } } -static NMPCacheOpsType -cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason) -{ - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - NMPObject *obj_cache; - gboolean was_visible; - NMPCacheOpsType cache_op; - - cache_op = nmp_cache_remove_netlink (priv->cache, obj_needle, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL); - - if (out_obj_cache) - *out_obj_cache = obj_cache; - else - nmp_object_unref (obj_cache); - if (out_was_visible) - *out_was_visible = was_visible; - - return cache_op; -} +/******************************************************************/ -static NMPCacheOpsType -cache_update_netlink (NMPlatform *platform, NMPObject *obj, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason) +static int +_nl_send_auto_with_seq (NMPlatform *platform, + struct nl_msg *nlmsg, + WaitForNlResponseResult *out_seq_result) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - NMPObject *obj_cache; - gboolean was_visible; - NMPCacheOpsType cache_op; + guint32 seq; + int nle; - /* This is basically a convenience method to call nmp_cache_update() and do_emit_signal() - * at once. */ + /* complete the message with a sequence number (ensuring it's not zero). */ + seq = priv->nlh_seq_next++ ?: priv->nlh_seq_next++; - cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible, reason); + nlmsg_hdr (nlmsg)->nlmsg_seq = seq; - if (out_obj_cache) - *out_obj_cache = obj_cache; - else - nmp_object_unref (obj_cache); - if (out_was_visible) - *out_was_visible = was_visible; + nle = nl_send_auto (priv->nlh, nlmsg); - return cache_op; -} + if (nle >= 0) { + nle = 0; + delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result); + } else + _LOGD ("netlink: send: failed sending message: %s (%d)", nl_geterror (nle), nle); -/******************************************************************/ + return nle; +} static void -_new_sequence_number (NMPlatform *platform, guint32 seq) +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; - _LOGt ("_new_sequence_number(): new sequence number %u", seq); - - priv->nlh_seq_expect = seq; -} + if (name && !name[0]) + name = NULL; -static void -do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action) -{ - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - guint32 seq; + g_return_if_fail (ifindex > 0 || name); - _LOGt ("do_request_link (%d,%s)", ifindex, name ? name : ""); + _LOGD ("do-request-link: %d %s", ifindex, name ? name : ""); if (ifindex > 0) { cache_prune_candidates_record_one (platform, (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex)); } - event_handler_read_netlink_all (platform, FALSE); - - if (_nl_sock_request_link (platform, priv->nlh_event, ifindex, name, &seq) == 0) - _new_sequence_number (platform, seq); + event_handler_read_netlink (platform, FALSE); - event_handler_read_netlink_all (platform, TRUE); - - cache_prune_candidates_prune (platform); - - if (handle_delayed_action) - delayed_action_handle_all (platform, FALSE); + nlmsg = _nl_msg_new_link (RTM_GETLINK, + 0, + ifindex, + name, + 0, + 0); + if (nlmsg) + _nl_send_auto_with_seq (platform, nlmsg, NULL); } static void -do_request_one_type (NMPlatform *platform, NMPObjectType obj_type, gboolean handle_delayed_action) +do_request_link (NMPlatform *platform, int ifindex, const char *name) { - do_request_all (platform, delayed_action_refresh_from_object_type (obj_type), handle_delayed_action); + do_request_link_no_delayed_actions (platform, ifindex, name); + delayed_action_handle_all (platform, FALSE); } static void -do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action) +do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - guint32 seq; DelayedActionType iflags; nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL)); @@ -2066,6 +3372,12 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) { if (NM_FLAGS_HAS (action_type, iflags)) { NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags); + const NMPClass *klass = nmp_class_from_type (obj_type); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct rtgenmsg gmsg = { + .rtgen_family = klass->addr_family, + }; + int nle; /* clear any delayed action that request a refresh of this object type. */ priv->delayed_action.flags &= ~iflags; @@ -2076,496 +3388,129 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha _LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)"); } - event_handler_read_netlink_all (platform, FALSE); + event_handler_read_netlink (platform, FALSE); - if (_nl_sock_request_all (platform, priv->nlh_event, obj_type, &seq) == 0) - _new_sequence_number (platform, seq); - } - } - event_handler_read_netlink_all (platform, TRUE); - - cache_prune_candidates_prune (platform); - - if (handle_delayed_action) - delayed_action_handle_all (platform, FALSE); -} - -static gboolean -kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl_object *nlo) -{ - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int nle; - - g_return_val_if_fail (nlo, FALSE); + /* reimplement + * nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP); + * because we need the sequence number. + */ + nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP); + if (!nlmsg) + goto next; - switch (obj_type) { - case NMP_OBJECT_TYPE_LINK: - nle = rtnl_link_add (priv->nlh, (struct rtnl_link *) nlo, NLM_F_CREATE); - break; - case NMP_OBJECT_TYPE_IP4_ADDRESS: - case NMP_OBJECT_TYPE_IP6_ADDRESS: - nle = rtnl_addr_add (priv->nlh, (struct rtnl_addr *) nlo, NLM_F_CREATE | NLM_F_REPLACE); - break; - case NMP_OBJECT_TYPE_IP4_ROUTE: - case NMP_OBJECT_TYPE_IP6_ROUTE: - nle = rtnl_route_add (priv->nlh, (struct rtnl_route *) nlo, NLM_F_CREATE | NLM_F_REPLACE); - break; - default: - g_return_val_if_reached (-NLE_INVAL); - } + nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO); + if (nle < 0) + goto next; - _LOGt ("kernel-add-%s: returned %s (%d)", - nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle); - - switch (nle) { - case -NLE_SUCCESS: - return -NLE_SUCCESS; - case -NLE_EXIST: - /* NLE_EXIST is considered equivalent to success to avoid race conditions. You - * never know when something sends an identical object just before - * NetworkManager. */ - if (obj_type != NMP_OBJECT_TYPE_LINK) - return -NLE_SUCCESS; - /* fall-through */ - default: - return nle; + _nl_send_auto_with_seq (platform, nlmsg, NULL); + } +next: + ; } } -static int -kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const struct nl_object *object) +static void +do_request_one_type (NMPlatform *platform, NMPObjectType obj_type) { - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int nle; - - switch (object_type) { - case NMP_OBJECT_TYPE_LINK: - nle = rtnl_link_delete (priv->nlh, (struct rtnl_link *) object); - break; - case NMP_OBJECT_TYPE_IP4_ADDRESS: - case NMP_OBJECT_TYPE_IP6_ADDRESS: - nle = rtnl_addr_delete (priv->nlh, (struct rtnl_addr *) object, 0); - break; - case NMP_OBJECT_TYPE_IP4_ROUTE: - case NMP_OBJECT_TYPE_IP6_ROUTE: - nle = rtnl_route_delete (priv->nlh, (struct rtnl_route *) object, 0); - break; - default: - g_assert_not_reached (); - } - - switch (nle) { - case -NLE_SUCCESS: - return NLE_SUCCESS; - case -NLE_OBJ_NOTFOUND: - _LOGt ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed", - nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle); - return -NLE_SUCCESS; - case -NLE_FAILURE: - if (object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) { - /* On RHEL7 kernel, deleting a non existing address fails with ENXIO (which libnl maps to NLE_FAILURE) */ - _LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed", - nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle); - return NLE_SUCCESS; - } - break; - case -NLE_NOADDR: - if (object_type == NMP_OBJECT_TYPE_IP4_ADDRESS || object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) { - _LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed", - nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle); - return -NLE_SUCCESS; - } - break; - default: - break; - } - _LOGt ("kernel-delete-%s: failed with %s (%d)", - nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle); - return nle; + do_request_all_no_delayed_actions (platform, delayed_action_refresh_from_object_type (obj_type)); + delayed_action_handle_all (platform, FALSE); } -static int -kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *complete_from_cache) +static void +event_seq_check (NMPlatform *platform, struct nl_msg *msg, WaitForNlResponseResult seq_result) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - struct nl_msg *msg; - int nle; - const int nlflags = 0; - int ifindex; - - ifindex = rtnl_link_get_ifindex (nlo); + DelayedActionWaitForNlResponseData *data; + guint32 seq_number; + guint i; - g_return_val_if_fail (ifindex > 0, FALSE); + seq_number = nlmsg_hdr (msg)->nlmsg_seq; - /* Previously, we were using rtnl_link_change(), which builds a request based - * on the diff with an original link instance. - * - * The diff only reused ifi_family, ifi_index, ifi_flags, and name from - * the original link (see rtnl_link_build_change_request()). - * - * We don't do that anymore as we don't have an "orig" netlink instance that - * we can use. Instead the caller must ensure to properly initialize @nlo, - * especially it must set family, ifindex (or ifname) and flags. - * ifname should be set *only* if the caller wishes to change the name. - * - * @complete_from_cache is a convenience to copy the link flags over the link inside - * the platform cache. */ - - if (*complete_from_cache) { - const NMPObject *obj_cache; + if (seq_number == 0) + return; - obj_cache = nmp_cache_lookup_link (priv->cache, ifindex); - if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) { - _LOGt ("kernel-change-link: failure changing link %d: cannot complete link", ifindex); - *complete_from_cache = FALSE; - return -NLE_INVAL; + 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 seqence 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; + return; + } } - - rtnl_link_set_flags (nlo, obj_cache->link.flags); - - /* If the caller wants to rename the link, he should explicitly set - * rtnl_link_set_name(). In all other cases, it should leave the name - * unset. Unfortunately, there is not public API in libnl to modify the - * attribute mask and clear (link->ce_mask = ~LINK_ATTR_IFNAME), so we - * require the caller to do the right thing -- i.e. don't set the name. - */ - } - - /* We don't use rtnl_link_change() because we have no original rtnl_link object - * at hand. We also don't use rtnl_link_add() because that doesn't have the - * hack to retry with RTM_SETLINK. Reimplement a mix of both. */ - - nle = rtnl_link_build_add_request (nlo, nlflags, &msg); - if (nle < 0) { - _LOGt ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)", - ifindex, nl_geterror (nle), -nle); - return nle; } -retry: - nle = nl_send_auto_complete (priv->nlh, msg); - if (nle < 0) - goto errout; - - nle = nl_wait_for_ack(priv->nlh); - if (nle == -NLE_OPNOTSUPP && nlmsg_hdr (msg)->nlmsg_type == RTM_NEWLINK) { - nlmsg_hdr (msg)->nlmsg_type = RTM_SETLINK; - goto retry; - } - -errout: - nlmsg_free(msg); - - /* NLE_EXIST is considered equivalent to success to avoid race conditions. You - * never know when something sends an identical object just before - * NetworkManager. - * - * When netlink returns NLE_OBJ_NOTFOUND, it usually means it failed to find - * firmware for the device, especially on nm_platform_link_set_up (). - * This is basically the same check as in the original code and could - * potentially be improved. - */ - switch (nle) { - case -NLE_SUCCESS: - _LOGt ("kernel-change-link: success changing link %d", ifindex); - break; - case -NLE_EXIST: - _LOGt ("kernel-change-link: success changing link %d: %s (%d)", - ifindex, nl_geterror (nle), -nle); - break; - case -NLE_OBJ_NOTFOUND: - _LOGt ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)", - ifindex, nl_geterror (nle), -nle); - break; - default: - _LOGt ("kernel-change-link: failure changing link %d: netlink error (%s, %d)", - ifindex, nl_geterror (nle), -nle); - break; - } - - return nle; + if (seq_number != priv->nlh_seq_last_handled) + _LOGt ("netlink: recvmsg: unwaited sequence number %u", seq_number); + priv->nlh_seq_last_handled = seq_number; } static void -ref_object (struct nl_object *obj, void *data) -{ - struct nl_object **out = data; - - nl_object_get (obj); - *out = obj; -} - -static int -event_seq_check (struct nl_msg *msg, gpointer user_data) +event_valid_msg (NMPlatform *platform, struct nl_msg *msg) { - NMPlatform *platform = NM_PLATFORM (user_data); NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - struct nlmsghdr *hdr; - - hdr = nlmsg_hdr (msg); - - if (hdr->nlmsg_seq == 0) - return NL_OK; - - priv->nlh_seq_last = hdr->nlmsg_seq; - - if (priv->nlh_seq_expect == 0) - _LOGt ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq); - else if (hdr->nlmsg_seq == priv->nlh_seq_expect) { - _LOGt ("event_seq_check(): seq %u received", hdr->nlmsg_seq); - - priv->nlh_seq_expect = 0; - } else - _LOGt ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last); - - return NL_OK; -} - -static int -event_err (struct sockaddr_nl *nla, struct nlmsgerr *nlerr, gpointer platform) -{ - _LOGt ("event_err(): error from kernel: %s (%d) for request %d", - strerror (nlerr ? -nlerr->error : 0), - nlerr ? -nlerr->error : 0, - NM_LINUX_PLATFORM_GET_PRIVATE (platform)->nlh_seq_last); - return NL_OK; -} - -/* This function does all the magic to avoid race conditions caused - * by concurrent usage of synchronous commands and an asynchronous cache. This - * might be a nice future addition to libnl but it requires to do all operations - * through the cache manager. In this case, nm-linux-platform serves as the - * cache manager instead of the one provided by libnl. - */ -static int -event_notification (struct nl_msg *msg, gpointer user_data) -{ - NMPlatform *platform = NM_PLATFORM (user_data); - auto_nl_object struct nl_object *nlo = NULL; - auto_nmp_obj NMPObject *obj = NULL; + nm_auto_nmpobj NMPObject *obj = NULL; + nm_auto_nmpobj NMPObject *obj_cache = NULL; + NMPCacheOpsType cache_op; struct nlmsghdr *msghdr; char buf_nlmsg_type[16]; + gboolean id_only = FALSE; + gboolean was_visible; msghdr = nlmsg_hdr (msg); if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR) _support_kernel_extended_ifa_flags_detect (msg); - nl_msg_parse (msg, ref_object, &nlo); - if (!nlo) - return NL_OK; - - if (_support_user_ipv6ll_still_undecided() && msghdr->nlmsg_type == RTM_NEWLINK) - _support_user_ipv6ll_detect ((struct rtnl_link *) nlo); - - switch (msghdr->nlmsg_type) { - case RTM_DELADDR: - case RTM_DELLINK: - case RTM_DELROUTE: - /* The event notifies about a deleted object. We don't need to initialize all the - * fields of the nmp-object. Shortcut nmp_object_from_nl(). */ - obj = nmp_object_from_nl (platform, nlo, TRUE, TRUE); - _LOGT ("event-notification: %s, seq %u: %s", - _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)), - msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0)); - break; - default: - obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE); - _LOGT ("event-notification: %s, seq %u: %s", - _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)), - msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); - break; - } - - if (obj) { - auto_nmp_obj NMPObject *obj_cache = NULL; - - switch (msghdr->nlmsg_type) { - - case RTM_NEWLINK: - case RTM_NEWADDR: - case RTM_NEWROUTE: - cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL); - break; - - case RTM_DELLINK: - case RTM_DELADDR: - case RTM_DELROUTE: - cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL); - break; - - default: - break; - } - - cache_prune_candidates_drop (platform, obj_cache); + if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) { + /* The event notifies about a deleted object. We don't need to initialize all + * fields of the object. */ + id_only = TRUE; } - return NL_OK; -} - -/******************************************************************/ - -static void -_log_dbg_sysctl_set_impl (const char *path, const char *value) -{ - GError *error = NULL; - char *contents, *contents_escaped; - char *value_escaped = g_strescape (value, NULL); - - if (!g_file_get_contents (path, &contents, NULL, &error)) { - debug ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message); - g_clear_error (&error); - } else { - g_strstrip (contents); - contents_escaped = g_strescape (contents, NULL); - if (strcmp (contents, value) == 0) - debug ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped); - else - debug ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped); - g_free (contents); - g_free (contents_escaped); - } - g_free (value_escaped); -} - -#define _log_dbg_sysctl_set(path, value) \ - G_STMT_START { \ - if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \ - _log_dbg_sysctl_set_impl (path, value); \ - } \ - } G_STMT_END - -static gboolean -sysctl_set (NMPlatform *platform, const char *path, const char *value) -{ - int fd, len, nwrote, tries; - char *actual; - - g_return_val_if_fail (path != NULL, FALSE); - g_return_val_if_fail (value != NULL, FALSE); - - /* Don't write outside known locations */ - g_assert (g_str_has_prefix (path, "/proc/sys/") - || g_str_has_prefix (path, "/sys/")); - /* Don't write to suspicious locations */ - g_assert (!strstr (path, "/../")); - - fd = open (path, O_WRONLY | O_TRUNC); - if (fd == -1) { - if (errno == ENOENT) { - debug ("sysctl: failed to open '%s': (%d) %s", - path, errno, strerror (errno)); - } else { - error ("sysctl: failed to open '%s': (%d) %s", - path, errno, strerror (errno)); - } - return FALSE; + obj = nmp_object_new_from_nl (platform, priv->cache, msg, id_only); + if (!obj) { + _LOGT ("event-notification: %s, seq %u: ignore", + _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)), + msghdr->nlmsg_seq); + return; } - _log_dbg_sysctl_set (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. - */ - actual = g_strdup_printf ("%s\n", value); - - /* Try to write the entire value three times if a partial write occurs */ - len = strlen (actual); - for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) { - nwrote = write (fd, actual, len); - if (nwrote == -1) { - if (errno == EINTR) { - debug ("sysctl: interrupted, will try again"); - continue; - } - break; - } - } - if (nwrote == -1 && errno != EEXIST) { - error ("sysctl: failed to set '%s' to '%s': (%d) %s", - path, value, errno, strerror (errno)); - } else if (nwrote < len) { - error ("sysctl: failed to set '%s' to '%s' after three attempts", - path, value); - } + _LOGT ("event-notification: %s, seq %u: %s", + _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)), + msghdr->nlmsg_seq, nmp_object_to_string (obj, + id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); - g_free (actual); - close (fd); - return (nwrote == len); -} - -static GHashTable *sysctl_get_prev_values; - -static void -_log_dbg_sysctl_get_impl (const char *path, const char *contents) -{ - const char *prev_value = NULL; - - if (!sysctl_get_prev_values) - sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - else - prev_value = g_hash_table_lookup (sysctl_get_prev_values, path); - - if (prev_value) { - if (strcmp (prev_value, contents) != 0) { - char *contents_escaped = g_strescape (contents, NULL); - char *prev_value_escaped = g_strescape (prev_value, NULL); - - debug ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped); - g_free (contents_escaped); - g_free (prev_value_escaped); - g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents)); - } - } else { - char *contents_escaped = g_strescape (contents, NULL); - - debug ("sysctl: reading '%s': '%s'", path, contents_escaped); - g_free (contents_escaped); - g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents)); - } -} - -#define _log_dbg_sysctl_get(path, contents) \ - G_STMT_START { \ - if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \ - _log_dbg_sysctl_get_impl (path, contents); \ - } else if (sysctl_get_prev_values) { \ - g_hash_table_destroy (sysctl_get_prev_values); \ - sysctl_get_prev_values = NULL; \ - } \ - } G_STMT_END + switch (msghdr->nlmsg_type) { -static char * -sysctl_get (NMPlatform *platform, const char *path) -{ - GError *error = NULL; - char *contents; + case RTM_NEWLINK: + case RTM_NEWADDR: + case RTM_NEWROUTE: + cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform); + do_emit_signal (platform, obj_cache, cache_op, was_visible); + break; - /* Don't write outside known locations */ - g_assert (g_str_has_prefix (path, "/proc/sys/") - || g_str_has_prefix (path, "/sys/")); - /* Don't write to suspicious locations */ - g_assert (!strstr (path, "/../")); + case RTM_DELLINK: + case RTM_DELADDR: + case RTM_DELROUTE: + cache_op = nmp_cache_remove_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform); + do_emit_signal (platform, obj_cache, cache_op, was_visible); + break; - if (!g_file_get_contents (path, &contents, NULL, &error)) { - /* We assume FAILED means EOPNOTSUP */ - if ( g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) - || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) - debug ("error reading %s: %s", path, error->message); - else - error ("error reading %s: %s", path, error->message); - g_clear_error (&error); - return NULL; + default: + break; } - g_strstrip (contents); - - _log_dbg_sysctl_get (path, contents); - - return contents; + cache_prune_candidates_drop (platform, obj_cache); } /******************************************************************/ @@ -2647,195 +3592,261 @@ _nm_platform_link_get_by_address (NMPlatform *platform, return obj ? &obj->link : NULL; } -static struct nl_object * -build_rtnl_link (int ifindex, const char *name, NMLinkType type) +/*****************************************************************************/ + +static const NMPObject * +link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link) { - struct rtnl_link *rtnllink; - int nle; + const NMPObject *obj = cache_lookup_link (platform, ifindex); - rtnllink = _nl_rtnl_link_alloc (ifindex, name); - if (type) { - nle = rtnl_link_set_type (rtnllink, nm_link_type_to_rtnl_type_string (type)); - g_assert (!nle); - } - return (struct nl_object *) rtnllink; -} + if (!obj) + return NULL; -struct nl_object * -_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) -{ - const NMPlatformLink *obj = (const NMPlatformLink *) _obj; + 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 build_rtnl_link (obj->ifindex, - obj->name[0] ? obj->name : NULL, - obj->type); + return obj->_link.netlink.lnk; } +/*****************************************************************************/ + static gboolean -do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo) +do_add_link_with_lookup (NMPlatform *platform, + NMLinkType link_type, + const char *name, + struct nl_msg *nlmsg, + const NMPlatformLink **out_link) { - NMPObject obj_needle; + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + const NMPObject *obj = NULL; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; int nle; + char s_buf[256]; + + event_handler_read_netlink (platform, FALSE); - event_handler_read_netlink_all (platform, FALSE); + if (nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) { + /* hm, a link with such a name already exists. Try reloading first. */ + do_request_link (platform, 0, name); - nle = kernel_add_object (platform, NMP_OBJECT_TYPE_LINK, (const struct nl_object *) nlo); + obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL); + if (obj) { + _LOGE ("do-add-link[%s/%s]: link already exists: %s", + name, + nm_link_type_to_string (link_type), + nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0)); + return FALSE; + } + } + + nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result); if (nle < 0) { - _LOGE ("do-add-link: failure adding link '%s': %s", name, nl_geterror (nle)); + _LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)", + name, + nm_link_type_to_string (link_type), + nl_geterror (nle), -nle); return FALSE; } - _LOGD ("do-add-link: success adding link '%s'", name); - nmp_object_stackinit_id_link (&obj_needle, 0); - g_strlcpy (obj_needle.link.name, name, sizeof (obj_needle.link.name)); + delayed_action_handle_all (platform, FALSE); - delayed_action_handle_all (platform, TRUE); + nm_assert (seq_result); - /* FIXME: we add the link object via the second netlink socket. Sometimes, - * the notification is not yet ready via nlh_event, so we have to re-request the - * link so that it is in the cache. A better solution would be to do everything - * via one netlink socket. */ - if (!nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, 0, obj_needle.link.name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) { - _LOGt ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name); - do_request_link (platform, 0, obj_needle.link.name, TRUE); - } + _NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK + ? LOGL_DEBUG + : LOGL_ERR, + "do-add-link[%s/%s]: %s", + name, + nm_link_type_to_string (link_type), + wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf))); - /* Return true, because kernel_add_object() succeeded. This doesn't indicate that the - * object is now actuall in the cache, because there could be a race. - * - * For that, you'd have to look at @out_obj. */ - return TRUE; -} - -static gboolean -do_add_link_with_lookup (NMPlatform *platform, const char *name, const struct rtnl_link *nlo, NMLinkType expected_link_type, NMPlatformLink *out_link) -{ - const NMPObject *obj; + if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) + obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL); - do_add_link (platform, name, nlo); + if (!obj) { + /* either kernel signaled failure, or it signaled success and the link object + * is not (yet) in the cache. Try to reload it... */ + do_request_link (platform, 0, name); + obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL); + } - obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, - 0, name, FALSE, expected_link_type, NULL, NULL); - if (out_link && obj) - *out_link = obj->link; + if (out_link) + *out_link = obj ? &obj->link : NULL; return !!obj; } static gboolean -do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo) +do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg) { + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; int nle; + char s_buf[256]; + const NMPObject *obj; 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_all (platform, FALSE); + event_handler_read_netlink (platform, FALSE); - nle = kernel_add_object (platform, NMP_OBJECT_GET_CLASS (obj_id)->obj_type, (const struct nl_object *) nlo); + nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result); if (nle < 0) { - _LOGW ("do-add-%s: failure adding %s '%s': %s (%d)", - NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, + _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), nl_geterror (nle), -nle); return FALSE; } - _LOGD ("do-add-%s: success adding object %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0)); - delayed_action_handle_all (platform, TRUE); + delayed_action_handle_all (platform, FALSE); + + nm_assert (seq_result); + + _NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK + ? LOGL_DEBUG + : LOGL_ERR, + "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, s_buf, sizeof (s_buf))); - /* FIXME: instead of re-requesting the added object, add it via nlh_event - * so that the events are in sync. */ - if (!nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj_id)) { - _LOGt ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0)); - do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE); + /* 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. + * + * FIXME: if the object already existed previously, we might not notice a + * missing update. It's not clear how to fix that reliably without refechting + * all the time. */ + obj = nmp_cache_lookup_obj (priv->cache, obj_id); + if (!obj) { + do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id)); + obj = nmp_cache_lookup_obj (priv->cache, obj_id); } - /* The return value doesn't say, whether the object is in the platform cache after adding - * it. - * Instead the return value says, whether kernel_add_object() succeeded. */ - return TRUE; + /* Adding is only successful, if kernel reported success *and* we have the + * expected object in cache afterwards. */ + return obj && seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; } - static gboolean -do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo) +do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - auto_nl_object struct nl_object *nlo_free = NULL; + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; int nle; + char s_buf[256]; + gboolean success = TRUE; + const char *log_detail = ""; - event_handler_read_netlink_all (platform, FALSE); + event_handler_read_netlink (platform, FALSE); - if (!nlo) - nlo = nlo_free = nmp_object_to_nl (platform, obj_id, FALSE); + nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result); + 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), + nl_geterror (nle), -nle); + goto out; + } - nle = kernel_delete_object (platform, NMP_OBJECT_GET_TYPE (obj_id), nlo); - if (nle < 0) - _LOGE ("do-delete-%s: failure deleting '%s': %s (%d)", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), nl_geterror (nle), -nle); - else - _LOGD ("do-delete-%s: success deleting '%s'", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0)); + delayed_action_handle_all (platform, FALSE); - delayed_action_handle_all (platform, TRUE); + nm_assert (seq_result); + + 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; - /* FIXME: instead of re-requesting the deleted object, add it via nlh_event - * so that the events are in sync. */ - if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_LINK) { - const NMPObject *obj; + _NMLOG (success ? LOGL_DEBUG : LOGL_ERR, + "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, s_buf, sizeof (s_buf)), + log_detail); - obj = nmp_cache_lookup_link_full (priv->cache, obj_id->link.ifindex, obj_id->link.ifindex <= 0 && obj_id->link.name[0] ? obj_id->link.name : NULL, FALSE, NM_LINK_TYPE_NONE, NULL, NULL); - if (obj && obj->_link.netlink.is_in_netlink) { - _LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0)); - do_request_link (platform, obj_id->link.ifindex, obj_id->link.name, TRUE); - } - } else { - if (nmp_cache_lookup_obj (priv->cache, obj_id)) { - _LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0)); - do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE); - } - } +out: + if (!nmp_cache_lookup_obj (priv->cache, obj_id)) + return TRUE; - /* The return value doesn't say, whether the object is in the platform cache after adding - * it. - * Instead the return value says, whether kernel_add_object() succeeded. */ - return nle >= 0; + /* such an object still exists in the cache. To be sure, refetch it (and + * hope it's gone) */ + do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id)); + return !!nmp_cache_lookup_obj (priv->cache, obj_id); } static NMPlatformError -do_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean complete_from_cache) +do_change_link (NMPlatform *platform, + int ifindex, + struct nl_msg *nlmsg) { + WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; int nle; - int ifindex; - gboolean complete_from_cache2 = complete_from_cache; + char s_buf[256]; + NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS; + NMLogLevel log_level = LOGL_DEBUG; + const char *log_result = "failure", *log_detail = ""; - ifindex = rtnl_link_get_ifindex (nlo); - if (ifindex <= 0) - g_return_val_if_reached (NM_PLATFORM_ERROR_BUG); +retry: + nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result); + if (nle < 0) { + _LOGE ("do-change-link[%d]: failure sending netlink request \"%s\" (%d)", + ifindex, + nl_geterror (nle), -nle); + return NM_PLATFORM_ERROR_UNSPECIFIED; + } - nle = kernel_change_link (platform, nlo, &complete_from_cache2); + /* 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)); - switch (nle) { - case -NLE_SUCCESS: - _LOGD ("do-change-link: success changing link %d", ifindex); - break; - case -NLE_EXIST: - _LOGD ("do-change-link: success changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle); - break; - case -NLE_OBJ_NOTFOUND: - /* fall-through */ - default: - if (complete_from_cache != complete_from_cache2) - _LOGD ("do-change-link: failure changing link %d: link does not exist in cache", ifindex); - else - _LOGE ("do-change-link: failure changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle); - return nle == -NLE_OBJ_NOTFOUND ? NM_PLATFORM_ERROR_NO_FIRMWARE : NM_PLATFORM_ERROR_UNSPECIFIED; + 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 = NM_PLATFORM_ERROR_NO_FIRMWARE; + } else { + log_level = LOGL_ERR; + result = NM_PLATFORM_ERROR_UNSPECIFIED; } + _NMLOG (log_level, + "do-change-link[%d]: %s changing link: %s%s", + ifindex, + log_result, + wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)), + log_detail); - /* FIXME: as we modify the link via a separate socket, the cache is not in - * sync and we have to refetch the link. */ - do_request_link (platform, ifindex, NULL, TRUE); - return NM_PLATFORM_ERROR_SUCCESS; + return result; } static gboolean @@ -2844,9 +3855,9 @@ link_add (NMPlatform *platform, NMLinkType type, const void *address, size_t address_len, - NMPlatformLink *out_link) + const NMPlatformLink **out_link) { - auto_nl_object struct nl_object *l = NULL; + 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 @@ -2860,34 +3871,50 @@ link_add (NMPlatform *platform, nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL); } - debug ("link: add link '%s' of type '%s' (%d)", + _LOGD ("link: add link '%s' of type '%s' (%d)", name, nm_link_type_to_string (type), (int) type); - l = build_rtnl_link (0, name, type); + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; - g_assert ( (address != NULL) ^ (address_len == 0) ); - if (address) { - auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, address_len); + if (address && address_len) + NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address); - rtnl_link_set_addr ((struct rtnl_link *) l, nladdr); - } + if (!_nl_msg_new_link_set_linkinfo (nlmsg, type)) + return FALSE; - return do_add_link_with_lookup (platform, name, (struct rtnl_link *) l, type, out_link); + return do_add_link_with_lookup (platform, type, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } static gboolean link_delete (NMPlatform *platform, int ifindex) { + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - NMPObject obj_needle; + NMPObject obj_id; const NMPObject *obj; obj = nmp_cache_lookup_link (priv->cache, ifindex); if (!obj || !obj->_link.netlink.is_in_netlink) return FALSE; - nmp_object_stackinit_id_link (&obj_needle, ifindex); - return do_delete_object (platform, &obj_needle, NULL); + nlmsg = _nl_msg_new_link (RTM_DELLINK, + 0, + ifindex, + NULL, + 0, + 0); + + nmp_object_stackinit_id_link (&obj_id, ifindex); + return do_delete_object (platform, &obj_id, nlmsg); } static const char * @@ -2933,33 +3960,35 @@ link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged) static gboolean link_refresh (NMPlatform *platform, int ifindex) { - do_request_link (platform, ifindex, NULL, TRUE); + do_request_link (platform, ifindex, NULL); return !!cache_lookup_link (platform, ifindex); } static NMPlatformError -link_change_flags (NMPlatform *platform, int ifindex, unsigned int flags, gboolean value) -{ - auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL); - const NMPObject *obj_cache; - char buf[256]; - - obj_cache = cache_lookup_link (platform, ifindex); - if (!obj_cache) - return NM_PLATFORM_ERROR_NOT_FOUND; - - rtnl_link_set_flags (change, obj_cache->link.flags); - if (value) - rtnl_link_set_flags (change, flags); - else - rtnl_link_unset_flags (change, flags); - - _LOGD ("link: change %d: flags %s '%s' (%d)", ifindex, - value ? "set" : "unset", - rtnl_link_flags2str (flags, buf, sizeof (buf)), - flags); - - return do_change_link (platform, change, FALSE); +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]; + + _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, NULL, 0)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + flags_mask, + flags_set); + if (!nlmsg) + return NM_PLATFORM_ERROR_UNSPECIFIED; + return do_change_link (platform, ifindex, nlmsg); } static gboolean @@ -2967,7 +3996,7 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware) { NMPlatformError plerr; - plerr = link_change_flags (platform, ifindex, IFF_UP, TRUE); + plerr = link_change_flags (platform, ifindex, IFF_UP, IFF_UP); if (out_no_firmware) *out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE; return plerr == NM_PLATFORM_ERROR_SUCCESS; @@ -2976,19 +4005,19 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware) static gboolean link_set_down (NMPlatform *platform, int ifindex) { - return link_change_flags (platform, ifindex, IFF_UP, FALSE) == NM_PLATFORM_ERROR_SUCCESS; + return link_change_flags (platform, ifindex, IFF_UP, 0) == NM_PLATFORM_ERROR_SUCCESS; } static gboolean link_set_arp (NMPlatform *platform, int ifindex) { - return link_change_flags (platform, ifindex, IFF_NOARP, FALSE) == NM_PLATFORM_ERROR_SUCCESS; + return link_change_flags (platform, ifindex, IFF_NOARP, 0) == NM_PLATFORM_ERROR_SUCCESS; } static gboolean link_set_noarp (NMPlatform *platform, int ifindex) { - return link_change_flags (platform, ifindex, IFF_NOARP, TRUE) == NM_PLATFORM_ERROR_SUCCESS; + return link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) == NM_PLATFORM_ERROR_SUCCESS; } static const char * @@ -3020,19 +4049,30 @@ link_get_udev_device (NMPlatform *platform, int ifindex) static gboolean link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled) { -#if HAVE_LIBNL_INET6_ADDR_GEN_MODE - if (_support_user_ipv6ll_get ()) { - auto_nl_object struct rtnl_link *nlo = _nl_rtnl_link_alloc (ifindex, NULL); - guint8 mode = enabled ? IN6_ADDR_GEN_MODE_NONE : IN6_ADDR_GEN_MODE_EUI64; - char buf[32]; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64; - rtnl_link_inet6_set_addr_gen_mode (nlo, mode); - debug ("link: change %d: set IPv6 address generation mode to %s", - ifindex, rtnl_link_inet6_addrgenmode2str (mode, buf, sizeof (buf))); - return do_change_link (platform, nlo, TRUE) == NM_PLATFORM_ERROR_SUCCESS; + if (!_support_user_ipv6ll_get ()) { + _LOGD ("link: change %d: user-ipv6ll: not supported", ifindex); + return FALSE; } -#endif - return FALSE; + + _LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s", + ifindex, + nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + 0, + 0); + if ( !nlmsg + || !_nl_msg_new_link_set_afspec (nlmsg, + mode)) + g_return_val_if_reached (FALSE); + + return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS; } static gboolean @@ -3067,16 +4107,30 @@ link_supports_vlans (NMPlatform *platform, int ifindex) static gboolean link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length) { - auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL); - auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, length); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; gs_free char *mac = NULL; - rtnl_link_set_addr (change, nladdr); + if (!address || !length) + g_return_val_if_reached (FALSE); - _LOGD ("link: change %d: address %s (%lu bytes)", ifindex, + _LOGD ("link: change %d: address: %s (%lu bytes)", ifindex, (mac = nm_utils_hwaddr_ntoa (address, length)), (unsigned long) length); - return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS; + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + 0, + 0); + if (!nlmsg) + return FALSE; + + NLA_PUT (nlmsg, IFLA_ADDRESS, length, address); + + return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS; +nla_put_failure: + g_return_val_if_reached (FALSE); } static gboolean @@ -3091,12 +4145,24 @@ link_get_permanent_address (NMPlatform *platform, static gboolean link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu) { - auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL); + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; - rtnl_link_set_mtu (change, mtu); - debug ("link: change %d: mtu %lu", ifindex, (unsigned long)mtu); + _LOGD ("link: change %d: mtu: %u", ifindex, (unsigned) mtu); - return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS; + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + 0, + 0); + if (!nlmsg) + return FALSE; + + NLA_PUT_U32 (nlmsg, IFLA_MTU, mtu); + + return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS; +nla_put_failure: + g_return_val_if_reached (FALSE); } static char * @@ -3148,649 +4214,680 @@ vlan_add (NMPlatform *platform, int parent, int vlan_id, guint32 vlan_flags, - NMPlatformLink *out_link) + const NMPlatformLink **out_link) { - auto_nl_object struct rtnl_link *rtnllink = (struct rtnl_link *) build_rtnl_link (0, name, NM_LINK_TYPE_VLAN); - unsigned int kernel_flags; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; - kernel_flags = 0; - if (vlan_flags & NM_VLAN_FLAG_REORDER_HEADERS) - kernel_flags |= VLAN_FLAG_REORDER_HDR; - if (vlan_flags & NM_VLAN_FLAG_GVRP) - kernel_flags |= VLAN_FLAG_GVRP; - if (vlan_flags & NM_VLAN_FLAG_LOOSE_BINDING) - kernel_flags |= VLAN_FLAG_LOOSE_BINDING; + 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); - rtnl_link_set_link (rtnllink, parent); - rtnl_link_vlan_set_id (rtnllink, vlan_id); - rtnl_link_vlan_set_flags (rtnllink, kernel_flags); + vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL; - debug ("link: add vlan '%s', parent %d, vlan id %d, flags %X (native: %X)", - name, parent, vlan_id, (unsigned int) vlan_flags, kernel_flags); + _LOGD ("link: add vlan '%s', parent %d, vlan id %d, flags %X", + name, parent, vlan_id, (unsigned int) vlan_flags); - return do_add_link_with_lookup (platform, name, rtnllink, NM_LINK_TYPE_VLAN, out_link); -} - -static gboolean -vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id) -{ - const NMPObject *obj = cache_lookup_link (platform, ifindex); - int p = 0, v = 0; - - if (obj) { - p = obj->link.parent; - v = obj->link.vlan_id; - } - if (parent) - *parent = p; - if (vlan_id) - *vlan_id = v; - return !!obj; -} - -static gboolean -vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to) -{ - auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN); - - rtnl_link_set_type (change, "vlan"); - rtnl_link_vlan_set_ingress_map (change, from, to); - - debug ("link: change %d: vlan ingress map %d -> %d", ifindex, from, to); - - return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS; -} - -static gboolean -vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to) -{ - auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN); + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; - rtnl_link_set_type (change, "vlan"); - rtnl_link_vlan_set_egress_map (change, from, to); + NLA_PUT_U32 (nlmsg, IFLA_LINK, parent); - debug ("link: change %d: vlan egress map %d -> %d", ifindex, from, to); + if (!_nl_msg_new_link_set_linkinfo_vlan (nlmsg, + vlan_id, + NM_VLAN_FLAGS_ALL, + vlan_flags, + NULL, + 0, + NULL, + 0)) + return FALSE; - return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS; + return do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -static gboolean -link_enslave (NMPlatform *platform, int master, int slave) -{ - auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (slave, NULL); +static int +link_gre_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkGre *props, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + char buffer[INET_ADDRSTRLEN]; + + _LOGD (LOG_FMT_IP_TUNNEL, + "gre", + name, + props->parent_ifindex, + nm_utils_inet4_ntop (props->local, NULL), + nm_utils_inet4_ntop (props->remote, buffer)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; - rtnl_link_set_master (change, master); - debug ("link: change %d: enslave to master %d", slave, master); + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; - return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS; -} + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "gre"); -static gboolean -link_release (NMPlatform *platform, int master, int slave) -{ - return link_enslave (platform, 0, slave); -} + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; -static char * -link_option_path (NMPlatform *platform, int master, const char *category, const char *option) -{ - const char *name = nm_platform_link_get_name (platform, master); + if (props->parent_ifindex) + NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex); + NLA_PUT_U32 (nlmsg, IFLA_GRE_LOCAL, props->local); + NLA_PUT_U32 (nlmsg, IFLA_GRE_REMOTE, props->remote); + NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl); + NLA_PUT_U8 (nlmsg, IFLA_GRE_TOS, props->tos); + NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery); + NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key)); + NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key)); + NLA_PUT_U32 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags)); + NLA_PUT_U32 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags)); - if (!name || !category || !option) - return NULL; + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); - return g_strdup_printf ("/sys/class/net/%s/%s/%s", - ASSERT_VALID_PATH_COMPONENT (name), - ASSERT_VALID_PATH_COMPONENT (category), - ASSERT_VALID_PATH_COMPONENT (option)); + return do_add_link_with_lookup (platform, NM_LINK_TYPE_GRE, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -static gboolean -link_set_option (NMPlatform *platform, int master, const char *category, const char *option, const char *value) -{ - gs_free char *path = link_option_path (platform, master, category, option); +static int +link_ip6tnl_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkIp6Tnl *props, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + char buffer[INET_ADDRSTRLEN]; + guint32 flowinfo; + + _LOGD (LOG_FMT_IP_TUNNEL, + "ip6tnl", + name, + props->parent_ifindex, + nm_utils_inet6_ntop (&props->local, NULL), + nm_utils_inet6_ntop (&props->remote, buffer)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; - return path && nm_platform_sysctl_set (platform, path, value); -} + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; -static char * -link_get_option (NMPlatform *platform, int master, const char *category, const char *option) -{ - gs_free char *path = link_option_path (platform, master, category, option); + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ip6tnl"); - return path ? nm_platform_sysctl_get (platform, path) : NULL; -} + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; -static const char * -master_category (NMPlatform *platform, int master) -{ - switch (nm_platform_link_get_type (platform, master)) { - case NM_LINK_TYPE_BRIDGE: - return "bridge"; - case NM_LINK_TYPE_BOND: - return "bonding"; - default: - return NULL; - } -} + if (props->parent_ifindex) + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex); -static const char * -slave_category (NMPlatform *platform, int slave) -{ - int master = nm_platform_link_get_master (platform, slave); + if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any))) + NLA_PUT (nlmsg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local); + if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any))) + NLA_PUT (nlmsg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote); - if (master <= 0) - return NULL; + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit); - switch (nm_platform_link_get_type (platform, master)) { - case NM_LINK_TYPE_BRIDGE: - return "brport"; - default: - return NULL; - } -} + flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK; + flowinfo |= (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT) + & IP6_FLOWINFO_TCLASS_MASK; + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo)); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto); -static gboolean -master_set_option (NMPlatform *platform, int master, const char *option, const char *value) -{ - return link_set_option (platform, master, master_category (platform, master), option, value); -} + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); -static char * -master_get_option (NMPlatform *platform, int master, const char *option) -{ - return link_get_option (platform, master, master_category (platform, master), option); + return do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -static gboolean -slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value) -{ - return link_set_option (platform, slave, slave_category (platform, slave), option, value); -} +static int +link_ipip_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkIpIp *props, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + char buffer[INET_ADDRSTRLEN]; + + _LOGD (LOG_FMT_IP_TUNNEL, + "ipip", + name, + props->parent_ifindex, + nm_utils_inet4_ntop (props->local, NULL), + nm_utils_inet4_ntop (props->remote, buffer)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; -static char * -slave_get_option (NMPlatform *platform, int slave, const char *option) -{ - return link_get_option (platform, slave, slave_category (platform, slave), option); -} + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; -static gboolean -infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link) -{ - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - const NMPObject *obj_parent; - const NMPObject *obj; - gs_free char *path = NULL; - gs_free char *id = NULL; - gs_free char *ifname = NULL; + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ipip"); - obj_parent = nmp_cache_lookup_link (priv->cache, parent); - if (!obj_parent || !obj_parent->link.name[0]) - g_return_val_if_reached (FALSE); + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; - ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key); + if (props->parent_ifindex) + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); - path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name)); - id = g_strdup_printf ("0x%04x", p_key); - if (!nm_platform_sysctl_set (platform, path, id)) - return FALSE; + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); - do_request_link (platform, 0, ifname, TRUE); - - obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, - 0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL); - if (out_link && obj) - *out_link = obj->link; - return !!obj; + return do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -typedef struct { - int p_key; - const char *mode; -} IpoibInfo; - -/* 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 +static int +link_macvlan_add (NMPlatform *platform, + const char *name, + int parent, + const NMPlatformLnkMacvlan *props, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + + _LOGD ("adding %s '%s' parent %u mode %u", + props->tap ? "macvtap" : "macvlan", + name, + parent, + props->mode); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) + return FALSE; -#define IPOIB_MODE_DATAGRAM 0 /* using unreliable datagram QPs */ -#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */ + NLA_PUT_U32 (nlmsg, IFLA_LINK, parent); -static const struct nla_policy infiniband_info_policy[IFLA_IPOIB_MAX + 1] = { - [IFLA_IPOIB_PKEY] = { .type = NLA_U16 }, - [IFLA_IPOIB_MODE] = { .type = NLA_U16 }, - [IFLA_IPOIB_UMCAST] = { .type = NLA_U16 }, -}; + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; -static int -infiniband_info_data_parser (struct nlattr *info_data, gpointer parser_data) -{ - IpoibInfo *info = parser_data; - struct nlattr *tb[IFLA_MACVLAN_MAX + 1]; - int err; + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->tap ? "macvtap" : "macvlan"); - err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, - (struct nla_policy *) infiniband_info_policy); - if (err < 0) - return err; - if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE]) - return -EINVAL; + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; - info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]); + NLA_PUT_U32 (nlmsg, IFLA_MACVLAN_MODE, props->mode); + NLA_PUT_U16 (nlmsg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0); - switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) { - case IPOIB_MODE_DATAGRAM: - info->mode = "datagram"; - break; - case IPOIB_MODE_CONNECTED: - info->mode = "connected"; - break; - default: - return -NLE_PARSE_ERR; - } + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); - return 0; + return do_add_link_with_lookup (platform, + props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN, + name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -static gboolean -infiniband_get_info (NMPlatform *platform, int ifindex, int *parent, int *p_key, const char **mode) -{ - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - const NMPObject *obj; - IpoibInfo info = { -1, NULL }; - - obj = cache_lookup_link (platform, ifindex); - if (!obj) +static int +link_sit_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkSit *props, + const NMPlatformLink **out_link) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + char buffer[INET_ADDRSTRLEN]; + + _LOGD (LOG_FMT_IP_TUNNEL, + "sit", + name, + props->parent_ifindex, + nm_utils_inet4_ntop (props->local, NULL), + nm_utils_inet4_ntop (props->remote, buffer)); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) return FALSE; - if (parent) - *parent = obj->link.parent; - - if (_nl_link_parse_info_data (priv->nlh, - ifindex, - infiniband_info_data_parser, - &info) != 0) { - const char *iface = obj->link.name; - char *path, *contents = NULL; - - /* Fall back to reading sysfs */ - path = g_strdup_printf ("/sys/class/net/%s/mode", ASSERT_VALID_PATH_COMPONENT (iface)); - contents = nm_platform_sysctl_get (platform, path); - g_free (path); - if (!contents) - return FALSE; + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; - if (strstr (contents, "datagram")) - info.mode = "datagram"; - else if (strstr (contents, "connected")) - info.mode = "connected"; - g_free (contents); + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "sit"); - path = g_strdup_printf ("/sys/class/net/%s/pkey", ASSERT_VALID_PATH_COMPONENT (iface)); - contents = nm_platform_sysctl_get (platform, path); - g_free (path); - if (!contents) - return FALSE; + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; - info.p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1); - g_free (contents); + if (props->parent_ifindex) + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex); + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local); + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos); + NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery); - if (info.p_key < 0) - return FALSE; - } + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); - if (p_key) - *p_key = info.p_key; - if (mode) - *mode = info.mode; - return TRUE; + return do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } static gboolean -veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props) +link_vxlan_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkVxlan *props, + const NMPlatformLink **out_link) { - const char *ifname; - int peer_ifindex; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nlattr *info; + struct nlattr *data; + struct nm_ifla_vxlan_port_range port_range; - ifname = nm_platform_link_get_name (platform, ifindex); - if (!ifname) - return FALSE; + g_return_val_if_fail (props, FALSE); + + _LOGD ("link: add vxlan '%s', parent %d, vxlan id %d", + name, props->parent_ifindex, props->id); - peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (ifname); - if (peer_ifindex <= 0) + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + NLM_F_CREATE, + 0, + name, + 0, + 0); + if (!nlmsg) return FALSE; - props->peer = peer_ifindex; - return TRUE; -} + if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO))) + goto nla_put_failure; -static gboolean -tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props) -{ - char *path, *val; - gboolean success = TRUE; + NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "vxlan"); - g_return_val_if_fail (props, FALSE); + if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA))) + goto nla_put_failure; - memset (props, 0, sizeof (*props)); - props->owner = -1; - props->group = -1; + NLA_PUT_U32 (nlmsg, IFLA_VXLAN_ID, props->id); - if (!ifname || !nm_utils_iface_valid_name (ifname)) - return FALSE; - ifname = ASSERT_VALID_PATH_COMPONENT (ifname); + if (props->group) + NLA_PUT (nlmsg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group); + else if (memcmp (&props->group6, &in6addr_any, sizeof (in6addr_any))) + NLA_PUT (nlmsg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6); - path = g_strdup_printf ("/sys/class/net/%s/owner", ifname); - val = nm_platform_sysctl_get (platform, path); - g_free (path); - if (val) { - props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); - if (errno) - success = FALSE; - g_free (val); - } else - success = FALSE; + if (props->local) + NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local); + else if (memcmp (&props->local6, &in6addr_any, sizeof (in6addr_any))) + NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6); - path = g_strdup_printf ("/sys/class/net/%s/group", ifname); - val = nm_platform_sysctl_get (platform, path); - g_free (path); - if (val) { - props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1); - if (errno) - success = FALSE; - g_free (val); - } else - success = FALSE; + if (props->parent_ifindex >= 0) + NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LINK, props->parent_ifindex); - path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname); - val = nm_platform_sysctl_get (platform, path); - g_free (path); - if (val) { - gint64 flags; + if (props->src_port_min || props->src_port_max) { + port_range.low = htons (props->src_port_min); + port_range.high = htons (props->src_port_max); + NLA_PUT (nlmsg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range); + } - flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0); - if (!errno) { -#ifndef IFF_MULTI_QUEUE - const int IFF_MULTI_QUEUE = 0x0100; -#endif - props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap"; - props->no_pi = !!(flags & IFF_NO_PI); - props->vnet_hdr = !!(flags & IFF_VNET_HDR); - props->multi_queue = !!(flags & IFF_MULTI_QUEUE); - } else - success = FALSE; - g_free (val); - } else - success = FALSE; + NLA_PUT_U16 (nlmsg, IFLA_VXLAN_PORT, htons (props->dst_port)); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TOS, props->tos); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TTL, props->ttl); + NLA_PUT_U32 (nlmsg, IFLA_VXLAN_AGEING, props->ageing); + NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LIMIT, props->limit); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_LEARNING, !!props->learning); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_PROXY, !!props->proxy); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_RSC, !!props->rsc); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L2MISS, !!props->l2miss); + NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L3MISS, !!props->l3miss); - return success; -} + nla_nest_end (nlmsg, data); + nla_nest_end (nlmsg, info); -static gboolean -tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props) -{ - return tun_get_properties_ifname (platform, nm_platform_link_get_name (platform, ifindex), props); + return do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link); +nla_put_failure: + g_return_val_if_reached (FALSE); } -static const struct nla_policy macvlan_info_policy[IFLA_MACVLAN_MAX + 1] = { - [IFLA_MACVLAN_MODE] = { .type = NLA_U32 }, -#ifdef MACVLAN_FLAG_NOPROMISC - [IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 }, -#endif -}; - -static int -macvlan_info_data_parser (struct nlattr *info_data, gpointer parser_data) -{ - NMPlatformMacvlanProperties *props = parser_data; - struct nlattr *tb[IFLA_MACVLAN_MAX + 1]; - int err; +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; - err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, - (struct nla_policy *) macvlan_info_policy); - if (err < 0) - return err; + map = g_new (NMVlanQosMapping, len); - switch (nla_get_u32 (tb[IFLA_MACVLAN_MODE])) { - case MACVLAN_MODE_PRIVATE: - props->mode = "private"; - break; - case MACVLAN_MODE_VEPA: - props->mode = "vepa"; - break; - case MACVLAN_MODE_BRIDGE: - props->mode = "bridge"; - break; - case MACVLAN_MODE_PASSTHRU: - props->mode = "passthru"; - break; - default: - return -NLE_PARSE_ERR; + 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 entires, 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]; } -#ifdef MACVLAN_FLAG_NOPROMISC - props->no_promisc = !!(nla_get_u16 (tb[IFLA_MACVLAN_FLAGS]) & MACVLAN_FLAG_NOPROMISC); -#else - props->no_promisc = FALSE; -#endif - - return 0; + *out_map = map; + *out_n_map = j; } static gboolean -macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props) +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) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int err; - const NMPObject *obj; - - obj = cache_lookup_link (platform, ifindex); - if (!obj) + const NMPObject *obj_cache; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + unsigned flags; + 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; + char s_flags[64]; + char s_ingress[256]; + char s_egress[256]; + + obj_cache = nmp_cache_lookup_link (priv->cache, ifindex); + if ( !obj_cache + || !obj_cache->_link.netlink.is_in_netlink) { + _LOGD ("link: change %d: %s: link does not exist", ifindex, "vlan"); return FALSE; - - props->parent_ifindex = obj->link.parent; - - err = _nl_link_parse_info_data (priv->nlh, ifindex, - macvlan_info_data_parser, props); - if (err != 0) { - warning ("(%s) could not read properties: %s", - obj->link.name, nl_geterror (err)); } - return (err == 0); -} - -/* 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 -/* older kernel header might not contain 'struct ifla_vxlan_port_range'. - * Redefine it. */ -struct nm_ifla_vxlan_port_range { - guint16 low; - guint16 high; -}; + lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL; + flags = obj_cache->link.flags; + + 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); + + _LOGD ("link: change %d: vlan:%s%s%s", + ifindex, + flags_mask + ? nm_sprintf_buf (s_flags, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask) + : "", + new_n_ingress_map + ? nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map", + new_ingress_map, + new_n_ingress_map, + s_ingress, + sizeof (s_ingress)) + : "", + new_n_egress_map + ? nm_platform_vlan_qos_mapping_to_string (" egress-qos-map", + new_egress_map, + new_n_egress_map, + s_egress, + sizeof (s_egress)) + : ""); + + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + 0, + 0); + 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); -static const struct nla_policy vxlan_info_policy[IFLA_VXLAN_MAX + 1] = { - [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 }, -}; + return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS; +} static int -vxlan_info_data_parser (struct nlattr *info_data, gpointer parser_data) +tun_add (NMPlatform *platform, const char *name, gboolean tap, + gint64 owner, gint64 group, gboolean pi, gboolean vnet_hdr, + gboolean multi_queue, const NMPlatformLink **out_link) { - NMPlatformVxlanProperties *props = parser_data; - struct nlattr *tb[IFLA_VXLAN_MAX + 1]; - struct nm_ifla_vxlan_port_range *range; - int err; + const NMPObject *obj; + struct ifreq ifr = { }; + int fd; - err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, - (struct nla_policy *) vxlan_info_policy); - if (err < 0) - return err; + _LOGD ("link: add %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT, + tap ? "tap" : "tun", name, owner, group); - memset (props, 0, sizeof (*props)); + fd = open ("/dev/net/tun", O_RDWR); + if (fd < 0) + return FALSE; - 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_GROUP6]) - memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6)); - if (tb[IFLA_VXLAN_LOCAL6]) - memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6)); + strncpy (ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN; - 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 (!pi) + ifr.ifr_flags |= IFF_NO_PI; + if (vnet_hdr) + ifr.ifr_flags |= IFF_VNET_HDR; + if (multi_queue) + ifr.ifr_flags |= NM_IFF_MULTI_QUEUE; - if (tb[IFLA_VXLAN_PORT]) - props->dst_port = nla_get_u16 (tb[IFLA_VXLAN_PORT]); + if (ioctl (fd, TUNSETIFF, &ifr)) { + close (fd); + return FALSE; + } - if (tb[IFLA_VXLAN_PORT_RANGE]) { - range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]); - props->src_port_min = range->low; - props->src_port_max = range->high; + if (owner >= 0 && owner < G_MAXINT32) { + if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) { + close (fd); + return FALSE; + } } - 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]); + if (group >= 0 && group < G_MAXINT32) { + if (ioctl (fd, TUNSETGROUP, (gid_t) group)) { + close (fd); + return FALSE; + } + } - return 0; + if (ioctl (fd, TUNSETPERSIST, 1)) { + close (fd); + return FALSE; + } + do_request_link (platform, 0, name); + obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, + 0, name, FALSE, + tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, + NULL, NULL); + if (out_link) + *out_link = obj ? &obj->link : NULL; + + return !!obj; } static gboolean -vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperties *props) +link_enslave (NMPlatform *platform, int master, int slave) { - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int err; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + int ifindex = slave; - err = _nl_link_parse_info_data (priv->nlh, ifindex, - vxlan_info_data_parser, props); - if (err != 0) { - warning ("(%s) could not read vxlan properties: %s", - nm_platform_link_get_name (platform, ifindex), nl_geterror (err)); - } - return (err == 0); -} - -static const struct nla_policy gre_info_policy[IFLA_GRE_MAX + 1] = { - [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 }, -}; + _LOGD ("link: change %d: enslave: master %d", slave, master); -static int -gre_info_data_parser (struct nlattr *info_data, gpointer parser_data) -{ - NMPlatformGreProperties *props = parser_data; - struct nlattr *tb[IFLA_GRE_MAX + 1]; - int err; + nlmsg = _nl_msg_new_link (RTM_NEWLINK, + 0, + ifindex, + NULL, + 0, + 0); + if (!nlmsg) + return FALSE; - err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, - (struct nla_policy *) gre_info_policy); - if (err < 0) - return err; + NLA_PUT_U32 (nlmsg, IFLA_MASTER, master); - props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0; - props->input_flags = nla_get_u16 (tb[IFLA_GRE_IFLAGS]); - props->output_flags = nla_get_u16 (tb[IFLA_GRE_OFLAGS]); - props->input_key = (props->input_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_IKEY]) : 0; - props->output_key = (props->output_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_OKEY]) : 0; - props->local = nla_get_u32 (tb[IFLA_GRE_LOCAL]); - props->remote = nla_get_u32 (tb[IFLA_GRE_REMOTE]); - props->tos = nla_get_u8 (tb[IFLA_GRE_TOS]); - props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]); - props->path_mtu_discovery = !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]); + return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS; +nla_put_failure: + g_return_val_if_reached (FALSE); +} - return 0; +static gboolean +link_release (NMPlatform *platform, int master, int slave) +{ + return link_enslave (platform, 0, slave); } +/******************************************************************/ + static gboolean -gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *props) +infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int err; + const NMPObject *obj_parent; + const NMPObject *obj; + gs_free char *path = NULL; + gs_free char *id = NULL; + gs_free char *ifname = NULL; - err = _nl_link_parse_info_data (priv->nlh, ifindex, - gre_info_data_parser, props); - if (err != 0) { - warning ("(%s) could not read gre properties: %s", - nm_platform_link_get_name (platform, ifindex), nl_geterror (err)); - } - return (err == 0); + obj_parent = nmp_cache_lookup_link (priv->cache, parent); + if (!obj_parent || !obj_parent->link.name[0]) + g_return_val_if_reached (FALSE); + + ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key); + + path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name)); + id = g_strdup_printf ("0x%04x", p_key); + if (!nm_platform_sysctl_set (platform, path, id)) + return FALSE; + + do_request_link (platform, 0, ifname); + + obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, + 0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL); + if (out_link) + *out_link = obj ? &obj->link : NULL; + return !!obj; } +/******************************************************************/ + static WifiData * wifi_get_wifi_data (NMPlatform *platform, int ifindex) { @@ -3846,16 +4943,6 @@ wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid) return wifi_utils_get_bssid (wifi_data, bssid); } -static GByteArray * -wifi_get_ssid (NMPlatform *platform, int ifindex) -{ - WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); - - if (!wifi_data) - return NULL; - return wifi_utils_get_ssid (wifi_data); -} - static guint32 wifi_get_frequency (NMPlatform *platform, int ifindex) { @@ -3906,6 +4993,15 @@ wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode) wifi_utils_set_mode (wifi_data, mode); } +static void +wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave) +{ + WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex); + + if (wifi_data) + wifi_utils_set_powersave (wifi_data, powersave); +} + static guint32 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs) { @@ -3926,6 +5022,7 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru wifi_utils_indicate_addressing_running (wifi_data, running); } +/******************************************************************/ static guint32 mesh_get_channel (NMPlatform *platform, int ifindex) @@ -3960,6 +5057,8 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) return wifi_utils_set_mesh_ssid (wifi_data, ssid, len); } +/******************************************************************/ + static gboolean link_get_wake_on_lan (NMPlatform *platform, int ifindex) { @@ -4019,208 +5118,124 @@ ip6_address_get_all (NMPlatform *platform, int ifindex) return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS); } -#define IPV4LL_NETWORK (htonl (0xA9FE0000L)) -#define IPV4LL_NETMASK (htonl (0xFFFF0000L)) - -static gboolean -ip4_is_link_local (const struct in_addr *src) -{ - return (src->s_addr & IPV4LL_NETMASK) == IPV4LL_NETWORK; -} - -static struct nl_object * -build_rtnl_addr (NMPlatform *platform, - int family, - int ifindex, - gconstpointer addr, - gconstpointer peer_addr, - int plen, - guint32 lifetime, - guint32 preferred, - guint flags, - const char *label) -{ - auto_nl_object struct rtnl_addr *rtnladdr = _nl_rtnl_addr_alloc (ifindex); - struct rtnl_addr *rtnladdr_copy; - int addrlen = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr); - auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (family, addr, addrlen); - int nle; - - /* IP address */ - nle = rtnl_addr_set_local (rtnladdr, nladdr); - if (nle) { - error ("build_rtnl_addr(): rtnl_addr_set_local failed with %s (%d)", nl_geterror (nle), nle); - return NULL; - } - - /* Tighten scope (IPv4 only) */ - if (family == AF_INET && ip4_is_link_local (addr)) - rtnl_addr_set_scope (rtnladdr, RT_SCOPE_LINK); - - /* IPv4 Broadcast address */ - if (family == AF_INET) { - in_addr_t bcast; - auto_nl_addr struct nl_addr *bcaddr = NULL; - - bcast = *((in_addr_t *) addr) | ~nm_utils_ip4_prefix_to_netmask (plen); - bcaddr = _nl_addr_build (family, &bcast, addrlen); - g_assert (bcaddr); - rtnl_addr_set_broadcast (rtnladdr, bcaddr); - } - - /* Peer/point-to-point address */ - if (peer_addr) { - auto_nl_addr struct nl_addr *nlpeer = _nl_addr_build (family, peer_addr, addrlen); - - nle = rtnl_addr_set_peer (rtnladdr, nlpeer); - if (nle && nle != -NLE_AF_NOSUPPORT) { - /* IPv6 doesn't support peer addresses yet */ - error ("build_rtnl_addr(): rtnl_addr_set_peer failed with %s (%d)", nl_geterror (nle), nle); - return NULL; - } - } - - _nl_rtnl_addr_set_prefixlen (rtnladdr, plen); - if ( (lifetime != 0 && lifetime != NM_PLATFORM_LIFETIME_PERMANENT) - || (preferred != 0 && preferred != NM_PLATFORM_LIFETIME_PERMANENT)) { - /* note that here we set the relative timestamps (ticking from *now*). */ - rtnl_addr_set_valid_lifetime (rtnladdr, lifetime); - rtnl_addr_set_preferred_lifetime (rtnladdr, preferred); - } - if (flags) { - if ((flags & ~0xFF) && !_support_kernel_extended_ifa_flags_get ()) { - /* Older kernels don't accept unknown netlink attributes. - * - * With commit libnl commit 5206c050504f8676a24854519b9c351470fb7cc6, libnl will only set - * the extended address flags attribute IFA_FLAGS when necessary (> 8 bit). But it's up to - * us not to shove those extended flags on to older kernels. - * - * Just silently clear them. The kernel should ignore those unknown flags anyway. */ - flags &= 0xFF; - } - rtnl_addr_set_flags (rtnladdr, flags); - } - if (label && *label) - rtnl_addr_set_label (rtnladdr, label); - - rtnladdr_copy = rtnladdr; - rtnladdr = NULL; - return (struct nl_object *) rtnladdr_copy; -} - -struct nl_object * -_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) -{ - const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj; - guint32 lifetime, preferred; - - nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred, - 0, 0, &lifetime, &preferred); - - return build_rtnl_addr (platform, - AF_INET, - obj->ifindex, - &obj->address, - obj->peer_address ? &obj->peer_address : NULL, - obj->plen, - lifetime, - preferred, - 0, - obj->label[0] ? obj->label : NULL); -} - -struct nl_object * -_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) -{ - const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj; - guint32 lifetime, preferred; - - nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred, - 0, 0, &lifetime, &preferred); - - return build_rtnl_addr (platform, - AF_INET6, - obj->ifindex, - &obj->address, - !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL, - obj->plen, - lifetime, - preferred, - 0, - NULL); -} - static gboolean ip4_address_add (NMPlatform *platform, int ifindex, in_addr_t addr, - in_addr_t peer_addr, int plen, + in_addr_t peer_addr, guint32 lifetime, guint32 preferred, const char *label) { - NMPObject obj_needle; - auto_nl_object struct nl_object *nlo = NULL; + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; - nlo = build_rtnl_addr (platform, AF_INET, ifindex, &addr, - peer_addr ? &peer_addr : NULL, - plen, lifetime, preferred, 0, - label); - return do_add_addrroute (platform, - nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen), - nlo); + nlmsg = _nl_msg_new_address (RTM_NEWADDR, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET, + ifindex, + &addr, + plen, + &peer_addr, + 0, + ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE, + lifetime, + preferred, + label); + + nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr); + return do_add_addrroute (platform, &obj_id, nlmsg); } static gboolean ip6_address_add (NMPlatform *platform, int ifindex, struct in6_addr addr, - struct in6_addr peer_addr, int plen, + struct in6_addr peer_addr, guint32 lifetime, guint32 preferred, guint flags) { - NMPObject obj_needle; - auto_nl_object struct nl_object *nlo = NULL; + 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, + &peer_addr, + flags, + RT_SCOPE_UNIVERSE, + lifetime, + preferred, + NULL); - nlo = build_rtnl_addr (platform, AF_INET6, ifindex, &addr, - IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr, - plen, lifetime, preferred, flags, - NULL); - return do_add_addrroute (platform, - nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen), - nlo); + nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen); + return do_add_addrroute (platform, &obj_id, nlmsg); } static gboolean ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address) { - NMPObject obj_needle; + 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, + NULL); + if (!nlmsg) + g_return_val_if_reached (FALSE); - nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen); - obj_needle.ip4_address.peer_address = peer_address; - return do_delete_object (platform, &obj_needle, NULL); + 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, int plen) { - NMPObject obj_needle; + 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, + NULL); + if (!nlmsg) + g_return_val_if_reached (FALSE); - nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen); - return do_delete_object (platform, &obj_needle, NULL); + nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen); + return do_delete_object (platform, &obj_id, nlmsg); } static const NMPlatformIP4Address * -ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen) +ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address) { - NMPObject obj_needle; + NMPObject obj_id; const NMPObject *obj; - nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen); - obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle); + nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address); + obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id); if (nmp_object_is_visible (obj)) return &obj->ip4_address; return NULL; @@ -4229,11 +5244,11 @@ ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen) static const NMPlatformIP6Address * ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen) { - NMPObject obj_needle; + NMPObject obj_id; const NMPObject *obj; - nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen); - obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle); + nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen); + obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id); if (nmp_object_is_visible (obj)) return &obj->ip6_address; return NULL; @@ -4292,113 +5307,29 @@ ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags fl return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags); } -static void -clear_host_address (int family, const void *network, int plen, void *dst) -{ - g_return_if_fail (plen == (guint8)plen); - g_return_if_fail (network); - - switch (family) { - case AF_INET: - *((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen); - break; - case AF_INET6: - nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen); - break; - default: - g_assert_not_reached (); - } -} - -static struct nl_object * -build_rtnl_route (int family, int ifindex, NMIPConfigSource source, - gconstpointer network, int plen, gconstpointer gateway, - gconstpointer pref_src, - guint32 metric, guint32 mss) -{ - guint32 network_clean[4]; - struct rtnl_route *rtnlroute; - struct rtnl_nexthop *nexthop; - int addrlen = (family == AF_INET) ? sizeof (in_addr_t) : sizeof (struct in6_addr); - /* Workaround a libnl bug by using zero destination address length for default routes */ - auto_nl_addr struct nl_addr *dst = NULL; - auto_nl_addr struct nl_addr *gw = gateway ? _nl_addr_build (family, gateway, addrlen) : NULL; - auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nl_addr_build (family, pref_src, addrlen) : NULL; - - /* There seem to be problems adding a route with non-zero host identifier. - * Adding IPv6 routes is simply ignored, without error message. - * In the IPv4 case, we got an error. Thus, we have to make sure, that - * the address is sane. */ - clear_host_address (family, network, plen, network_clean); - dst = _nl_addr_build (family, network_clean, plen ? addrlen : 0); - nl_addr_set_prefixlen (dst, plen); - - rtnlroute = _nl_rtnl_route_alloc (); - rtnl_route_set_table (rtnlroute, RT_TABLE_MAIN); - rtnl_route_set_tos (rtnlroute, 0); - rtnl_route_set_dst (rtnlroute, dst); - rtnl_route_set_priority (rtnlroute, metric); - rtnl_route_set_family (rtnlroute, family); - rtnl_route_set_protocol (rtnlroute, _nm_ip_config_source_to_rtprot (source)); - - nexthop = _nl_rtnl_route_nh_alloc (); - rtnl_route_nh_set_ifindex (nexthop, ifindex); - if (gw && !nl_addr_iszero (gw)) - rtnl_route_nh_set_gateway (nexthop, gw); - if (pref_src_nl) - rtnl_route_set_pref_src (rtnlroute, pref_src_nl); - rtnl_route_add_nexthop (rtnlroute, nexthop); - - if (mss > 0) - rtnl_route_set_metric (rtnlroute, RTAX_ADVMSS, mss); - - return (struct nl_object *) rtnlroute; -} - -struct nl_object * -_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) -{ - const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj; - - return build_rtnl_route (AF_INET, - obj->ifindex, - obj->source, - &obj->network, - obj->plen, - &obj->gateway, - obj->pref_src ? &obj->pref_src : NULL, - obj->metric, - obj->mss); -} - -struct nl_object * -_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) -{ - const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj; - - return build_rtnl_route (AF_INET6, - obj->ifindex, - obj->source, - &obj->network, - obj->plen, - &obj->gateway, - NULL, - obj->metric, - obj->mss); -} - static gboolean ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source, in_addr_t network, int plen, in_addr_t gateway, - guint32 pref_src, guint32 metric, guint32 mss) + in_addr_t pref_src, guint32 metric, guint32 mss) { - NMPObject obj_needle; - auto_nl_object struct nl_object *nlo = NULL; + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; - nlo = build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss); - return do_add_addrroute (platform, - nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric), - nlo); + nlmsg = _nl_msg_new_route (RTM_NEWROUTE, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET, + ifindex, + source, + gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK, + &network, + plen, + &gateway, + metric, + mss, + pref_src ? &pref_src : NULL); + + nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric); + return do_add_addrroute (platform, &obj_id, nlmsg); } static gboolean @@ -4406,30 +5337,34 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source, struct in6_addr network, int plen, struct in6_addr gateway, guint32 metric, guint32 mss) { - NMPObject obj_needle; - auto_nl_object struct nl_object *nlo = NULL; + NMPObject obj_id; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; - metric = nm_utils_ip6_route_metric_normalize (metric); + nlmsg = _nl_msg_new_route (RTM_NEWROUTE, + NLM_F_CREATE | NLM_F_REPLACE, + AF_INET6, + ifindex, + source, + !IN6_IS_ADDR_UNSPECIFIED (&gateway) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK, + &network, + plen, + &gateway, + metric, + mss, + NULL); - nlo = build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss); - return do_add_addrroute (platform, - nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric), - nlo); + nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric); + return do_add_addrroute (platform, &obj_id, nlmsg); } static gboolean ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - in_addr_t gateway = 0; - auto_nl_object struct nl_object *nlo = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 0); - uint8_t scope = RT_SCOPE_NOWHERE; - const NMPObject *obj; - NMPObject obj_needle; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; - g_return_val_if_fail (nlo, FALSE); - - nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric); + nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric); if (metric == 0) { /* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route. @@ -4439,94 +5374,81 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen * Instead, make sure that we have the most recent state and process all * delayed actions (including re-reading data from netlink). */ delayed_action_handle_all (platform, TRUE); - } - - obj = nmp_cache_lookup_obj (priv->cache, &obj_needle); - - if (metric == 0 && !obj) { - /* hmm... we are about to delete an IP4 route with metric 0. We must only - * send the delete request if such a route really exists. Above we refreshed - * the platform cache, still no such route exists. - * - * Be extra careful and reload the routes. We must be sure that such a - * route doesn't exists, because when we add an IPv4 address, we immediately - * afterwards try to delete the kernel-added device route with metric 0. - * It might be, that we didn't yet get the notification about that route. - * - * FIXME: once our ip4_address_add() is sure that upon return we have - * the latest state from in the platform cache, we might save this - * additional expensive cache-resync. */ - do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE, TRUE); - - obj = nmp_cache_lookup_obj (priv->cache, &obj_needle); - if (!obj) - return TRUE; - } - - if (!_nl_has_capability (1 /* NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE */)) { - /* When searching for a matching IPv4 route to delete, the kernel - * searches for a matching scope, unless the RTM_DELROUTE message - * specifies RT_SCOPE_NOWHERE (see fib_table_delete()). - * - * However, if we set the scope of @rtnlroute to RT_SCOPE_NOWHERE (or - * leave it unset), rtnl_route_build_msg() will reset the scope to - * rtnl_route_guess_scope() -- which probably guesses wrong. - * - * As a workaround, we look at the cached route and use that scope. - * - * Newer versions of libnl, no longer reset the scope if explicitly set to RT_SCOPE_NOWHERE. - * So, this workaround is only needed unless we have NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE. - **/ - if (obj) - scope = nm_platform_route_scope_inv (obj->ip4_route.scope_inv); + if (!nmp_cache_lookup_obj (priv->cache, &obj_id)) { + /* hmm... we are about to delete an IP4 route with metric 0. We must only + * send the delete request if such a route really exists. Above we refreshed + * the platform cache, still no such route exists. + * + * Be extra careful and reload the routes. We must be sure that such a + * route doesn't exists, because when we add an IPv4 address, we immediately + * afterwards try to delete the kernel-added device route with metric 0. + * It might be, that we didn't yet get the notification about that route. + * + * FIXME: once our ip4_address_add() is sure that upon return we have + * the latest state from in the platform cache, we might save this + * additional expensive cache-resync. */ + do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE); - if (scope == RT_SCOPE_NOWHERE) { - /* If we would set the scope to RT_SCOPE_NOWHERE, libnl would guess the scope. - * But probably it will guess 'link' because we set the next hop of the route - * to zero (0.0.0.0). A better guess is 'global'. */ - scope = RT_SCOPE_UNIVERSE; + if (!nmp_cache_lookup_obj (priv->cache, &obj_id)) + return TRUE; } } - rtnl_route_set_scope ((struct rtnl_route *) nlo, scope); - - /* we only support routes with TOS zero. As such, delete_route() is also only able to delete - * routes with tos==0. build_rtnl_route() already initializes tos properly. */ - /* The following fields are also relevant when comparing the route, but the default values - * are already as we want them: - * - * type: RTN_UNICAST (setting to zero would ignore the type, but we only want to delete RTN_UNICAST) - * pref_src: NULL - */ + nlmsg = _nl_msg_new_route (RTM_DELROUTE, + 0, + AF_INET, + ifindex, + NM_IP_CONFIG_SOURCE_UNKNOWN, + RT_SCOPE_NOWHERE, + &network, + plen, + NULL, + metric, + 0, + NULL); + if (!nlmsg) + return FALSE; - return do_delete_object (platform, &obj_needle, nlo); + return do_delete_object (platform, &obj_id, nlmsg); } static gboolean ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric) { - struct in6_addr gateway = IN6ADDR_ANY_INIT; - auto_nl_object struct nl_object *nlo = NULL; - NMPObject obj_needle; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + NMPObject obj_id; metric = nm_utils_ip6_route_metric_normalize (metric); - nlo = build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0); + nlmsg = _nl_msg_new_route (RTM_DELROUTE, + 0, + AF_INET6, + ifindex, + NM_IP_CONFIG_SOURCE_UNKNOWN, + RT_SCOPE_NOWHERE, + &network, + plen, + NULL, + metric, + 0, + NULL); + if (!nlmsg) + return FALSE; - nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric); + nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric); - return do_delete_object (platform, &obj_needle, nlo); + return do_delete_object (platform, &obj_id, nlmsg); } static const NMPlatformIP4Route * ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric) { - NMPObject obj_needle; + NMPObject obj_id; const NMPObject *obj; - nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric); - obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle); + nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric); + obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id); if (nmp_object_is_visible (obj)) return &obj->ip4_route; return NULL; @@ -4535,13 +5457,13 @@ ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, g static const NMPlatformIP6Route * ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric) { - NMPObject obj_needle; + NMPObject obj_id; const NMPObject *obj; metric = nm_utils_ip6_route_metric_normalize (metric); - nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric); - obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle); + nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric); + obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id); if (nmp_object_is_visible (obj)) return &obj->ip6_route; return NULL; @@ -4553,22 +5475,6 @@ ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int p #define ERROR_CONDITIONS ((GIOCondition) (G_IO_ERR | G_IO_NVAL)) #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP)) -static int -verify_source (struct nl_msg *msg, gpointer user_data) -{ - struct ucred *creds = nlmsg_get_creds (msg); - - if (!creds || creds->pid) { - if (creds) - warning ("netlink: received non-kernel message (pid %d)", creds->pid); - else - warning ("netlink: received message without credentials"); - return NL_STOP; - } - - return NL_OK; -} - static gboolean event_handler (GIOChannel *channel, GIOCondition io_condition, @@ -4578,148 +5484,288 @@ event_handler (GIOChannel *channel, return TRUE; } -static gboolean -event_handler_read_netlink_one (NMPlatform *platform) +/*****************************************************************************/ + +/* copied from libnl3's recvmsgs() */ +static int +event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int nle; + struct nl_sock *sk = priv->nlh; + int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0; + unsigned char *buf = NULL; + struct nlmsghdr *hdr; + WaitForNlResponseResult seq_result; + + /* + nla is passed on to not only to nl_recv() but may also be passed + to a function pointer provided by the caller which may or may not + initialize the variable. Thomas Graf. + */ + struct sockaddr_nl nla = {0}; + struct nl_msg *msg = NULL; + struct ucred *creds = NULL; +continue_reading: errno = 0; - nle = nl_recvmsgs_default (priv->nlh_event); + n = nl_recv (sk, &nla, &buf, &creds); /* Work around a libnl bug fixed in 3.2.22 (375a6294) */ - if (nle == 0 && errno == EAGAIN) { + if (n == 0 && errno == EAGAIN) { /* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */ G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK); - nle = -NLE_AGAIN; + n = -NLE_AGAIN; } - if (nle < 0) - switch (nle) { - case -NLE_AGAIN: - return FALSE; - case -NLE_DUMP_INTR: - debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); - break; - case -NLE_NOMEM: - info ("Too many netlink events. Need to resynchronize platform cache"); - /* Drain the event queue, we've lost events and are out of sync anyway and we'd - * like to free up some space. We'll read in the status synchronously. */ - _nl_sock_flush_data (priv->nlh_event); - priv->nlh_seq_expect = 0; - 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, - NULL); - break; - default: - error ("Failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); - break; + if (n <= 0) + return n; + + if (!handle_events) { + /* we read until failure or there is nothing to read (EAGAIN). */ + goto continue_reading; } - return TRUE; + + hdr = (struct nlmsghdr *) buf; + while (nlmsg_ok (hdr, n)) { + gboolean abort_parsing = FALSE; + + nlmsg_free (msg); + msg = nlmsg_convert (hdr); + if (!msg) { + err = -NLE_NOMEM; + goto out; + } + + nlmsg_set_proto (msg, NETLINK_ROUTE); + nlmsg_set_src (msg, &nla); + nrecv++; + + if (!creds || creds->pid) { + if (creds) + _LOGD ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid); + else + _LOGD ("netlink: recvmsg: received message without credentials"); + goto stop; + } + + _LOGt ("netlink: recvmsg: new message type %d, seq %u", + hdr->nlmsg_type, hdr->nlmsg_seq); + + if (creds) + nlmsg_set_creds (msg, creds); + + 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; + } + + /* 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 = 0; + 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 retuning + * NL_SKIP or NL_PROCEED (dangerous) */ + err = -NLE_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 = -NLE_MSG_TRUNC; + abort_parsing = TRUE; + } else if (e->error) { + int errsv = e->error > 0 ? e->error : -e->error; + + /* Error message reported back from kernel. */ + _LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d", + strerror (errsv), + errsv, + nlmsg_hdr (msg)->nlmsg_seq); + seq_result = -errsv; + } else + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } else { + /* 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); + seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; + } + + event_seq_check (platform, msg, seq_result); + err = 0; + hdr = nlmsg_next (hdr, &n); + + if (abort_parsing) + goto out; + } + + nlmsg_free (msg); + free (buf); + free (creds); + buf = NULL; + msg = NULL; + creds = NULL; + + if (multipart) { + /* Multipart message not yet complete, continue reading */ + goto continue_reading; + } +stop: + err = 0; +out: + nlmsg_free (msg); + free (buf); + free (creds); + + if (interrupted) + err = -NLE_DUMP_INTR; + + if (!err) + err = nrecv; + + return err; } +/*****************************************************************************/ + static gboolean -event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks) +event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int r; + int r, nle; struct pollfd pfd; gboolean any = FALSE; - gint64 timestamp = 0, now; - const int TIMEOUT = 250; - int timeout = 0; - guint32 wait_for_seq = 0; + gint64 now_ns; + int timeout_ms; + guint i; + struct { + guint32 seq_number; + gint64 timeout_abs_ns; + } data_next; while (TRUE) { - while (event_handler_read_netlink_one (platform)) + + while (TRUE) { + + nle = event_handler_recvmsgs (platform, TRUE); + + if (nle < 0) + switch (nle) { + case -NLE_AGAIN: + goto after_read; + case -NLE_DUMP_INTR: + _LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); + break; + case -NLE_NOMEM: + _LOGI ("netlink: read: too many netlink events. Need to resynchronize platform cache"); + /* Drain the event queue, we've lost events and are out of sync anyway and we'd + * like to free up some space. We'll read in the status synchronously. */ + delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC); + event_handler_recvmsgs (platform, FALSE); + 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, + NULL); + break; + default: + _LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); + break; + } any = TRUE; + } - if (!wait_for_acks || priv->nlh_seq_expect == 0) { - if (wait_for_seq) - _LOGt ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect); +after_read: + + if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) return any; - } - now = nm_utils_get_monotonic_timestamp_ms (); - if (wait_for_seq != priv->nlh_seq_expect) { - /* We are waiting for a new sequence number (or we will wait for the first time). - * Reset/start counting the overall wait time. */ - _LOGt ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect); - wait_for_seq = priv->nlh_seq_expect; - timestamp = now; - timeout = TIMEOUT; - } else { - if ((now - timestamp) >= TIMEOUT) { - /* timeout. Don't wait for this sequence number anymore. */ - break; - } + now_ns = 0; + data_next.seq_number = 0; + data_next.timeout_abs_ns = 0; + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) { + 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 ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) > data->timeout_abs_ns) + delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT); + else { + i++; - /* readjust the wait-time. */ - timeout = TIMEOUT - (now - timestamp); + if ( data_next.seq_number == 0 + || data_next.timeout_abs_ns > data->timeout_abs_ns) + data_next.seq_number = data->seq_number; + data_next.timeout_abs_ns = data->timeout_abs_ns; + } } + if ( !wait_for_acks + || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) + return any; + + nm_assert (data_next.seq_number); + nm_assert (data_next.timeout_abs_ns > 0); + nm_assert (now_ns > 0); + + _LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number); + + timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000); + memset (&pfd, 0, sizeof (pfd)); - pfd.fd = nl_socket_get_fd (priv->nlh_event); + pfd.fd = nl_socket_get_fd (priv->nlh); pfd.events = POLLIN; - r = poll (&pfd, 1, timeout); + r = poll (&pfd, 1, MAX (1, timeout_ms)); if (r == 0) { - /* timeout. */ - break; + /* timeout and there is nothing to read. */ + goto after_read; } if (r < 0) { int errsv = errno; if (errsv != EINTR) { - _LOGE ("read-netlink-all: poll failed with %s", strerror (errsv)); + _LOGE ("netlink: read: poll failed with %s", strerror (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. */ } } - - _LOGW ("read-netlink-all: timeout waiting for ACK to sequence number %u...", wait_for_seq); - priv->nlh_seq_expect = 0; - return any; -} - -static struct nl_sock * -setup_socket (gboolean event, gpointer user_data) -{ - struct nl_sock *sock; - int nle; - - sock = nl_socket_alloc (); - g_return_val_if_fail (sock, NULL); - - /* Only ever accept messages from kernel */ - nle = nl_socket_modify_cb (sock, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, user_data); - g_assert (!nle); - - /* Dispatch event messages (event socket only) */ - if (event) { - nl_socket_modify_cb (sock, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data); - nl_socket_modify_cb (sock, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, event_seq_check, user_data); - nl_socket_modify_err_cb (sock, NL_CB_CUSTOM, event_err, user_data); - } - - nle = nl_connect (sock, NETLINK_ROUTE); - g_assert (!nle); - nle = nl_socket_set_passcred (sock, 1); - g_assert (!nle); - - /* No blocking for event socket, so that we can drain it safely. */ - if (event) { - nle = nl_socket_set_nonblocking (sock); - g_assert (!nle); - } - - return sock; } /******************************************************************/ @@ -4728,12 +5774,12 @@ static void cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_device) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - auto_nmp_obj NMPObject *obj_cache = NULL; + nm_auto_nmpobj NMPObject *obj_cache = NULL; gboolean was_visible; NMPCacheOpsType cache_op; cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform); - do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL); + do_emit_signal (platform, obj_cache, cache_op, was_visible); } static void @@ -4745,23 +5791,23 @@ udev_device_added (NMPlatform *platform, ifname = g_udev_device_get_name (udev_device); if (!ifname) { - debug ("udev-add: failed to get device's interface"); + _LOGD ("udev-add: failed to get device's interface"); return; } if (g_udev_device_get_property (udev_device, "IFINDEX")) ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX"); else { - warning ("(%s): udev-add: failed to get device's ifindex", ifname); + _LOGW ("(%s): udev-add: failed to get device's ifindex", ifname); return; } if (ifindex <= 0) { - warning ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex); + _LOGW ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex); return; } if (!g_udev_device_get_sysfs_path (udev_device)) { - debug ("(%s): udev-add: couldn't determine device path; ignoring...", ifname); + _LOGD ("(%s): udev-add: couldn't determine device path; ignoring...", ifname); return; } @@ -4791,7 +5837,7 @@ udev_device_removed (NMPlatform *platform, ifindex = obj->link.ifindex; } - debug ("udev-remove: IFINDEX=%d", ifindex); + _LOGD ("udev-remove: IFINDEX=%d", ifindex); if (ifindex <= 0) return; @@ -4817,9 +5863,9 @@ handle_udev_event (GUdevClient *client, ifindex = g_udev_device_get_property (udev_device, "IFINDEX"); seqnum = g_udev_device_get_seqnum (udev_device); - debug ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, - action, subsys, g_udev_device_get_name (udev_device), - ifindex ? ifindex : "unknown", seqnum); + _LOGD ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT, + action, subsys, g_udev_device_get_name (udev_device), + ifindex ? ifindex : "unknown", seqnum); if (!strcmp (action, "add") || !strcmp (action, "move")) udev_device_added (platform, udev_device); @@ -4836,9 +5882,11 @@ nm_linux_platform_init (NMLinuxPlatform *self) self->priv = priv; + priv->nlh_seq_next = 1; priv->cache = nmp_cache_new (); 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)); priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit); } @@ -4856,29 +5904,37 @@ constructed (GObject *_object) _LOGD ("create"); - /* Initialize netlink socket for requests */ - priv->nlh = setup_socket (FALSE, platform); + priv->nlh = nl_socket_alloc (); g_assert (priv->nlh); - debug ("Netlink socket for requests established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh)); - /* Initialize netlink socket for events */ - priv->nlh_event = setup_socket (TRUE, platform); - g_assert (priv->nlh_event); + 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); + /* The default buffer size wasn't enough for the testsuites. It might just * as well happen with NetworkManager itself. For now let's hope 128KB is * good enough. + * + * FIXME: it's unclear that this is still actually needed. The testsuite + * certainly doesn't fail for me. Maybe it can be removed. */ - nle = nl_socket_set_buffer_size (priv->nlh_event, 131072, 0); + nle = nl_socket_set_buffer_size (priv->nlh, 131072, 0); g_assert (!nle); - nle = nl_socket_add_memberships (priv->nlh_event, + + nle = nl_socket_add_memberships (priv->nlh, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, 0); g_assert (!nle); - debug ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh_event), nl_socket_get_fd (priv->nlh_event)); + _LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh)); - priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event)); + priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh)); g_io_channel_set_encoding (priv->event_channel, NULL, NULL); g_io_channel_set_close_on_unref (priv->event_channel, TRUE); @@ -4931,12 +5987,12 @@ dispose (GObject *object) _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); - nm_clear_g_source (&priv->delayed_action.idle_id); - g_clear_pointer (&priv->prune_candidates, g_hash_table_unref); G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object); @@ -4951,16 +6007,21 @@ nm_linux_platform_finalize (GObject *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); /* Free netlink resources */ g_source_remove (priv->event_id); g_io_channel_unref (priv->event_channel); nl_socket_free (priv->nlh); - nl_socket_free (priv->nlh_event); g_object_unref (priv->udev_client); g_hash_table_unref (priv->wifi_data); + if (priv->sysctl_get_prev_values) { + sysctl_clear_cache_list = g_slist_remove (sysctl_clear_cache_list, object); + g_hash_table_destroy (priv->sysctl_get_prev_values); + } + G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object); } @@ -4991,6 +6052,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->link_get_type_name = link_get_type_name; platform_class->link_get_unmanaged = link_get_unmanaged; + platform_class->link_get_lnk = link_get_lnk; + platform_class->link_refresh = link_refresh; platform_class->link_set_up = link_set_up; @@ -5017,33 +6080,23 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->link_enslave = link_enslave; platform_class->link_release = link_release; - platform_class->master_set_option = master_set_option; - platform_class->master_get_option = master_get_option; - platform_class->slave_set_option = slave_set_option; - platform_class->slave_get_option = slave_get_option; platform_class->vlan_add = vlan_add; - platform_class->vlan_get_info = vlan_get_info; - platform_class->vlan_set_ingress_map = vlan_set_ingress_map; - platform_class->vlan_set_egress_map = vlan_set_egress_map; + platform_class->link_vlan_change = link_vlan_change; + platform_class->link_vxlan_add = link_vxlan_add; - platform_class->infiniband_partition_add = infiniband_partition_add; - platform_class->infiniband_get_info = infiniband_get_info; + platform_class->tun_add = tun_add; - platform_class->veth_get_properties = veth_get_properties; - platform_class->tun_get_properties = tun_get_properties; - platform_class->macvlan_get_properties = macvlan_get_properties; - platform_class->vxlan_get_properties = vxlan_get_properties; - platform_class->gre_get_properties = gre_get_properties; + platform_class->infiniband_partition_add = infiniband_partition_add; platform_class->wifi_get_capabilities = wifi_get_capabilities; platform_class->wifi_get_bssid = wifi_get_bssid; - platform_class->wifi_get_ssid = wifi_get_ssid; platform_class->wifi_get_frequency = wifi_get_frequency; platform_class->wifi_get_quality = wifi_get_quality; platform_class->wifi_get_rate = wifi_get_rate; 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; @@ -5051,6 +6104,12 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->mesh_set_channel = mesh_set_channel; platform_class->mesh_set_ssid = mesh_set_ssid; + platform_class->link_gre_add = link_gre_add; + platform_class->link_ip6tnl_add = link_ip6tnl_add; + platform_class->link_macvlan_add = link_macvlan_add; + platform_class->link_ipip_add = link_ipip_add; + platform_class->link_sit_add = link_sit_add; + platform_class->ip4_address_get = ip4_address_get; platform_class->ip6_address_get = ip6_address_get; platform_class->ip4_address_get_all = ip4_address_get_all; |