diff options
Diffstat (limited to 'src/platform')
24 files changed, 3490 insertions, 1038 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c index be430152..06dd7e13 100644 --- a/src/platform/nm-fake-platform.c +++ b/src/platform/nm-fake-platform.c @@ -938,7 +938,6 @@ wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs) static void wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running) { - ; } static guint32 diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index e5961c7e..0ed8fa06 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -21,6 +21,7 @@ #include "nm-linux-platform.h" +#include <poll.h> #include <endian.h> #include <errno.h> #include <unistd.h> @@ -36,14 +37,14 @@ #include <linux/if_link.h> #include <linux/if_tun.h> #include <linux/if_tunnel.h> -#include <netlink/netlink.h> -#include <netlink/msg.h> +#include <linux/ip6_tunnel.h> #include <libudev.h> #include "nm-utils.h" #include "nm-core-internal.h" #include "nm-setting-vlan.h" +#include "nm-netlink.h" #include "nm-core-utils.h" #include "nmp-object.h" #include "nmp-netns.h" @@ -79,10 +80,6 @@ enum { #define VLAN_FLAG_MVRP 0x8 -/* nm-internal error codes for libnl. Make sure they don't overlap. */ -#define _NLE_NM_NOBUFS 500 -#define _NLE_MSG_TRUNC 501 - /*****************************************************************************/ #define IFQDISCSIZ 32 @@ -127,6 +124,18 @@ enum { #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) #endif +#define IFLA_TUN_UNSPEC 0 +#define IFLA_TUN_OWNER 1 +#define IFLA_TUN_GROUP 2 +#define IFLA_TUN_TYPE 3 +#define IFLA_TUN_PI 4 +#define IFLA_TUN_VNET_HDR 5 +#define IFLA_TUN_PERSIST 6 +#define IFLA_TUN_MULTI_QUEUE 7 +#define IFLA_TUN_NUM_QUEUES 8 +#define IFLA_TUN_NUM_DISABLED_QUEUES 9 +#define __IFLA_TUN_MAX 10 +#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1) static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */); @@ -283,7 +292,7 @@ typedef enum { #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \ for ((iflags) = (DelayedActionType) 0x1LL; (iflags) <= DELAYED_ACTION_TYPE_MAX; (iflags) <<= 1) \ - if (NM_FLAGS_HAS (flags_all, iflags)) + if (NM_FLAGS_ANY (flags_all, iflags)) typedef enum { /* Negative values are errors from kernel. Add dummy member to @@ -297,13 +306,9 @@ typedef enum { WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS, } WaitForNlResponseResult; -typedef 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_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name); @@ -328,7 +333,9 @@ wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result) } static const char * -wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size) +wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, + const char *errmsg, + char *buf, gsize buf_size) { char *buf0 = buf; @@ -343,8 +350,13 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, g 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))); + if (seq_result < 0) { + nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s%s%s)", + -((int) seq_result), + g_strerror (-((int) seq_result)), + errmsg ? " - " : "", + errmsg ?: ""); + } else nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result); break; @@ -538,8 +550,7 @@ static const LinkDesc linktypes[] = { { NM_LINK_TYPE_OPENVSWITCH, "openvswitch", "openvswitch", NULL }, { NM_LINK_TYPE_PPP, "ppp", NULL, "ppp" }, { NM_LINK_TYPE_SIT, "sit", "sit", NULL }, - { NM_LINK_TYPE_TAP, "tap", NULL, NULL }, - { NM_LINK_TYPE_TUN, "tun", NULL, NULL }, + { NM_LINK_TYPE_TUN, "tun", "tun", NULL }, { NM_LINK_TYPE_VETH, "veth", "veth", NULL }, { NM_LINK_TYPE_VLAN, "vlan", "vlan", "vlan" }, { NM_LINK_TYPE_VXLAN, "vxlan", "vxlan", "vxlan" }, @@ -800,36 +811,25 @@ _linktype_get_type (NMPlatform *platform, && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE) && nm_streq (ifname, obj->link.name) && ( !kind - || !g_strcmp0 (kind, obj->link.kind))) { + || nm_streq0 (kind, obj->link.kind))) { nm_assert (obj->link.kind == g_intern_string (obj->link.kind)); *out_kind = obj->link.kind; return obj->link.type; } } + /* we intern kind to not require us to keep the pointer alive. Essentially + * leaking it in a global cache. That should be safe enough, because the + * kind comes only from kernel messages, which depend on the number of + * available drivers. So, there is not the danger that we leak uncontrolled + * many kinds. */ *out_kind = g_intern_string (kind); if (kind) { for (i = 0; i < G_N_ELEMENTS (linktypes); i++) { - if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0) + if (nm_streq0 (kind, linktypes[i].rtnl_type)) { return linktypes[i].nm_type; - } - - if (!strcmp (kind, "tun")) { - NMPlatformTunProperties props; - - if ( platform - && nm_platform_link_tun_get_properties (platform, ifindex, &props)) { - if (!g_strcmp0 (props.mode, "tap")) - return NM_LINK_TYPE_TAP; - if (!g_strcmp0 (props.mode, "tun")) - return NM_LINK_TYPE_TUN; } - - /* try guessing the type using the link flags instead... */ - if (flags & IFF_POINTOPOINT) - return NM_LINK_TYPE_TUN; - return NM_LINK_TYPE_TAP; } } @@ -951,137 +951,6 @@ _nl_addattr_l (struct nlmsghdr *n, return TRUE; } -static void -_nm_auto_nl_msg_cleanup (void *ptr) -{ - nlmsg_free (*((struct nl_msg **) ptr)); -} -#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup) - -static const char * -_nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len) -{ - const char *b; - const char *s; - guint flags, flags_before; - const char *prefix; - - nm_utils_to_string_buffer_init (&buf, &len); - b = buf; - - switch (hdr->nlmsg_type) { - case RTM_NEWLINK: s = "RTM_NEWLINK"; break; - case RTM_DELLINK: s = "RTM_DELLINK"; break; - case RTM_NEWADDR: s = "RTM_NEWADDR"; break; - case RTM_DELADDR: s = "RTM_DELADDR"; break; - case RTM_NEWROUTE: s = "RTM_NEWROUTE"; break; - case RTM_DELROUTE: s = "RTM_DELROUTE"; break; - case RTM_NEWQDISC: s = "RTM_NEWQDISC"; break; - case RTM_DELQDISC: s = "RTM_DELQDISC"; break; - case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break; - case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break; - case NLMSG_NOOP: s = "NLMSG_NOOP"; break; - case NLMSG_ERROR: s = "NLMSG_ERROR"; break; - case NLMSG_DONE: s = "NLMSG_DONE"; break; - case NLMSG_OVERRUN: s = "NLMSG_OVERRUN"; break; - default: s = NULL; break; - } - - if (s) - nm_utils_strbuf_append_str (&buf, &len, s); - else - nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type); - - flags = hdr->nlmsg_flags; - - if (!flags) { - nm_utils_strbuf_append_str (&buf, &len, ", flags 0"); - goto flags_done; - } - -#define _F(f, n) \ - G_STMT_START { \ - if (NM_FLAGS_ALL (flags, f)) { \ - flags &= ~(f); \ - nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \ - if (!flags) \ - goto flags_done; \ - prefix = ","; \ - } \ - } G_STMT_END - - prefix = ", flags "; - flags_before = flags; - _F (NLM_F_REQUEST, "request"); - _F (NLM_F_MULTI, "multi"); - _F (NLM_F_ACK, "ack"); - _F (NLM_F_ECHO, "echo"); - _F (NLM_F_DUMP_INTR, "dump_intr"); - _F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered"); - - if (flags_before != flags) - prefix = ";"; - - switch (hdr->nlmsg_type) { - case RTM_NEWLINK: - case RTM_NEWADDR: - case RTM_NEWROUTE: - case RTM_NEWQDISC: - case RTM_NEWTFILTER: - _F (NLM_F_REPLACE, "replace"); - _F (NLM_F_EXCL, "excl"); - _F (NLM_F_CREATE, "create"); - _F (NLM_F_APPEND, "append"); - break; - case RTM_GETLINK: - case RTM_GETADDR: - case RTM_GETROUTE: - case RTM_DELQDISC: - case RTM_DELTFILTER: - _F (NLM_F_DUMP, "dump"); - _F (NLM_F_ROOT, "root"); - _F (NLM_F_MATCH, "match"); - _F (NLM_F_ATOMIC, "atomic"); - break; - } - -#undef _F - - if (flags_before != flags) - prefix = ";"; - nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags); - -flags_done: - - nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq); - - return b; -} - -static int -_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len, - const struct nla_policy *policy) -{ - return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy); -} -#define nla_parse(...) _nl_nla_parse(__VA_ARGS__) - -static int -_nl_nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], - int maxtype, const struct nla_policy *policy) -{ - return nlmsg_parse (nlh, hdrlen, tb, maxtype, (struct nla_policy *) policy); -} -#define nlmsg_parse(...) _nl_nlmsg_parse(__VA_ARGS__) - -static int -_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla, - const struct nla_policy *policy) -{ - return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy); -} -#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__) - /****************************************************************** * NMPObject/netlink functions ******************************************************************/ @@ -1292,6 +1161,7 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data) [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 }, [IFLA_IPTUN_FLOWINFO] = { .type = NLA_U32 }, [IFLA_IPTUN_PROTO] = { .type = NLA_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NLA_U32 }, }; struct nlattr *tb[IFLA_IPTUN_MAX + 1]; int err; @@ -1326,6 +1196,8 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data) } if (tb[IFLA_IPTUN_PROTO]) props->proto = nla_get_u8 (tb[IFLA_IPTUN_PROTO]); + if (tb[IFLA_IPTUN_FLAGS]) + props->flags = nla_get_u32 (tb[IFLA_IPTUN_FLAGS]); return obj; } @@ -1505,6 +1377,60 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data) /*****************************************************************************/ +static NMPObject * +_parse_lnk_tun (const char *kind, struct nlattr *info_data) +{ + static const struct nla_policy policy[IFLA_TUN_MAX + 1] = { + [IFLA_TUN_OWNER] = { .type = NLA_U32 }, + [IFLA_TUN_GROUP] = { .type = NLA_U32 }, + [IFLA_TUN_TYPE] = { .type = NLA_U8 }, + [IFLA_TUN_PI] = { .type = NLA_U8 }, + [IFLA_TUN_VNET_HDR] = { .type = NLA_U8 }, + [IFLA_TUN_PERSIST] = { .type = NLA_U8 }, + [IFLA_TUN_MULTI_QUEUE] = { .type = NLA_U8 }, + [IFLA_TUN_NUM_QUEUES] = { .type = NLA_U32 }, + [IFLA_TUN_NUM_DISABLED_QUEUES] = { .type = NLA_U32 }, + }; + struct nlattr *tb[IFLA_TUN_MAX + 1]; + int err; + NMPObject *obj; + NMPlatformLnkTun *props; + + if (!info_data || !nm_streq0 (kind, "tun")) + return NULL; + + err = nla_parse_nested (tb, IFLA_TUN_MAX, info_data, policy); + if (err < 0) + return NULL; + + if (!tb[IFLA_TUN_TYPE]) { + /* we require at least a type. */ + return NULL; + } + + obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_TUN, NULL); + props = &obj->lnk_tun; + + props->type = nla_get_u8 (tb[IFLA_TUN_TYPE]); + + props->pi = !!nla_get_u8_cond (tb, IFLA_TUN_PI, FALSE); + props->vnet_hdr = !!nla_get_u8_cond (tb, IFLA_TUN_VNET_HDR, FALSE); + props->multi_queue = !!nla_get_u8_cond (tb, IFLA_TUN_MULTI_QUEUE, FALSE); + props->persist = !!nla_get_u8_cond (tb, IFLA_TUN_PERSIST, FALSE); + + if (tb[IFLA_TUN_OWNER]) { + props->owner_valid = TRUE; + props->owner = nla_get_u32 (tb[IFLA_TUN_OWNER]); + } + if (tb[IFLA_TUN_GROUP]) { + props->group_valid = TRUE; + props->group = nla_get_u32 (tb[IFLA_TUN_GROUP]); + } + return obj; +} + +/*****************************************************************************/ + static gboolean _vlan_qos_mapping_from_nla (struct nlattr *nlattr, const NMVlanQosMapping **out_map, @@ -1936,6 +1862,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr case NM_LINK_TYPE_SIT: lnk_data = _parse_lnk_sit (nl_info_kind, nl_info_data); break; + case NM_LINK_TYPE_TUN: + lnk_data = _parse_lnk_tun (nl_info_kind, nl_info_data); + break; case NM_LINK_TYPE_VLAN: lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data); break; @@ -2665,8 +2594,7 @@ _nl_msg_new_link (int nlmsg_type, 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); + msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags); if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0) goto nla_put_failure; @@ -2708,8 +2636,6 @@ _nl_msg_new_address (int nlmsg_type, 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). */ @@ -2822,8 +2748,6 @@ _nl_msg_new_route (int nlmsg_type, nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE)); msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags); - if (!msg) - g_return_val_if_reached (NULL); if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0) goto nla_put_failure; @@ -2921,8 +2845,6 @@ _nl_msg_new_qdisc (int nlmsg_type, }; msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags); - if (!msg) - return NULL; if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0) goto nla_put_failure; @@ -2997,8 +2919,6 @@ _nl_msg_new_tfilter (int nlmsg_type, }; msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags); - if (!msg) - return NULL; if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0) goto nla_put_failure; @@ -3040,6 +2960,7 @@ typedef struct { DelayedActionWaitForNlResponseType response_type; gint64 timeout_abs_ns; WaitForNlResponseResult *out_seq_result; + char **out_errmsg; union { gint *out_refresh_all_in_progess; NMPObject **out_route_get; @@ -3500,7 +3421,7 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND, (int) data->response_type, data->seq_result ? ", " : "", - data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : ""); + data->seq_result ? wait_for_nl_response_to_string (data->seq_result, NULL, b, sizeof (b)) : ""); } else nm_utils_strbuf_append_str (&buf, &buf_size, " (any)"); break; @@ -3582,27 +3503,58 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform, } static void -delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform, - WaitForNlResponseResult fallback_result) +delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform, + WaitForNlResponseResult force_result, + guint32 *out_next_seq_number, + gint64 *out_next_timeout_abs_ns, + gint64 *p_now_ns) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + guint i; + guint32 next_seq_number = 0; + gint64 next_timeout_abs_ns = 0; + gint now_ns = 0; + + for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) { + const DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i); + + if (data->seq_result) + delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result); + else if ( p_now_ns + && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) >= data->timeout_abs_ns)) { + /* the caller can optionally check for timeout by providing a p_now_ns argument. */ + delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT); + } else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) + delayed_action_wait_for_nl_response_complete (platform, i, force_result); + else { + if ( next_seq_number == 0 + || next_timeout_abs_ns > data->timeout_abs_ns) { + next_seq_number = data->seq_number; + next_timeout_abs_ns = data->timeout_abs_ns; + } + i++; + } + } - if (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) { - const DelayedActionWaitForNlResponseData *data; - guint idx = priv->delayed_action.list_wait_for_nl_response->len - 1; - WaitForNlResponseResult r; - - data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx); + if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) { + nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)); + nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0); + } - /* prefer the result that we already have. */ - r = data->seq_result ? : fallback_result; + NM_SET_OUT (out_next_seq_number, next_seq_number); + NM_SET_OUT (out_next_timeout_abs_ns, next_timeout_abs_ns); + NM_SET_OUT (p_now_ns, now_ns); +} - delayed_action_wait_for_nl_response_complete (platform, idx, r); - } - } - 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_wait_for_nl_response_complete_all (NMPlatform *platform, + WaitForNlResponseResult fallback_result) +{ + delayed_action_wait_for_nl_response_complete_check (platform, + fallback_result, + NULL, + NULL, + NULL); } /*****************************************************************************/ @@ -3785,6 +3737,7 @@ static void delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult *out_seq_result, + char **out_errmsg, DelayedActionWaitForNlResponseType response_type, gpointer response_out_data) { @@ -3792,6 +3745,7 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform, .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, + .out_errmsg = out_errmsg, .response_type = response_type, .response.out_data = response_out_data, }; @@ -3976,40 +3930,46 @@ cache_on_change (NMPlatform *platform, && (obj_new && obj_new->_link.netlink.is_in_netlink) && (!obj_old || !obj_old->_link.netlink.is_in_netlink)) { - if (!obj_new->_link.netlink.lnk) { + gboolean re_request_link = FALSE; + const NMPlatformLnkTun *lnk_tun; + + if ( !obj_new->_link.netlink.lnk + && NM_IN_SET (obj_new->link.type, NM_LINK_TYPE_GRE, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_INFINIBAND, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_TUN, + NM_LINK_TYPE_VLAN, + NM_LINK_TYPE_VXLAN)) { /* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that * kernel didn't send this notification, thus when we first learn about a link * that lacks an lnk_data we re-request it again. * * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */ - switch (obj_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 (obj_new->link.ifindex)); - break; - default: - break; - } - } - if ( obj_new->link.type == NM_LINK_TYPE_VETH - && obj_new->link.parent == 0) { + re_request_link = TRUE; + } else if ( obj_new->link.type == NM_LINK_TYPE_TUN + && obj_new->_link.netlink.lnk + && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun) + && !lnk_tun->persist + && lnk_tun->pi + && !lnk_tun->vnet_hdr + && !lnk_tun->multi_queue + && !lnk_tun->owner_valid + && !lnk_tun->group_valid) { + /* kernel has/had a know issue that the first notification for TUN device would + * be sent with invalid parameters. The message looks like that kind, so refetch + * it. */ + re_request_link = TRUE; + } else if ( obj_new->link.type == NM_LINK_TYPE_VETH + && obj_new->link.parent == 0) { /* the initial notification when adding a veth pair can lack the parent/IFLA_LINK * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827). * Request it again. */ - delayed_action_schedule (platform, - DELAYED_ACTION_TYPE_REFRESH_LINK, - GINT_TO_POINTER (obj_new->link.ifindex)); - } - if ( obj_new->link.type == NM_LINK_TYPE_ETHERNET - && obj_new->link.addr.len == 0) { + re_request_link = TRUE; + } else if ( obj_new->link.type == NM_LINK_TYPE_ETHERNET + && obj_new->link.addr.len == 0) { /* Due to a kernel bug, we sometimes receive spurious NEWLINK * messages after a wifi interface has disappeared. Since the * link is not present anymore we can't determine its type and @@ -4017,6 +3977,9 @@ cache_on_change (NMPlatform *platform, * specified. Request the link again to check if it really * exists. https://bugzilla.redhat.com/show_bug.cgi?id=1302037 */ + re_request_link = TRUE; + } + if (re_request_link) { delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (obj_new->link.ifindex)); @@ -4067,8 +4030,10 @@ cache_on_change (NMPlatform *platform, static guint32 _nlh_seq_next_get (NMLinuxPlatformPrivate *priv) { - /* generate a new sequence number, but skip zero. */ - return priv->nlh_seq_next++ ?: priv->nlh_seq_next++; + /* generate a new sequence number, but never return zero. + * Wrapping numbers are not a problem, because we don't rely + * on strictly increasing sequence numbers. */ + return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next); } /** @@ -4079,12 +4044,13 @@ _nlh_seq_next_get (NMLinuxPlatformPrivate *priv) * @response_type: * @response_out_data: * - * Returns: 0 on success or a negative errno. Beware, it's an errno, not nlerror. + * Returns: 0 on success or a negative errno. */ static int _nl_send_nlmsghdr (NMPlatform *platform, struct nlmsghdr *nlhdr, WaitForNlResponseResult *out_seq_result, + char **out_errmsg, DelayedActionWaitForNlResponseType response_type, gpointer response_out_data) { @@ -4129,7 +4095,7 @@ again: } } - delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, + delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg, response_type, response_out_data); return 0; } @@ -4148,6 +4114,7 @@ static int _nl_send_nlmsg (NMPlatform *platform, struct nl_msg *nlmsg, WaitForNlResponseResult *out_seq_result, + char **out_errmsg, DelayedActionWaitForNlResponseType response_type, gpointer response_out_data) { @@ -4166,7 +4133,7 @@ _nl_send_nlmsg (NMPlatform *platform, return nle; } - delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, + delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg, response_type, response_out_data); return 0; } @@ -4204,7 +4171,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha 0, 0); if (nlmsg) { - nle = _nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)", ifindex, name ?: "", @@ -4263,8 +4230,6 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio * because we need the sequence number. */ nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP); - if (!nlmsg) - continue; if ( klass->obj_type == NMP_OBJECT_TYPE_QDISC || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) { @@ -4281,7 +4246,7 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio if (nle < 0) continue; - if (_nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) { + if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) { nm_assert (*out_refresh_all_in_progess > 0); *out_refresh_all_in_progess -= 1; } @@ -4325,7 +4290,7 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number) } static void -event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result) +event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result, const char *msg) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); DelayedActionWaitForNlResponseData *data; @@ -4344,11 +4309,13 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu /* 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. + /* we already saw an error for this sequence number. * Preserve it. */ } else if ( seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) data->seq_result = seq_result; + if (data->out_errmsg && !*data->out_errmsg) + *data->out_errmsg = g_strdup (msg); return; } } @@ -4391,7 +4358,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event obj = nmp_object_new_from_nl (platform, cache, msg, id_only); if (!obj) { _LOGT ("event-notification: %s: ignore", - _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr))); + nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr))); return; } @@ -4409,7 +4376,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event } _LOGT ("event-notification: %s%s: %s", - _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)), + nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)), is_dump ? ", in-dump" : "", nmp_object_to_string (obj, id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, @@ -4547,13 +4514,14 @@ do_add_link_with_lookup (NMPlatform *platform, { const NMPObject *obj = NULL; WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; int nle; char s_buf[256]; NMPCache *cache = nm_platform_get_cache (platform); event_handler_read_netlink (platform, FALSE); - nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)", name, @@ -4573,7 +4541,7 @@ do_add_link_with_lookup (NMPlatform *platform, "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))); + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf))); if (out_link) { obj = nmp_cache_lookup_link_full (cache, 0, name, FALSE, link_type, NULL, NULL); @@ -4590,6 +4558,7 @@ do_add_addrroute (NMPlatform *platform, gboolean suppress_netlink_failure) { WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; int nle; char s_buf[256]; @@ -4599,7 +4568,7 @@ do_add_addrroute (NMPlatform *platform, event_handler_read_netlink (platform, FALSE); - nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, @@ -4620,7 +4589,7 @@ do_add_addrroute (NMPlatform *platform, "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))); + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf))); if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) { /* In rare cases, the object is not yet ready as we received the ACK from @@ -4641,6 +4610,7 @@ static gboolean do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg) { WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; int nle; char s_buf[256]; gboolean success; @@ -4648,7 +4618,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg * event_handler_read_netlink (platform, FALSE); - nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, @@ -4680,7 +4650,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg * "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)), + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)), log_detail); if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id), @@ -4711,6 +4681,7 @@ do_change_link (NMPlatform *platform, nm_auto_pop_netns NMPNetns *netns = NULL; int nle; WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; char s_buf[256]; NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS; NMLogLevel log_level = LOGL_DEBUG; @@ -4726,7 +4697,7 @@ do_change_link (NMPlatform *platform, } retry: - nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { log_level = LOGL_ERR; log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)", @@ -4783,7 +4754,7 @@ out: "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)), + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)), log_detail); return result; } @@ -4860,6 +4831,12 @@ link_refresh (NMPlatform *platform, int ifindex) return !!nm_platform_link_get_obj (platform, ifindex, TRUE); } +static void +refresh_all (NMPlatform *platform, NMPObjectType obj_type) +{ + do_request_one_type (platform, obj_type); +} + static gboolean link_set_netns (NMPlatform *platform, int ifindex, @@ -5392,6 +5369,7 @@ link_ip6tnl_add (NMPlatform *platform, & IP6_FLOWINFO_TCLASS_MASK; NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo)); NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto); + NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLAGS, props->flags); nla_nest_end (nlmsg, data); nla_nest_end (nlmsg, info); @@ -5615,6 +5593,62 @@ nla_put_failure: } static gboolean +link_tun_add (NMPlatform *platform, + const char *name, + const NMPlatformLnkTun *props, + const NMPlatformLink **out_link, + int *out_fd) +{ + const NMPObject *obj; + struct ifreq ifr = { }; + nm_auto_close int fd = -1; + + nm_assert (NM_IN_SET (props->type, IFF_TAP, IFF_TUN)); + nm_assert (props->persist || out_fd); + + fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC); + if (fd < 0) + return FALSE; + + nm_utils_ifname_cpy (ifr.ifr_name, name); + ifr.ifr_flags = ((short) props->type) + | ((short) IFF_TUN_EXCL) + | (!props->pi ? (short) IFF_NO_PI : (short) 0) + | ( props->vnet_hdr ? (short) IFF_VNET_HDR : (short) 0) + | ( props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0); + if (ioctl (fd, TUNSETIFF, &ifr)) + return FALSE; + + if (props->owner_valid) { + if (ioctl (fd, TUNSETOWNER, (uid_t) props->owner)) + return FALSE; + } + + if (props->group_valid) { + if (ioctl (fd, TUNSETGROUP, (gid_t) props->group)) + return FALSE; + } + + if (props->persist) { + if (ioctl (fd, TUNSETPERSIST, 1)) + return FALSE; + } + + do_request_link (platform, 0, name); + obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform), + 0, name, FALSE, + NM_LINK_TYPE_TUN, + NULL, NULL); + + if (!obj) + return FALSE; + + NM_SET_OUT (out_link, &obj->link); + NM_SET_OUT (out_fd, nm_steal_fd (&fd)); + return TRUE; +} + +static gboolean link_vxlan_add (NMPlatform *platform, const char *name, const NMPlatformLnkVxlan *props, @@ -5854,64 +5888,6 @@ link_vlan_change (NMPlatform *platform, return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS; } -static int -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) -{ - const NMPObject *obj; - struct ifreq ifr = { }; - int fd; - - fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC); - if (fd < 0) - return FALSE; - - nm_utils_ifname_cpy (ifr.ifr_name, name); - ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN; - - 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 (ioctl (fd, TUNSETIFF, &ifr)) { - nm_close (fd); - return FALSE; - } - - if (owner >= 0 && owner < G_MAXINT32) { - if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) { - nm_close (fd); - return FALSE; - } - } - - if (group >= 0 && group < G_MAXINT32) { - if (ioctl (fd, TUNSETGROUP, (gid_t) group)) { - nm_close (fd); - return FALSE; - } - } - - if (ioctl (fd, TUNSETPERSIST, 1)) { - nm_close (fd); - return FALSE; - } - do_request_link (platform, 0, name); - obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform), - 0, name, FALSE, - tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, - NULL, NULL); - if (out_link) - *out_link = obj ? &obj->link : NULL; - - nm_close (fd); - return !!obj; -} - static gboolean link_enslave (NMPlatform *platform, int master, int slave) { @@ -6453,7 +6429,7 @@ ip_route_get (NMPlatform *platform, } seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; - nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route); + nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, NULL, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route); if (nle < 0) { _LOGE ("get-route: failure sending netlink request \"%s\" (%d)", g_strerror (-nle), -nle); @@ -6492,6 +6468,7 @@ qdisc_add (NMPlatform *platform, const NMPlatformQdisc *qdisc) { WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; int nle; char s_buf[256]; nm_auto_nlmsg struct nl_msg *msg = NULL; @@ -6500,7 +6477,7 @@ qdisc_add (NMPlatform *platform, event_handler_read_netlink (platform, FALSE); - nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nl_geterror (nle), -nle); @@ -6515,7 +6492,7 @@ qdisc_add (NMPlatform *platform, ? LOGL_DEBUG : LOGL_WARN, "do-add-qdisc: %s", - wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf))); + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf))); if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) return NM_PLATFORM_ERROR_SUCCESS; @@ -6531,6 +6508,7 @@ tfilter_add (NMPlatform *platform, const NMPlatformTfilter *tfilter) { WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN; + gs_free char *errmsg = NULL; int nle; char s_buf[256]; nm_auto_nlmsg struct nl_msg *msg = NULL; @@ -6539,7 +6517,7 @@ tfilter_add (NMPlatform *platform, event_handler_read_netlink (platform, FALSE); - nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); + nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL); if (nle < 0) { _LOGE ("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nl_geterror (nle), -nle); @@ -6554,7 +6532,7 @@ tfilter_add (NMPlatform *platform, ? LOGL_DEBUG : LOGL_WARN, "do-add-tfilter: %s", - wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf))); + wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf))); if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) return NM_PLATFORM_ERROR_SUCCESS; @@ -6585,15 +6563,12 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); struct nl_sock *sk = priv->nlh; - int n, err = 0, multipart = 0, interrupted = 0; + int n; + int err = 0; + gboolean multipart = 0; + gboolean interrupted = FALSE; 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}; nm_auto_free struct ucred *creds = NULL; nm_auto_free unsigned char *buf = NULL; @@ -6601,56 +6576,28 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events) continue_reading: g_clear_pointer (&buf, free); g_clear_pointer (&creds, free); - errno = 0; n = nl_recv (sk, &nla, &buf, &creds); if (n <= 0) { - /* workaround libnl3 <= 3.2.15 returning danling pointers in case nl_recv() - * fails. Fixed by libnl3 69468517d0de1675d80f24661ff57a5dbac7275c. */ - buf = NULL; - creds = NULL; - } - - switch (n) { - case 0: - /* Work around a libnl bug fixed in 3.2.22 (375a6294) */ - if (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); - n = -NLE_AGAIN; - } - break; - case -NLE_MSG_TRUNC: { - int buf_size; - - /* the message receive buffer was too small. We lost one message, which - * is unfortunate. Try to double the buffer size for the next time. */ - buf_size = nl_socket_get_msg_buf_size (sk); - if (buf_size < 512*1024) { - buf_size *= 2; - _LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size); - if (nl_socket_set_msg_buf_size (sk, buf_size) < 0) - nm_assert_not_reached (); - if (!handle_events) - goto continue_reading; - } - n = -_NLE_MSG_TRUNC; - break; - } - case -NLE_NOMEM: - if (errno == ENOBUFS) { - /* we are very much interested in a overrun of the receive buffer. - * nl_recv() maps all kinds of errors to NLE_NOMEM, so check also - * for errno explicitly. And if so, hack our own return code to signal - * the overrun. */ - n = -_NLE_NM_NOBUFS; + + if (n == -NLE_MSG_TRUNC) { + int buf_size; + + /* the message receive buffer was too small. We lost one message, which + * is unfortunate. Try to double the buffer size for the next time. */ + buf_size = nl_socket_get_msg_buf_size (sk); + if (buf_size < 512*1024) { + buf_size *= 2; + _LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size); + if (nl_socket_set_msg_buf_size (sk, buf_size) < 0) + nm_assert_not_reached (); + if (!handle_events) + goto continue_reading; + } } - break; - } - if (n <= 0) return n; + } hdr = (struct nlmsghdr *) buf; while (nlmsg_ok (hdr, n)) { @@ -6659,12 +6606,9 @@ continue_reading: gboolean process_valid_msg = FALSE; guint32 seq_number; char buf_nlmsghdr[400]; + const char *extack_msg = NULL; - msg = nlmsg_convert (hdr); - if (!msg) { - err = -NLE_NOMEM; - goto out; - } + msg = nlmsg_alloc_convert (hdr); nlmsg_set_proto (msg, NETLINK_ROUTE); nlmsg_set_src (msg, &nla); @@ -6679,13 +6623,13 @@ continue_reading: } _LOGt ("netlink: recvmsg: new message %s", - _nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr))); + nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr))); if (creds) nlmsg_set_creds (msg, creds); if (hdr->nlmsg_flags & NLM_F_MULTI) - multipart = 1; + multipart = TRUE; if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) { /* @@ -6693,7 +6637,7 @@ continue_reading: * all messages until a NLMSG_DONE is * received and report the inconsistency. */ - interrupted = 1; + interrupted = TRUE; } /* Other side wishes to see an ack for this message */ @@ -6708,7 +6652,7 @@ continue_reading: * 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; + multipart = FALSE; seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; } else if (hdr->nlmsg_type == NLMSG_NOOP) { /* Message to be ignored, the default action is to @@ -6735,10 +6679,28 @@ continue_reading: } else if (e->error) { int errsv = e->error > 0 ? e->error : -e->error; + if ( NM_FLAGS_HAS (hdr->nlmsg_flags, NLM_F_ACK_TLVS) + && hdr->nlmsg_len >= sizeof (*e) + e->msg.nlmsg_len) { + static const struct nla_policy policy[NLMSGERR_ATTR_MAX + 1] = { + [NLMSGERR_ATTR_MSG] = { .type = NLA_STRING }, + [NLMSGERR_ATTR_OFFS] = { .type = NLA_U32 }, + }; + struct nlattr *tb[NLMSGERR_ATTR_MAX + 1]; + struct nlattr *tlvs; + + tlvs = (struct nlattr *) ((char *) e + sizeof (*e) + e->msg.nlmsg_len - NLMSG_HDRLEN); + if (!nla_parse (tb, NLMSGERR_ATTR_MAX, tlvs, + hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len, policy)) { + if (tb[NLMSGERR_ATTR_MSG]) + extack_msg = nla_get_string (tb[NLMSGERR_ATTR_MSG]); + } + } + /* Error message reported back from kernel. */ - _LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d", + _LOGD ("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d", strerror (errsv), errsv, + NM_PRINT_FMT_QUOTED (extack_msg, " \"", extack_msg, "\"", ""), nlmsg_hdr (msg)->nlmsg_seq); seq_result = -errsv; } else @@ -6767,7 +6729,7 @@ continue_reading: seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK; } - event_seq_check (platform, seq_number, seq_result); + event_seq_check (platform, seq_number, seq_result, extack_msg); if (abort_parsing) goto stop; @@ -6787,9 +6749,9 @@ stop: * Repeat reading. */ goto continue_reading; } -out: + if (interrupted) - err = -NLE_DUMP_INTR; + return -NLE_DUMP_INTR; return err; } @@ -6800,46 +6762,50 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks) { nm_auto_pop_netns NMPNetns *netns = NULL; NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); - int r, nle; + int r; struct pollfd pfd; gboolean any = FALSE; - gint64 now_ns; int timeout_ms; - guint i; struct { guint32 seq_number; gint64 timeout_abs_ns; - } data_next; + gint64 now_ns; + } next; - if (!nm_platform_netns_push (platform, &netns)) + if (!nm_platform_netns_push (platform, &netns)) { + delayed_action_wait_for_nl_response_complete_all (platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS); return FALSE; + } - while (TRUE) { - - while (TRUE) { + for (;;) { + for (;;) { + int nle; nle = event_handler_recvmsgs (platform, TRUE); if (nle < 0) { switch (nle) { - case -NLE_AGAIN: + case -EAGAIN: 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_MSG_TRUNC: - case -_NLE_NM_NOBUFS: + case -NLE_MSG_TRUNC: + case -ENOBUFS: _LOGI ("netlink: read: %s. Need to resynchronize platform cache", ({ const char *_reason = "unknown"; switch (nle) { - case -_NLE_MSG_TRUNC: _reason = "message truncated"; break; - case -_NLE_NM_NOBUFS: _reason = "too many netlink events"; break; + case -NLE_MSG_TRUNC: _reason = "message truncated"; break; + case -ENOBUFS: _reason = "too many netlink events"; break; } _reason; })); event_handler_recvmsgs (platform, FALSE); - delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC); + delayed_action_wait_for_nl_response_complete_all (platform, + WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC); + delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES | @@ -6863,39 +6829,23 @@ after_read: if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) return any; - 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++; - - 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; - } - } - } + delayed_action_wait_for_nl_response_complete_check (platform, + WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN, + &next.seq_number, + &next.timeout_abs_ns, + &next.now_ns); if ( !wait_for_acks || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) return any; - nm_assert (data_next.seq_number); - nm_assert (data_next.timeout_abs_ns > 0); - nm_assert (now_ns > 0); + nm_assert (next.seq_number); + nm_assert (next.now_ns > 0); + nm_assert (next.timeout_abs_ns > next.now_ns); - _LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number); + _LOGT ("netlink: read: wait for ACK for sequence number %u...", next.seq_number); - timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000); + timeout_ms = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NS_PER_SECOND / 1000); memset (&pfd, 0, sizeof (pfd)); pfd.fd = nl_socket_get_fd (priv->nlh); @@ -6906,6 +6856,7 @@ after_read: /* timeout and there is nothing to read. */ goto after_read; } + if (r < 0) { int errsv = errno; @@ -7047,11 +6998,10 @@ nm_linux_platform_init (NMLinuxPlatform *self) { NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (self); - priv->nlh_seq_next = 1; 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); + priv->wifi_data = g_hash_table_new_full (nm_direct_hash, NULL, NULL, (GDestroyNotify) wifi_utils_unref); } static void @@ -7097,6 +7047,10 @@ constructed (GObject *_object) nle = nl_socket_set_buffer_size (priv->nlh, 8*1024*1024, 0); g_assert (!nle); + nle = nl_socket_set_ext_ack (priv->nlh, TRUE); + if (nle) + _LOGD ("could not enable extended acks on netlink socket"); + /* explicitly set the msg buffer size and disable MSG_PEEK. * If we later encounter NLE_MSG_TRUNC, we will adjust the buffer size. */ nl_socket_disable_msg_peek (priv->nlh); @@ -7176,7 +7130,8 @@ dispose (GObject *object) _LOGD ("dispose"); - delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING); + 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); @@ -7226,6 +7181,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->link_add = link_add; platform_class->link_delete = link_delete; + platform_class->refresh_all = refresh_all; platform_class->link_refresh = link_refresh; platform_class->link_set_netns = link_set_netns; @@ -7264,8 +7220,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) platform_class->link_vlan_change = link_vlan_change; platform_class->link_vxlan_add = link_vxlan_add; - platform_class->tun_add = tun_add; - platform_class->infiniband_partition_add = infiniband_partition_add; platform_class->infiniband_partition_delete = infiniband_partition_delete; @@ -7290,6 +7244,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass) 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->link_tun_add = link_tun_add; platform_class->object_delete = object_delete; platform_class->ip4_address_add = ip4_address_add; diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c new file mode 100644 index 00000000..4cb19780 --- /dev/null +++ b/src/platform/nm-netlink.c @@ -0,0 +1,1491 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform.c - Handle runtime kernel networking configuration + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-netlink.h" + +#include <unistd.h> +#include <fcntl.h> + +/*****************************************************************************/ + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +/*****************************************************************************/ + +#define NL_SOCK_PASSCRED (1<<1) +#define NL_MSG_PEEK (1<<3) +#define NL_MSG_PEEK_EXPLICIT (1<<4) +#define NL_NO_AUTO_ACK (1<<5) + +#ifndef NETLINK_EXT_ACK +#define NETLINK_EXT_ACK 11 +#endif + +#define NL_MSG_CRED_PRESENT 1 + +struct nl_msg { + int nm_protocol; + int nm_flags; + struct sockaddr_nl nm_src; + struct sockaddr_nl nm_dst; + struct ucred nm_creds; + struct nlmsghdr * nm_nlh; + size_t nm_size; + int nm_refcnt; +}; + +struct nl_sock { + struct sockaddr_nl s_local; + struct sockaddr_nl s_peer; + int s_fd; + int s_proto; + unsigned int s_seq_next; + unsigned int s_seq_expect; + int s_flags; + size_t s_bufsize; +}; + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int, + NM_UTILS_LOOKUP_DEFAULT (NULL), + NM_UTILS_LOOKUP_ITEM (NLE_UNSPEC, "NLE_UNSPEC"), + NM_UTILS_LOOKUP_ITEM (NLE_BUG, "NLE_BUG"), + NM_UTILS_LOOKUP_ITEM (NLE_NATIVE_ERRNO, "NLE_NATIVE_ERRNO"), + + NM_UTILS_LOOKUP_ITEM (NLE_ATTRSIZE, "NLE_ATTRSIZE"), + NM_UTILS_LOOKUP_ITEM (NLE_BAD_SOCK, "NLE_BAD_SOCK"), + NM_UTILS_LOOKUP_ITEM (NLE_DUMP_INTR, "NLE_DUMP_INTR"), + NM_UTILS_LOOKUP_ITEM (NLE_MSG_OVERFLOW, "NLE_MSG_OVERFLOW"), + NM_UTILS_LOOKUP_ITEM (NLE_MSG_TOOSHORT, "NLE_MSG_TOOSHORT"), + NM_UTILS_LOOKUP_ITEM (NLE_MSG_TRUNC, "NLE_MSG_TRUNC"), + NM_UTILS_LOOKUP_ITEM (NLE_SEQ_MISMATCH, "NLE_SEQ_MISMATCH"), +) + +const char * +nl_geterror (int err) +{ + const char *s; + + err = nl_errno (err); + + if (err >= _NLE_BASE) { + s = _geterror (err); + if (s) + return s; + } + return g_strerror (err); +} + +/*****************************************************************************/ + +NM_UTILS_ENUM2STR_DEFINE (nl_nlmsgtype2str, int, + NM_UTILS_ENUM2STR (NLMSG_NOOP, "NOOP"), + NM_UTILS_ENUM2STR (NLMSG_ERROR, "ERROR"), + NM_UTILS_ENUM2STR (NLMSG_DONE, "DONE"), + NM_UTILS_ENUM2STR (NLMSG_OVERRUN, "OVERRUN"), +); + +NM_UTILS_FLAGS2STR_DEFINE (nl_nlmsg_flags2str, int, + NM_UTILS_FLAGS2STR (NLM_F_REQUEST, "REQUEST"), + NM_UTILS_FLAGS2STR (NLM_F_MULTI, "MULTI"), + NM_UTILS_FLAGS2STR (NLM_F_ACK, "ACK"), + NM_UTILS_FLAGS2STR (NLM_F_ECHO, "ECHO"), + NM_UTILS_FLAGS2STR (NLM_F_ROOT, "ROOT"), + NM_UTILS_FLAGS2STR (NLM_F_MATCH, "MATCH"), + NM_UTILS_FLAGS2STR (NLM_F_ATOMIC, "ATOMIC"), + NM_UTILS_FLAGS2STR (NLM_F_REPLACE, "REPLACE"), + NM_UTILS_FLAGS2STR (NLM_F_EXCL, "EXCL"), + NM_UTILS_FLAGS2STR (NLM_F_CREATE, "CREATE"), + NM_UTILS_FLAGS2STR (NLM_F_APPEND, "APPEND"), +); + +/*****************************************************************************/ + +const char * +nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len) +{ + const char *b; + const char *s; + guint flags, flags_before; + const char *prefix; + + if (!nm_utils_to_string_buffer_init_null (hdr, &buf, &len)) + return buf; + + b = buf; + + switch (hdr->nlmsg_type) { + case RTM_NEWLINK: s = "RTM_NEWLINK"; break; + case RTM_DELLINK: s = "RTM_DELLINK"; break; + case RTM_NEWADDR: s = "RTM_NEWADDR"; break; + case RTM_DELADDR: s = "RTM_DELADDR"; break; + case RTM_NEWROUTE: s = "RTM_NEWROUTE"; break; + case RTM_DELROUTE: s = "RTM_DELROUTE"; break; + case RTM_NEWQDISC: s = "RTM_NEWQDISC"; break; + case RTM_DELQDISC: s = "RTM_DELQDISC"; break; + case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break; + case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break; + case NLMSG_NOOP: s = "NLMSG_NOOP"; break; + case NLMSG_ERROR: s = "NLMSG_ERROR"; break; + case NLMSG_DONE: s = "NLMSG_DONE"; break; + case NLMSG_OVERRUN: s = "NLMSG_OVERRUN"; break; + default: s = NULL; break; + } + + if (s) + nm_utils_strbuf_append_str (&buf, &len, s); + else + nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type); + + flags = hdr->nlmsg_flags; + + if (!flags) { + nm_utils_strbuf_append_str (&buf, &len, ", flags 0"); + goto flags_done; + } + +#define _F(f, n) \ + G_STMT_START { \ + if (NM_FLAGS_ALL (flags, f)) { \ + flags &= ~(f); \ + nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \ + if (!flags) \ + goto flags_done; \ + prefix = ","; \ + } \ + } G_STMT_END + + prefix = ", flags "; + flags_before = flags; + _F (NLM_F_REQUEST, "request"); + _F (NLM_F_MULTI, "multi"); + _F (NLM_F_ACK, "ack"); + _F (NLM_F_ECHO, "echo"); + _F (NLM_F_DUMP_INTR, "dump_intr"); + _F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered"); + + if (flags_before != flags) + prefix = ";"; + + switch (hdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_NEWADDR: + case RTM_NEWROUTE: + case RTM_NEWQDISC: + case RTM_NEWTFILTER: + _F (NLM_F_REPLACE, "replace"); + _F (NLM_F_EXCL, "excl"); + _F (NLM_F_CREATE, "create"); + _F (NLM_F_APPEND, "append"); + break; + case RTM_GETLINK: + case RTM_GETADDR: + case RTM_GETROUTE: + case RTM_DELQDISC: + case RTM_DELTFILTER: + _F (NLM_F_DUMP, "dump"); + _F (NLM_F_ROOT, "root"); + _F (NLM_F_MATCH, "match"); + _F (NLM_F_ATOMIC, "atomic"); + break; + } + +#undef _F + + if (flags_before != flags) + prefix = ";"; + nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags); + +flags_done: + + nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq); + + return b; +} + +/*****************************************************************************/ + +struct nlmsghdr * +nlmsg_hdr (struct nl_msg *n) +{ + return n->nm_nlh; +} + +void * +nlmsg_reserve (struct nl_msg *n, size_t len, int pad) +{ + char *buf = (char *) n->nm_nlh; + size_t nlmsg_len = n->nm_nlh->nlmsg_len; + size_t tlen; + + if (len > n->nm_size) + return NULL; + + tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len; + + if ((tlen + nlmsg_len) > n->nm_size) + return NULL; + + buf += nlmsg_len; + n->nm_nlh->nlmsg_len += tlen; + + if (tlen > len) + memset(buf + len, 0, tlen - len); + + return buf; +} + +/*****************************************************************************/ + +static int + get_default_page_size (void) +{ + static int val = 0; + int v; + + if (G_UNLIKELY (val == 0)) { + v = getpagesize (); + g_assert (v > 0); + val = v; + } + return val; +} + +struct nlattr * +nla_reserve (struct nl_msg *msg, int attrtype, int attrlen) +{ + struct nlattr *nla; + int tlen; + + if (attrlen < 0) + return NULL; + + tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen); + + if (tlen > msg->nm_size) + return NULL; + + nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh); + nla->nla_type = attrtype; + nla->nla_len = nla_attr_size(attrlen); + + if (attrlen) + memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); + msg->nm_nlh->nlmsg_len = tlen; + + return nla; +} + +struct nl_msg * +nlmsg_alloc_size (size_t len) +{ + struct nl_msg *nm; + + if (len < sizeof (struct nlmsghdr)) + len = sizeof (struct nlmsghdr); + + nm = g_slice_new0 (struct nl_msg); + + nm->nm_refcnt = 1; + nm->nm_protocol = -1; + nm->nm_size = len; + nm->nm_nlh = g_malloc0 (len); + nm->nm_nlh->nlmsg_len = nlmsg_total_size (0); + return nm; +} + +/** + * Allocate a new netlink message with the default maximum payload size. + * + * Allocates a new netlink message without any further payload. The + * maximum payload size defaults to PAGESIZE or as otherwise specified + * with nlmsg_set_default_size(). + * + * @return Newly allocated netlink message or NULL. + */ +struct nl_msg * +nlmsg_alloc (void) +{ + return nlmsg_alloc_size (get_default_page_size ()); +} + +/** + * Allocate a new netlink message with maximum payload size specified. + */ +struct nl_msg * +nlmsg_alloc_inherit (struct nlmsghdr *hdr) +{ + struct nl_msg *nm; + + nm = nlmsg_alloc (); + if (hdr) { + struct nlmsghdr *new = nm->nm_nlh; + + new->nlmsg_type = hdr->nlmsg_type; + new->nlmsg_flags = hdr->nlmsg_flags; + new->nlmsg_seq = hdr->nlmsg_seq; + new->nlmsg_pid = hdr->nlmsg_pid; + } + + return nm; +} + +struct nl_msg * +nlmsg_alloc_convert (struct nlmsghdr *hdr) +{ + struct nl_msg *nm; + + nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len)); + memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len); + return nm; +} + +struct nl_msg * +nlmsg_alloc_simple (int nlmsgtype, int flags) +{ + struct nlmsghdr nlh = { + .nlmsg_type = nlmsgtype, + .nlmsg_flags = flags, + }; + + return nlmsg_alloc_inherit (&nlh); +} + +int +nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad) +{ + void *tmp; + + tmp = nlmsg_reserve (n, len, pad); + if (tmp == NULL) + return -ENOMEM; + + memcpy(tmp, data, len); + return 0; +} + +int +nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], + int maxtype, const struct nla_policy *policy) +{ + if (!nlmsg_valid_hdr(nlh, hdrlen)) + return -NLE_MSG_TOOSHORT; + + return nla_parse (tb, maxtype, nlmsg_attrdata(nlh, hdrlen), + nlmsg_attrlen(nlh, hdrlen), policy); +} + +struct nlmsghdr * +nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq, + int type, int payload, int flags) +{ + struct nlmsghdr *nlh; + + if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN) + g_return_val_if_reached (NULL); + + nlh = (struct nlmsghdr *) n->nm_nlh; + nlh->nlmsg_type = type; + nlh->nlmsg_flags = flags; + nlh->nlmsg_pid = pid; + nlh->nlmsg_seq = seq; + + if (payload > 0 && + nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL) + return NULL; + + return nlh; +} + +uint64_t +nla_get_u64 (const struct nlattr *nla) +{ + uint64_t tmp = 0; + + if (nla && nla_len(nla) >= sizeof (tmp)) + memcpy(&tmp, nla_data(nla), sizeof (tmp)); + + return tmp; +} + +size_t +nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize) +{ + size_t srclen = nla_len(nla); + const char *src = nla_data(nla); + + if (srclen > 0 && src[srclen - 1] == '\0') + srclen--; + + if (dstsize > 0) { + size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen; + + memset(dst, 0, dstsize); + memcpy(dst, src, len); + } + + return srclen; +} + +int +nla_memcpy (void *dest, const struct nlattr *src, int count) +{ + int minlen; + + if (!src) + return 0; + + minlen = NM_MIN (count, (int) nla_len (src)); + memcpy(dest, nla_data(src), minlen); + + return minlen; +} + +int +nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data) +{ + struct nlattr *nla; + + nla = nla_reserve(msg, attrtype, datalen); + if (!nla) { + if (datalen < 0) + g_return_val_if_reached (-NLE_BUG); + + return -ENOMEM; + } + + if (datalen > 0) + memcpy (nla_data(nla), data, datalen); + + return 0; +} + +struct nlattr * +nla_find (const struct nlattr *head, int len, int attrtype) +{ + const struct nlattr *nla; + int rem; + + nla_for_each_attr (nla, head, len, rem) { + if (nla_type (nla) == attrtype) + return (struct nlattr*)nla; + } + + return NULL; +} + +void +nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr) +{ + ssize_t len; + + len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr; + if (len < 0) + g_return_if_reached (); + else if (len > 0) { + msg->nm_nlh->nlmsg_len -= len; + memset(nlmsg_tail(msg->nm_nlh), 0, len); + } +} + +struct nlattr * +nla_nest_start (struct nl_msg *msg, int attrtype) +{ + struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh); + + if (nla_put(msg, attrtype, 0, NULL) < 0) + return NULL; + + return start; +} + +static int +_nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty) +{ + size_t pad, len; + + len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start; + + if ( len > USHRT_MAX + || (!keep_empty && len == NLA_HDRLEN)) { + /* + * Max nlattr size exceeded or empty nested attribute, trim the + * attribute header again + */ + nla_nest_cancel(msg, start); + + /* Return error only if nlattr size was exceeded */ + return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE; + } + + start->nla_len = len; + + pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len; + if (pad > 0) { + /* + * Data inside attribute does not end at a alignment boundry. + * Pad accordingly and accoun for the additional space in + * the message. nlmsg_reserve() may never fail in this situation, + * the allocate message buffer must be a multiple of NLMSG_ALIGNTO. + */ + if (!nlmsg_reserve(msg, pad, 0)) + g_return_val_if_reached (-NLE_BUG); + } + + return 0; +} + +int +nla_nest_end (struct nl_msg *msg, struct nlattr *start) +{ + return _nest_end (msg, start, 0); +} + +static const uint16_t nla_attr_minlen[NLA_TYPE_MAX+1] = { + [NLA_U8] = sizeof (uint8_t), + [NLA_U16] = sizeof (uint16_t), + [NLA_U32] = sizeof (uint32_t), + [NLA_U64] = sizeof (uint64_t), + [NLA_STRING] = 1, + [NLA_FLAG] = 0, +}; + +static int +validate_nla (const struct nlattr *nla, int maxtype, + const struct nla_policy *policy) +{ + const struct nla_policy *pt; + unsigned int minlen = 0; + int type = nla_type(nla); + + if (type < 0 || type > maxtype) + return 0; + + pt = &policy[type]; + + if (pt->type > NLA_TYPE_MAX) + g_return_val_if_reached (-NLE_BUG); + + if (pt->minlen) + minlen = pt->minlen; + else if (pt->type != NLA_UNSPEC) + minlen = nla_attr_minlen[pt->type]; + + if (nla_len(nla) < minlen) + return -NLE_UNSPEC; + + if (pt->maxlen && nla_len(nla) > pt->maxlen) + return -NLE_UNSPEC; + + if (pt->type == NLA_STRING) { + const char *data = nla_data(nla); + if (data[nla_len(nla) - 1] != '\0') + return -NLE_UNSPEC; + } + + return 0; +} + +int +nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len, + const struct nla_policy *policy) +{ + struct nlattr *nla; + int rem, err; + + memset(tb, 0, sizeof (struct nlattr *) * (maxtype + 1)); + + nla_for_each_attr(nla, head, len, rem) { + int type = nla_type(nla); + + if (type > maxtype) + continue; + + if (policy) { + err = validate_nla(nla, maxtype, policy); + if (err < 0) + goto errout; + } + + tb[type] = nla; + } + + err = 0; +errout: + return err; +} + +/*****************************************************************************/ + +void nlmsg_free (struct nl_msg *msg) +{ + if (!msg) + return; + + if (msg->nm_refcnt < 1) + g_return_if_reached (); + + msg->nm_refcnt--; + + if (msg->nm_refcnt <= 0) { + g_free (msg->nm_nlh); + g_slice_free (struct nl_msg, msg); + } +} + +int +nlmsg_get_proto (struct nl_msg *msg) +{ + return msg->nm_protocol; +} + +void +nlmsg_set_proto (struct nl_msg *msg, int protocol) +{ + msg->nm_protocol = protocol; +} + +void +nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr) +{ + memcpy (&msg->nm_src, addr, sizeof (*addr)); +} + +struct ucred * +nlmsg_get_creds (struct nl_msg *msg) +{ + if (msg->nm_flags & NL_MSG_CRED_PRESENT) + return &msg->nm_creds; + return NULL; +} + +void +nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds) +{ + memcpy (&msg->nm_creds, creds, sizeof (*creds)); + msg->nm_flags |= NL_MSG_CRED_PRESENT; +} + +/*****************************************************************************/ + +void * +genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family, + int hdrlen, int flags, uint8_t cmd, uint8_t version) +{ + struct nlmsghdr *nlh; + struct genlmsghdr hdr = { + .cmd = cmd, + .version = version, + }; + + nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags); + if (nlh == NULL) + return NULL; + + memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr)); + + return (char *) nlmsg_data (nlh) + GENL_HDRLEN; +} + +void * +genlmsg_data (const struct genlmsghdr *gnlh) +{ + return ((unsigned char *) gnlh + GENL_HDRLEN); +} + +void * +genlmsg_user_hdr (const struct genlmsghdr *gnlh) +{ + return genlmsg_data (gnlh); +} + +struct genlmsghdr * +genlmsg_hdr (struct nlmsghdr *nlh) +{ + return nlmsg_data (nlh); +} + +void * +genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen) +{ + return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen); +} + +struct nlattr * +genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen) +{ + return genlmsg_user_data (gnlh, hdrlen); +} + +int +genlmsg_len (const struct genlmsghdr *gnlh) +{ + const struct nlmsghdr *nlh; + + nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN); + return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN); +} + +int +genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen) +{ + return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen); +} + +int +genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen) +{ + struct genlmsghdr *ghdr; + + if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN)) + return 0; + + ghdr = nlmsg_data (nlh); + if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen)) + return 0; + + return 1; +} + +int +genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], + int maxtype, const struct nla_policy *policy) +{ + struct genlmsghdr *ghdr; + + if (!genlmsg_valid_hdr (nlh, hdrlen)) + return -NLE_MSG_TOOSHORT; + + ghdr = nlmsg_data (nlh); + return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen), + genlmsg_attrlen (ghdr, hdrlen), policy); +} + +static int +_genl_parse_getfamily (struct nl_msg *msg, void *arg) +{ + static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = { + [CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 }, + [CTRL_ATTR_FAMILY_NAME] = { .type = NLA_STRING, + .maxlen = GENL_NAMSIZ }, + [CTRL_ATTR_VERSION] = { .type = NLA_U32 }, + [CTRL_ATTR_HDRSIZE] = { .type = NLA_U32 }, + [CTRL_ATTR_MAXATTR] = { .type = NLA_U32 }, + [CTRL_ATTR_OPS] = { .type = NLA_NESTED }, + [CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED }, + }; + struct nlattr *tb[CTRL_ATTR_MAX+1]; + struct nlmsghdr *nlh = nlmsg_hdr (msg); + gint32 *response_data = arg; + + if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy)) + return NL_SKIP; + + if (tb[CTRL_ATTR_FAMILY_ID]) + *response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]); + + return NL_STOP; +} + +int +genl_ctrl_resolve (struct nl_sock *sk, const char *name) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + int result = -ENOMEM; + gint32 response_data = -1; + const struct nl_cb cb = { + .valid_cb = _genl_parse_getfamily, + .valid_arg = &response_data, + }; + + msg = nlmsg_alloc (); + + if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, + 0, 0, CTRL_CMD_GETFAMILY, 1)) + goto out; + + if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0) + goto out; + + result = nl_send_auto (sk, msg); + if (result < 0) + goto out; + + result = nl_recvmsgs (sk, &cb); + if (result < 0) + goto out; + + /* If search was successful, request may be ACKed after data */ + result = nl_wait_for_ack (sk, NULL); + if (result < 0) + goto out; + + if (response_data > 0) + result = response_data; + else + result = -ENOENT; + +out: + return result; +} + +/*****************************************************************************/ + +struct nl_sock * +nl_socket_alloc (void) +{ + struct nl_sock *sk; + + sk = g_slice_new0 (struct nl_sock); + + sk->s_fd = -1; + sk->s_local.nl_family = AF_NETLINK; + sk->s_peer.nl_family = AF_NETLINK; + sk->s_seq_expect = sk->s_seq_next = time(NULL); + + return sk; +} + +void +nl_socket_free (struct nl_sock *sk) +{ + if (!sk) + return; + + if (sk->s_fd >= 0) + nm_close (sk->s_fd); + g_slice_free (struct nl_sock, sk); +} + +int +nl_socket_get_fd (const struct nl_sock *sk) +{ + return sk->s_fd; +} + +uint32_t +nl_socket_get_local_port (const struct nl_sock *sk) +{ + return sk->s_local.nl_pid; +} + +size_t +nl_socket_get_msg_buf_size (struct nl_sock *sk) +{ + return sk->s_bufsize; +} + +int +nl_socket_set_passcred (struct nl_sock *sk, int state) +{ + int err; + + if (sk->s_fd == -1) + return -NLE_BAD_SOCK; + + err = setsockopt (sk->s_fd, SOL_SOCKET, SO_PASSCRED, + &state, sizeof (state)); + if (err < 0) + return -nl_syserr2nlerr (errno); + + if (state) + sk->s_flags |= NL_SOCK_PASSCRED; + else + sk->s_flags &= ~NL_SOCK_PASSCRED; + + return 0; +} + +int +nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize) +{ + sk->s_bufsize = bufsize; + + return 0; +} + +struct sockaddr_nl * +nlmsg_get_dst (struct nl_msg *msg) +{ + return &msg->nm_dst; +} + +int +nl_socket_set_nonblocking (const struct nl_sock *sk) +{ + if (sk->s_fd == -1) + return -NLE_BAD_SOCK; + + if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0) + return -nl_syserr2nlerr (errno); + + return 0; +} + +int +nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf) +{ + int err; + + if (rxbuf <= 0) + rxbuf = 32768; + + if (txbuf <= 0) + txbuf = 32768; + + if (sk->s_fd == -1) + return -NLE_BAD_SOCK; + + err = setsockopt (sk->s_fd, SOL_SOCKET, SO_SNDBUF, + &txbuf, sizeof (txbuf)); + if (err < 0) { + return -nl_syserr2nlerr (errno); + } + + err = setsockopt (sk->s_fd, SOL_SOCKET, SO_RCVBUF, + &rxbuf, sizeof (rxbuf)); + if (err < 0) { + return -nl_syserr2nlerr (errno); + } + + return 0; +} + +int +nl_socket_add_memberships (struct nl_sock *sk, int group, ...) +{ + int err; + va_list ap; + + if (sk->s_fd == -1) + return -NLE_BAD_SOCK; + + va_start(ap, group); + + while (group != 0) { + if (group < 0) { + va_end(ap); + g_return_val_if_reached (-NLE_BUG); + } + + err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, + &group, sizeof (group)); + if (err < 0) { + va_end(ap); + return -nl_syserr2nlerr (errno); + } + + group = va_arg(ap, int); + } + + va_end(ap); + + return 0; +} + +int +nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable) +{ + int err, val; + + if (sk->s_fd == -1) + return -NLE_BAD_SOCK; + + val = !!enable; + err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof (val)); + if (err < 0) + return -nl_syserr2nlerr (errno); + + return 0; +} + +void nl_socket_disable_msg_peek (struct nl_sock *sk) +{ + sk->s_flags |= NL_MSG_PEEK_EXPLICIT; + sk->s_flags &= ~NL_MSG_PEEK; +} + +int +nl_connect (struct nl_sock *sk, int protocol) +{ + int err; + socklen_t addrlen; + struct sockaddr_nl local = { 0 }; + + if (sk->s_fd != -1) + return -NLE_BAD_SOCK; + + sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); + if (sk->s_fd < 0) { + err = -nl_syserr2nlerr (errno); + goto errout; + } + + err = nl_socket_set_buffer_size(sk, 0, 0); + if (err < 0) + goto errout; + + nm_assert (sk->s_local.nl_pid == 0); + + err = bind (sk->s_fd, (struct sockaddr*) &sk->s_local, + sizeof (sk->s_local)); + if (err != 0) { + err = -nl_syserr2nlerr (errno); + goto errout; + } + + addrlen = sizeof (local); + err = getsockname (sk->s_fd, (struct sockaddr *) &local, + &addrlen); + if (err < 0) { + err = -nl_syserr2nlerr (errno); + goto errout; + } + + if (addrlen != sizeof (local)) { + err = -NLE_UNSPEC; + goto errout; + } + + if (local.nl_family != AF_NETLINK) { + err = -NLE_UNSPEC; + goto errout; + } + + sk->s_local = local; + sk->s_proto = protocol; + + return 0; + +errout: + if (sk->s_fd != -1) { + close(sk->s_fd); + sk->s_fd = -1; + } + return err; +} + +/*****************************************************************************/ + +static void +_cb_init (struct nl_cb *dst, const struct nl_cb *src) +{ + nm_assert (dst); + + if (src) + *dst = *src; + else + memset (dst, 0, sizeof (*dst)); +} + +static int ack_wait_handler(struct nl_msg *msg, void *arg) +{ + return NL_STOP; +} + +int +nl_wait_for_ack (struct nl_sock *sk, + const struct nl_cb *cb) +{ + struct nl_cb cb2; + + _cb_init (&cb2, cb); + cb2.ack_cb = ack_wait_handler; + return nl_recvmsgs (sk, &cb2); +} + +#define NL_CB_CALL(cb, type, msg) \ +do { \ + const struct nl_cb *_cb = (cb); \ + \ + if (_cb->type##_cb) { \ + err = _cb->type##_cb ((msg), _cb->type##_arg); \ + switch (err) { \ + case NL_OK: \ + err = 0; \ + break; \ + case NL_SKIP: \ + goto skip; \ + case NL_STOP: \ + goto stop; \ + default: \ + goto out; \ + } \ + } \ +} while (0) + +int +nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb) +{ + int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0; + gs_free unsigned char *buf = NULL; + struct nlmsghdr *hdr; + struct sockaddr_nl nla = { 0 }; + gs_free struct ucred *creds = NULL; + +continue_reading: + n = nl_recv (sk, &nla, &buf, &creds); + if (n <= 0) + return n; + + hdr = (struct nlmsghdr *) buf; + while (nlmsg_ok (hdr, n)) { + nm_auto_nlmsg struct nl_msg *msg = NULL; + + msg = nlmsg_alloc_convert (hdr); + + nlmsg_set_proto (msg, sk->s_proto); + nlmsg_set_src (msg, &nla); + if (creds) + nlmsg_set_creds (msg, creds); + + nrecv++; + + /* Only do sequence checking if auto-ack mode is enabled */ + if (!(sk->s_flags & NL_NO_AUTO_ACK)) { + if (hdr->nlmsg_seq != sk->s_seq_expect) { + err = -NLE_SEQ_MISMATCH; + goto out; + } + } + + if (hdr->nlmsg_type == NLMSG_DONE || + hdr->nlmsg_type == NLMSG_ERROR || + hdr->nlmsg_type == NLMSG_NOOP || + hdr->nlmsg_type == NLMSG_OVERRUN) { + /* We can't check for !NLM_F_MULTI since some netlink + * users in the kernel are broken. */ + sk->s_seq_expect++; + } + + if (hdr->nlmsg_flags & NLM_F_MULTI) + multipart = 1; + + if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) { + /* + * We have to continue reading to clear + * all messages until a NLMSG_DONE is + * received and report the inconsistency. + */ + interrupted = 1; + } + + /* messages terminates a multipart message, this is + * usually the end of a message and therefore we slip + * out of the loop by default. the user may overrule + * this action by skipping this packet. */ + if (hdr->nlmsg_type == NLMSG_DONE) { + multipart = 0; + NL_CB_CALL(cb, finish, msg); + } + + /* Message to be ignored, the default action is to + * skip this message if no callback is specified. The + * user may overrule this action by returning + * NL_PROCEED. */ + else if (hdr->nlmsg_type == NLMSG_NOOP) + goto skip; + + /* Data got lost, report back to user. The default action is to + * quit parsing. The user may overrule this action by retuning + * NL_SKIP or NL_PROCEED (dangerous) */ + else if (hdr->nlmsg_type == NLMSG_OVERRUN) { + err = -NLE_MSG_OVERFLOW; + goto out; + } + + /* Message carries a nlmsgerr */ + else if (hdr->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *e = nlmsg_data(hdr); + + if (hdr->nlmsg_len < nlmsg_size(sizeof (*e))) { + /* Truncated error message, the default action + * is to stop parsing. The user may overrule + * this action by returning NL_SKIP or + * NL_PROCEED (dangerous) */ + err = -NLE_MSG_TRUNC; + goto out; + } + if (e->error) { + /* Error message reported back from kernel. */ + if (cb->err_cb) { + err = cb->err_cb (&nla, e, + cb->err_arg); + if (err < 0) + goto out; + else if (err == NL_SKIP) + goto skip; + else if (err == NL_STOP) { + err = -e->error; + goto out; + } + } else { + err = -e->error; + goto out; + } + } else + NL_CB_CALL(cb, ack, msg); + } else { + /* Valid message (not checking for MULTIPART bit to + * get along with broken kernels. NL_SKIP has no + * effect on this. */ + NL_CB_CALL(cb, valid, msg); + } +skip: + err = 0; + hdr = nlmsg_next(hdr, &n); + } + + if (multipart) { + /* Multipart message not yet complete, continue reading */ + nm_clear_g_free (&creds); + nm_clear_g_free (&buf); + + goto continue_reading; + } + +stop: + err = 0; + +out: + if (interrupted) + err = -NLE_DUMP_INTR; + + return err ?: nrecv; +} + +int +nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr) +{ + int ret; + + if (sk->s_fd < 0) + return -NLE_BAD_SOCK; + + nlmsg_set_src (msg, &sk->s_local); + + ret = sendmsg(sk->s_fd, hdr, 0); + if (ret < 0) + return -nl_syserr2nlerr (errno); + + return ret; +} + +int +nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen) +{ + struct sockaddr_nl *dst; + struct ucred *creds; + struct msghdr hdr = { + .msg_name = (void *) &sk->s_peer, + .msg_namelen = sizeof (struct sockaddr_nl), + .msg_iov = iov, + .msg_iovlen = iovlen, + }; + char buf[CMSG_SPACE(sizeof (struct ucred))]; + + /* Overwrite destination if specified in the message itself, defaults + * to the peer address of the socket. + */ + dst = nlmsg_get_dst(msg); + if (dst->nl_family == AF_NETLINK) + hdr.msg_name = dst; + + /* Add credentials if present. */ + creds = nlmsg_get_creds(msg); + if (creds != NULL) { + struct cmsghdr *cmsg; + + hdr.msg_control = buf; + hdr.msg_controllen = sizeof (buf); + + cmsg = CMSG_FIRSTHDR(&hdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof (struct ucred)); + memcpy(CMSG_DATA(cmsg), creds, sizeof (struct ucred)); + } + + return nl_sendmsg(sk, msg, &hdr); +} + +void +nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg) +{ + struct nlmsghdr *nlh; + + nlh = nlmsg_hdr(msg); + if (nlh->nlmsg_pid == NL_AUTO_PORT) + nlh->nlmsg_pid = nl_socket_get_local_port(sk); + + if (nlh->nlmsg_seq == NL_AUTO_SEQ) + nlh->nlmsg_seq = sk->s_seq_next++; + + if (msg->nm_protocol == -1) + msg->nm_protocol = sk->s_proto; + + nlh->nlmsg_flags |= NLM_F_REQUEST; + + if (!(sk->s_flags & NL_NO_AUTO_ACK)) + nlh->nlmsg_flags |= NLM_F_ACK; +} + +int +nl_send (struct nl_sock *sk, struct nl_msg *msg) +{ + struct iovec iov = { + .iov_base = (void *) nlmsg_hdr(msg), + .iov_len = nlmsg_hdr(msg)->nlmsg_len, + }; + + return nl_send_iovec(sk, msg, &iov, 1); +} + +int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg) +{ + nl_complete_msg(sk, msg); + + return nl_send(sk, msg); +} + +int +nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla, + unsigned char **buf, struct ucred **creds) +{ + ssize_t n; + int flags = 0; + static int page_size = 0; + struct iovec iov; + struct msghdr msg = { + .msg_name = (void *) nla, + .msg_namelen = sizeof (struct sockaddr_nl), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + gs_free struct ucred* tmpcreds = NULL; + int retval; + + nm_assert (nla); + nm_assert (buf && !*buf); + nm_assert (!creds || !*creds); + + if ( (sk->s_flags & NL_MSG_PEEK) + || ( !(sk->s_flags & NL_MSG_PEEK_EXPLICIT) + && sk->s_bufsize == 0)) + flags |= MSG_PEEK | MSG_TRUNC; + + if (page_size == 0) + page_size = getpagesize() * 4; + + iov.iov_len = sk->s_bufsize ? : page_size; + iov.iov_base = g_malloc (iov.iov_len); + + if ( creds + && (sk->s_flags & NL_SOCK_PASSCRED)) { + msg.msg_controllen = CMSG_SPACE (sizeof (struct ucred)); + msg.msg_control = g_malloc (msg.msg_controllen); + } + +retry: + n = recvmsg(sk->s_fd, &msg, flags); + if (!n) { + retval = 0; + goto abort; + } + + if (n < 0) { + if (errno == EINTR) + goto retry; + + retval = -nl_syserr2nlerr (errno); + goto abort; + } + + if (msg.msg_flags & MSG_CTRUNC) { + if (msg.msg_controllen == 0) { + retval = -NLE_MSG_TRUNC; + goto abort; + } + + msg.msg_controllen *= 2; + msg.msg_control = g_realloc (msg.msg_control, msg.msg_controllen); + goto retry; + } + + if ( iov.iov_len < n + || (msg.msg_flags & MSG_TRUNC)) { + /* respond with error to an incomplete message */ + if (flags == 0) { + retval = -NLE_MSG_TRUNC; + goto abort; + } + + /* Provided buffer is not long enough, enlarge it + * to size of n (which should be total length of the message) + * and try again. */ + iov.iov_base = g_realloc (iov.iov_base, n); + iov.iov_len = n; + flags = 0; + goto retry; + } + + if (flags != 0) { + /* Buffer is big enough, do the actual reading */ + flags = 0; + goto retry; + } + + if (msg.msg_namelen != sizeof (struct sockaddr_nl)) { + retval = -NLE_UNSPEC; + goto abort; + } + + if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) { + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + if (cmsg->cmsg_type != SCM_CREDENTIALS) + continue; + tmpcreds = g_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds)); + break; + } + } + + retval = n; + +abort: + g_free (msg.msg_control); + + if (retval <= 0) { + g_free (iov.iov_base); + return retval; + } + + *buf = iov.iov_base; + NM_SET_OUT (creds, g_steal_pointer (&tmpcreds)); + return retval; +} diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h new file mode 100644 index 00000000..c0dc09c4 --- /dev/null +++ b/src/platform/nm-netlink.h @@ -0,0 +1,511 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-platform.c - Handle runtime kernel networking configuration + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2018 Red Hat, Inc. + */ + +#ifndef __NM_NETLINK_H__ +#define __NM_NETLINK_H__ + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/genetlink.h> + +/*****************************************************************************/ +#define _NLE_BASE 100000 +#define NLE_UNSPEC (_NLE_BASE + 0) +#define NLE_BUG (_NLE_BASE + 1) +#define NLE_NATIVE_ERRNO (_NLE_BASE + 2) +#define NLE_SEQ_MISMATCH (_NLE_BASE + 3) +#define NLE_MSG_TRUNC (_NLE_BASE + 4) +#define NLE_MSG_TOOSHORT (_NLE_BASE + 5) +#define NLE_DUMP_INTR (_NLE_BASE + 6) +#define NLE_ATTRSIZE (_NLE_BASE + 7) +#define NLE_BAD_SOCK (_NLE_BASE + 8) +#define NLE_NOADDR (_NLE_BASE + 9) +#define NLE_MSG_OVERFLOW (_NLE_BASE + 10) + +#define _NLE_BASE_END (_NLE_BASE + 11) + +#define NLMSGERR_ATTR_UNUSED 0 +#define NLMSGERR_ATTR_MSG 1 +#define NLMSGERR_ATTR_OFFS 2 +#define NLMSGERR_ATTR_COOKIE 3 +#define NLMSGERR_ATTR_MAX 3 + +#ifndef NLM_F_ACK_TLVS +#define NLM_F_ACK_TLVS 0x200 +#endif + +static inline int +nl_errno (int err) +{ + /* the error codes from our netlink implementation are plain errno + * extended with our own error in a particular range starting from + * _NLE_BASE. + * + * However, often we encode errors as negative values. This function + * normalizes the error and returns its positive value. */ + return err >= 0 + ? err + : ((err == G_MININT) ? NLE_BUG : -errno); +} + +static inline int +nl_syserr2nlerr (int err) +{ + if (err == G_MININT) + return NLE_NATIVE_ERRNO; + if (err < 0) + err = -err; + return (err >= _NLE_BASE && err < _NLE_BASE_END) + ? NLE_NATIVE_ERRNO + : err; +} + +const char *nl_geterror (int err); + +/*****************************************************************************/ + +/* Basic attribute data types */ +enum { + NLA_UNSPEC, /* Unspecified type, binary data chunk */ + NLA_U8, /* 8 bit integer */ + NLA_U16, /* 16 bit integer */ + NLA_U32, /* 32 bit integer */ + NLA_U64, /* 64 bit integer */ + NLA_STRING, /* NUL terminated character string */ + NLA_FLAG, /* Flag */ + NLA_MSECS, /* Micro seconds (64bit) */ + NLA_NESTED, /* Nested attributes */ + NLA_NESTED_COMPAT, + NLA_NUL_STRING, + NLA_BINARY, + NLA_S8, + NLA_S16, + NLA_S32, + NLA_S64, + __NLA_TYPE_MAX, +}; + +#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1) + +struct nl_msg; + +/*****************************************************************************/ + +const char *nl_nlmsgtype2str (int type, char *buf, size_t size); + +const char *nl_nlmsg_flags2str (int flags, char *buf, size_t len); + +const char *nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len); + +/*****************************************************************************/ + +struct nla_policy { + /* Type of attribute or NLA_UNSPEC */ + uint16_t type; + + /* Minimal length of payload required */ + uint16_t minlen; + + /* Maximal length of payload allowed */ + uint16_t maxlen; +}; + +/*****************************************************************************/ + +static inline int +nla_attr_size(int payload) +{ + nm_assert (payload >= 0); + + return NLA_HDRLEN + payload; +} + +static inline int +nla_total_size (int payload) +{ + return NLA_ALIGN (nla_attr_size (payload)); +} + +static inline int +nla_padlen (int payload) +{ + return nla_total_size(payload) - nla_attr_size(payload); +} + +struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen); + +static inline int +nla_len (const struct nlattr *nla) +{ + return nla->nla_len - NLA_HDRLEN; +} + +static inline int +nla_type (const struct nlattr *nla) +{ + return nla->nla_type & NLA_TYPE_MASK; +} + +static inline void * +nla_data (const struct nlattr *nla) +{ + nm_assert (nla); + return (char *) nla + NLA_HDRLEN; +} + +static inline uint8_t +nla_get_u8 (const struct nlattr *nla) +{ + return *(const uint8_t *) nla_data (nla); +} + +static inline uint8_t +nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_val) +{ + nm_assert (tb); + nm_assert (attr >= 0); + + return tb[attr] ? nla_get_u8 (tb[attr]) : default_val; +} + +static inline uint16_t +nla_get_u16 (const struct nlattr *nla) +{ + return *(const uint16_t *) nla_data (nla); +} + +static inline uint32_t +nla_get_u32(const struct nlattr *nla) +{ + return *(const uint32_t *) nla_data (nla); +} + +uint64_t nla_get_u64 (const struct nlattr *nla); + +static inline char * +nla_get_string (const struct nlattr *nla) +{ + return (char *) nla_data (nla); +} + +size_t nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize); + +int nla_memcpy (void *dest, const struct nlattr *src, int count); + +int nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data); + +static inline int +nla_put_string (struct nl_msg *msg, int attrtype, const char *str) +{ + return nla_put(msg, attrtype, strlen(str) + 1, str); +} + +#define NLA_PUT(msg, attrtype, attrlen, data) \ + do { \ + if (nla_put(msg, attrtype, attrlen, data) < 0) \ + goto nla_put_failure; \ + } while(0) + +#define NLA_PUT_TYPE(msg, type, attrtype, value) \ + do { \ + type __tmp = value; \ + NLA_PUT(msg, attrtype, sizeof(type), &__tmp); \ + } while(0) + +#define NLA_PUT_U8(msg, attrtype, value) \ + NLA_PUT_TYPE(msg, uint8_t, attrtype, value) + +#define NLA_PUT_U16(msg, attrtype, value) \ + NLA_PUT_TYPE(msg, uint16_t, attrtype, value) + +#define NLA_PUT_U32(msg, attrtype, value) \ + NLA_PUT_TYPE(msg, uint32_t, attrtype, value) + +#define NLA_PUT_U64(msg, attrtype, value) \ + NLA_PUT_TYPE(msg, uint64_t, attrtype, value) + +#define NLA_PUT_STRING(msg, attrtype, value) \ + NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value) + +struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype); + +static inline int +nla_ok (const struct nlattr *nla, int remaining) +{ + return remaining >= sizeof(*nla) && + nla->nla_len >= sizeof(*nla) && + nla->nla_len <= remaining; +} + +static inline struct nlattr * +nla_next(const struct nlattr *nla, int *remaining) +{ + int totlen = NLA_ALIGN(nla->nla_len); + + *remaining -= totlen; + return (struct nlattr *) ((char *) nla + totlen); +} + +#define nla_for_each_attr(pos, head, len, rem) \ + for (pos = head, rem = len; \ + nla_ok(pos, rem); \ + pos = nla_next(pos, &(rem))) + +#define nla_for_each_nested(pos, nla, rem) \ + for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); \ + nla_ok(pos, rem); \ + pos = nla_next(pos, &(rem))) + +void nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr); +struct nlattr *nla_nest_start (struct nl_msg *msg, int attrtype); +int nla_nest_end (struct nl_msg *msg, struct nlattr *start); + +int nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len, + const struct nla_policy *policy); + +static inline int +nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla, + const struct nla_policy *policy) +{ + return nla_parse (tb, maxtype, nla_data(nla), nla_len(nla), policy); +} + +/*****************************************************************************/ + +struct nl_msg *nlmsg_alloc (void); + +struct nl_msg *nlmsg_alloc_size (size_t max); + +struct nl_msg *nlmsg_alloc_inherit (struct nlmsghdr *hdr); + +struct nl_msg *nlmsg_alloc_convert (struct nlmsghdr *hdr); + +struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags); + +void *nlmsg_reserve (struct nl_msg *n, size_t len, int pad); + +int nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad); + +void nlmsg_free (struct nl_msg *msg); + +static inline int +nlmsg_size (int payload) +{ + nm_assert (payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4); + return NLMSG_HDRLEN + payload; +} + +static inline int +nlmsg_total_size (int payload) +{ + return NLMSG_ALIGN (nlmsg_size (payload)); +} + +static inline int +nlmsg_ok (const struct nlmsghdr *nlh, int remaining) +{ + return (remaining >= (int)sizeof(struct nlmsghdr) && + nlh->nlmsg_len >= sizeof(struct nlmsghdr) && + nlh->nlmsg_len <= remaining); +} + +static inline struct nlmsghdr * +nlmsg_next (struct nlmsghdr *nlh, int *remaining) +{ + int totlen = NLMSG_ALIGN(nlh->nlmsg_len); + + *remaining -= totlen; + + return (struct nlmsghdr *) ((unsigned char *) nlh + totlen); +} + +int nlmsg_get_proto (struct nl_msg *msg); +void nlmsg_set_proto (struct nl_msg *msg, int protocol); + +void nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr); + +struct ucred *nlmsg_get_creds (struct nl_msg *msg); +void nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds); + +static inline void +_nm_auto_nl_msg_cleanup (struct nl_msg **ptr) +{ + nlmsg_free (*ptr); +} +#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup) + +static inline void * +nlmsg_data (const struct nlmsghdr *nlh) +{ + return (unsigned char *) nlh + NLMSG_HDRLEN; +} + +static inline void * +nlmsg_tail (const struct nlmsghdr *nlh) +{ + return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len); +} + +struct nlmsghdr *nlmsg_hdr (struct nl_msg *n); + +static inline int +nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen) +{ + if (nlh->nlmsg_len < nlmsg_size (hdrlen)) + return 0; + + return 1; +} + +static inline int +nlmsg_datalen (const struct nlmsghdr *nlh) +{ + return nlh->nlmsg_len - NLMSG_HDRLEN; +} + +static inline int +nlmsg_attrlen (const struct nlmsghdr *nlh, int hdrlen) +{ + return NM_MAX ((int) (nlmsg_datalen (nlh) - NLMSG_ALIGN (hdrlen)), 0); +} + +static inline struct nlattr * +nlmsg_attrdata (const struct nlmsghdr *nlh, int hdrlen) +{ + unsigned char *data = nlmsg_data(nlh); + return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen)); +} + +static inline struct nlattr * +nlmsg_find_attr (struct nlmsghdr *nlh, int hdrlen, int attrtype) +{ + return nla_find (nlmsg_attrdata (nlh, hdrlen), + nlmsg_attrlen (nlh, hdrlen), + attrtype); +} + +int nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], + int maxtype, const struct nla_policy *policy); + +struct nlmsghdr *nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq, + int type, int payload, int flags); + +/*****************************************************************************/ + +#define NL_AUTO_PORT 0 +#define NL_AUTO_SEQ 0 + +struct nl_sock; + +struct nl_sock *nl_socket_alloc (void); + +void nl_socket_free (struct nl_sock *sk); + +int nl_socket_get_fd (const struct nl_sock *sk); + +struct sockaddr_nl *nlmsg_get_dst (struct nl_msg *msg); + +size_t nl_socket_get_msg_buf_size (struct nl_sock *sk); +int nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize); + +int nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf); + +int nl_socket_set_passcred (struct nl_sock *sk, int state); + +int nl_socket_set_nonblocking (const struct nl_sock *sk); + +void nl_socket_disable_msg_peek (struct nl_sock *sk); + +uint32_t nl_socket_get_local_port (const struct nl_sock *sk); + +int nl_socket_add_memberships (struct nl_sock *sk, int group, ...); + +int nl_connect (struct nl_sock *sk, int protocol); + +int nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla, + unsigned char **buf, struct ucred **creds); + +int nl_send (struct nl_sock *sk, struct nl_msg *msg); + +int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg); + +/*****************************************************************************/ + +enum nl_cb_action { + /* Proceed with wathever would come next */ + NL_OK, + /* Skip this message */ + NL_SKIP, + /* Stop parsing altogether and discard remaining messages */ + NL_STOP, +}; + +typedef int (*nl_recvmsg_msg_cb_t) (struct nl_msg *msg, void *arg); + +typedef int (*nl_recvmsg_err_cb_t) (struct sockaddr_nl *nla, + struct nlmsgerr *nlerr, void *arg); + +struct nl_cb { + nl_recvmsg_msg_cb_t valid_cb; + void * valid_arg; + + nl_recvmsg_msg_cb_t finish_cb; + void * finish_arg; + + nl_recvmsg_msg_cb_t ack_cb; + void * ack_arg; + + nl_recvmsg_err_cb_t err_cb; + void * err_arg; +}; + +int nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr); + +int nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen); + +void nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg); + +int nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb); + +int nl_wait_for_ack (struct nl_sock *sk, + const struct nl_cb *cb); + +int nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable); + +/*****************************************************************************/ + +void *genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family, + int hdrlen, int flags, uint8_t cmd, uint8_t version); +void *genlmsg_data (const struct genlmsghdr *gnlh); +void *genlmsg_user_hdr (const struct genlmsghdr *gnlh); +struct genlmsghdr *genlmsg_hdr (struct nlmsghdr *nlh); +void *genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen); +struct nlattr *genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen); +int genlmsg_len (const struct genlmsghdr *gnlh); +int genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen); +int genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen); +int genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], + int maxtype, const struct nla_policy *policy); + +int genl_ctrl_resolve (struct nl_sock *sk, const char *name); + +/*****************************************************************************/ + +#endif /* __NM_NETLINK_H__ */ diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c index b664e8a9..114bf4b3 100644 --- a/src/platform/nm-platform-utils.c +++ b/src/platform/nm-platform-utils.c @@ -589,6 +589,8 @@ nmp_utils_ip_config_source_coerce_to_rtprot (NMIPConfigSource source) switch (source) { case NM_IP_CONFIG_SOURCE_KERNEL: return RTPROT_KERNEL; + case NM_IP_CONFIG_SOURCE_IP6LL: + return RTPROT_KERNEL; case NM_IP_CONFIG_SOURCE_DHCP: return RTPROT_DHCP; case NM_IP_CONFIG_SOURCE_NDISC: @@ -656,6 +658,7 @@ nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize case NM_IP_CONFIG_SOURCE_KERNEL: s = "kernel"; break; case NM_IP_CONFIG_SOURCE_SHARED: s = "shared"; break; case NM_IP_CONFIG_SOURCE_IP4LL: s = "ipv4ll"; break; + case NM_IP_CONFIG_SOURCE_IP6LL: s = "ipv6ll"; break; case NM_IP_CONFIG_SOURCE_PPP: s = "ppp"; break; case NM_IP_CONFIG_SOURCE_WWAN: s = "wwan"; break; case NM_IP_CONFIG_SOURCE_VPN: s = "vpn"; break; diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index c7ed90e3..84d862d2 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -353,6 +353,45 @@ nm_platform_process_events (NMPlatform *self) klass->process_events (self); } +const NMPlatformLink * +nm_platform_process_events_ensure_link (NMPlatform *self, + int ifindex, + const char *ifname) +{ + const NMPObject *obj; + gboolean refreshed = FALSE; + + g_return_val_if_fail (NM_IS_PLATFORM (self), NULL); + + if (ifindex <= 0 && !ifname) + return NULL; + + /* we look into the cache, whether a link for given ifindex/ifname + * exits. If not, we poll the netlink socket, maybe the event + * with the link is waiting. + * + * Then we try again to find the object. + * + * If the link is already cached the first time, we avoid polling + * the netlink socket. */ +again: + obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self), + ifindex, + ifname, + FALSE, /* also invisible. We don't care here whether udev is ready */ + NM_LINK_TYPE_NONE, + NULL, NULL); + if (obj) + return NMP_OBJECT_CAST_LINK (obj); + if (!refreshed) { + refreshed = TRUE; + nm_platform_process_events (self); + goto again; + } + + return NULL; +} + /*****************************************************************************/ /** @@ -597,11 +636,11 @@ nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name) * further by moving children/slaves to the end. */ g_ptr_array_sort_with_data (links, _link_get_all_presort, GINT_TO_POINTER (sort_by_name)); - unseen = g_hash_table_new (g_direct_hash, g_direct_equal); + unseen = g_hash_table_new (nm_direct_hash, NULL); for (i = 0; i < links->len; i++) { item = NMP_OBJECT_CAST_LINK (links->pdata[i]); nm_assert (item->ifindex > 0); - if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL)) + if (!g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL)) nm_assert_not_reached (); } @@ -1118,6 +1157,21 @@ nm_platform_link_supports_slaves (NMPlatform *self, int ifindex) } /** + * nm_platform_refresh_all: + * @self: platform instance + * @obj_type: The object type to request. + * + * Resync and re-request all objects from kernel of a certain @obj_type. + */ +void +nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type) +{ + _CHECK_SELF_VOID (self, klass); + + klass->refresh_all (self, obj_type); +} + +/** * nm_platform_link_refresh: * @self: platform instance * @ifindex: Interface index @@ -1325,30 +1379,26 @@ gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length) { const NMPlatformLink *pllink; - gconstpointer a = NULL; - guint8 l = 0; _CHECK_SELF (self, klass, NULL); - if (length) - *length = 0; - g_return_val_if_fail (ifindex > 0, NULL); pllink = nm_platform_link_get (self, ifindex); - if (pllink && pllink->addr.len > 0) { - if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) { - if (length) - *length = 0; - g_return_val_if_reached (NULL); - } - a = pllink->addr.data; - l = pllink->addr.len; + + if ( !pllink + || pllink->addr.len <= 0) { + NM_SET_OUT (length, 0); + return NULL; } - if (length) - *length = l; - return a; + if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) { + NM_SET_OUT (length, 0); + g_return_val_if_reached (NULL); + } + + NM_SET_OUT (length, pllink->addr.len); + return pllink->addr.data; } /** @@ -1844,6 +1894,12 @@ nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLin return _link_get_lnk (self, ifindex, NM_LINK_TYPE_SIT, out_link); } +const NMPlatformLnkTun * +nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link) +{ + return _link_get_lnk (self, ifindex, NM_LINK_TYPE_TUN, out_link); +} + const NMPlatformLnkVlan * nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link) { @@ -1990,33 +2046,43 @@ nm_platform_link_vxlan_add (NMPlatform *self, * @vnet_hdr: whether to set the IFF_VNET_HDR flag * @multi_queue: whether to set the IFF_MULTI_QUEUE flag * @out_link: on success, the link object + * @out_fd: (allow-none): if give, return the file descriptor for the + * created device. Note that when creating a non-persistent device, + * this argument is mandatory, otherwise it makes no sense + * to create such an interface. + * The caller is responsible for closing this file descriptor. * * Create a TUN or TAP interface. */ NMPlatformError nm_platform_link_tun_add (NMPlatform *self, const char *name, - gboolean tap, - gint64 owner, - gint64 group, - gboolean pi, - gboolean vnet_hdr, - gboolean multi_queue, - const NMPlatformLink **out_link) + const NMPlatformLnkTun *props, + const NMPlatformLink **out_link, + int *out_fd) { + char b[255]; NMPlatformError plerr; _CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG); g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG); + g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG); + g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), NM_PLATFORM_ERROR_BUG); + + /* creating a non-persistant device requires that the caller handles + * the file descriptor. */ + g_return_val_if_fail (props->persist || out_fd, NM_PLATFORM_ERROR_BUG); - plerr = _link_add_check_existing (self, name, tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, out_link); + NM_SET_OUT (out_fd, -1); + + plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link); if (plerr != NM_PLATFORM_ERROR_SUCCESS) return plerr; - _LOGD ("link: adding %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT, - tap ? "tap" : "tun", name, owner, group); - if (!klass->tun_add (self, name, tap, owner, group, pi, vnet_hdr, multi_queue, out_link)) + _LOGD ("link: adding tun '%s' %s", + name, nm_platform_lnk_tun_to_string (props, b, sizeof (b))); + if (!klass->link_tun_add (self, name, props, out_link, out_fd)) return NM_PLATFORM_ERROR_UNSPECIFIED; return NM_PLATFORM_ERROR_SUCCESS; } @@ -2620,44 +2686,100 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe return TRUE; } +/** + * nm_platform_link_tun_get_properties: + * @self: the #NMPlatform instance + * @ifindex: the ifindex to look up + * @out_properties: (out): (allow-none): return the read properties + * + * Only recent versions of kernel export tun properties via netlink. + * So, if that's the case, then we have the NMPlatformLnkTun instance + * in the platform cache ready to return. Otherwise, this function + * falls back reading sysctl to obtain the tun properties. That + * is racy, because querying sysctl means that the object might + * be already removed from cache (while NM didn't yet process the + * netlink message). + * + * Hence, to lookup the tun properties, you always need to use this + * function, and use it with care knowing that it might obtain its + * data by reading sysctl. Note that we don't want to add this workaround + * to the platform cache itself, because the cache should (mainly) + * contain data from netlink. To access the sysctl side channel, the + * user needs to do explicitly. + * + * Returns: #TRUE, if the properties could be read. */ gboolean -nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props) +nm_platform_link_tun_get_properties (NMPlatform *self, + int ifindex, + NMPlatformLnkTun *out_properties) { - nm_auto_close int dirfd = -1; + const NMPObject *plobj; + const NMPObject *pllnk; char ifname[IFNAMSIZ]; + gint64 owner; + gint64 group; gint64 flags; - gboolean success = TRUE; + _CHECK_SELF (self, klass, FALSE); g_return_val_if_fail (ifindex > 0, FALSE); - g_return_val_if_fail (props, FALSE); - - memset (props, 0, sizeof (*props)); - props->owner = -1; - props->group = -1; - dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname); - if (dirfd < 0) + /* we consider also invisible links (those that are not yet in udev). */ + plobj = nm_platform_link_get_obj (self, ifindex, FALSE); + if (!plobj) + return FALSE; + if (NMP_OBJECT_CAST_LINK (plobj)->type != NM_LINK_TYPE_TUN) return FALSE; - props->owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXINT64, -1); - if (errno) - success = FALSE; + pllnk = plobj->_link.netlink.lnk; + if (pllnk) { + nm_assert (NMP_OBJECT_GET_TYPE (pllnk) == NMP_OBJECT_TYPE_LNK_TUN); + nm_assert (NMP_OBJECT_GET_CLASS (pllnk)->lnk_link_type == NM_LINK_TYPE_TUN); - props->group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXINT64, -1); - if (errno) - success = FALSE; + /* recent kernels expose tun properties via netlink and thus we have them + * in the platform cache. */ + NM_SET_OUT (out_properties, pllnk->lnk_tun); + return TRUE; + } - flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1); - if (flags >= 0) { - 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 & NM_IFF_MULTI_QUEUE); - } else - success = FALSE; + /* fallback to reading sysctl. */ + { + nm_auto_close int dirfd = -1; - return success; + dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname); + if (dirfd < 0) + return FALSE; + + owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXUINT32, -2); + if (owner == -2) + return FALSE; + + group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXUINT32, -2); + if (group == -2) + return FALSE; + + flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1); + if (flags == -1) + return FALSE; + } + + if (out_properties) { + memset (out_properties, 0, sizeof (*out_properties)); + if (owner != -1) { + out_properties->owner_valid = TRUE; + out_properties->owner = owner; + } + if (group != -1) { + out_properties->group_valid = TRUE; + out_properties->group = group; + } + out_properties->type = (flags & TUN_TYPE_MASK); + out_properties->pi = !(flags & IFF_NO_PI); + out_properties->vnet_hdr = !!(flags & IFF_VNET_HDR); + out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE); + out_properties->persist = !!(flags & IFF_PERSIST); + } + return TRUE; } gboolean @@ -2932,7 +3054,7 @@ nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *ob * @user_data: user data for @predicate * * Returns the result of lookup in a GPtrArray. The result array contains - * references objects from the cache, it's destroy function will unref them. + * references objects from the cache, its destroy function will unref them. * * The user must unref the GPtrArray, which will also unref the NMPObject * elements. @@ -3111,24 +3233,68 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr } static gboolean -array_contains_ip6_address (const GPtrArray *addresses, const NMPlatformIP6Address *address, gint32 now) +_addr_array_clean_expired (int addr_family, int ifindex, GPtrArray *array, guint32 now, GHashTable **idx) { - guint len = addresses ? addresses->len : 0; guint i; + gboolean any_addrs = FALSE; - for (i = 0; i < len; i++) { - NMPlatformIP6Address *candidate = NMP_OBJECT_CAST_IP6_ADDRESS (addresses->pdata[i]); + nm_assert_addr_family (addr_family); + nm_assert (ifindex > 0); + nm_assert (now > 0); - if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) { - guint32 lifetime, preferred; + if (!array) + return FALSE; + + /* remove all addresses that are already expired. */ + for (i = 0; i < array->len; i++) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS (array->pdata[i]); + +#if NM_MORE_ASSERTS > 10 + nm_assert (a); + nm_assert (a->ifindex == ifindex); + { + const NMPObject *o = NMP_OBJECT_UP_CAST (a); + guint j; - if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred, - now, &lifetime, &preferred)) - return TRUE; + nm_assert (NMP_OBJECT_GET_CLASS (o)->addr_family == addr_family); + for (j = i + 1; j < array->len; j++) { + const NMPObject *o2 = array->pdata[j]; + + nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_GET_TYPE (o2)); + nm_assert (!nmp_object_id_equal (o, o2)); + } } +#endif + + if ( addr_family == AF_INET6 + && NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_TEMPORARY)) { + /* temporary addresses are never added explicitly by NetworkManager but + * kernel adds them via mngtempaddr flag. + * + * We drop them from this list. */ + goto clear_and_next; + } + + if (!nm_utils_lifetime_get (a->timestamp, a->lifetime, a->preferred, + now, NULL)) + goto clear_and_next; + + if (idx) { + if (G_UNLIKELY (!*idx)) { + *idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal); + } + if (!g_hash_table_add (*idx, (gpointer) NMP_OBJECT_UP_CAST (a))) + nm_assert_not_reached (); + } + any_addrs = TRUE; + continue; + +clear_and_next: + nmp_object_unref (g_steal_pointer (&array->pdata[i])); } - return FALSE; + return any_addrs; } static gboolean @@ -3179,7 +3345,7 @@ ip4_addr_subnets_build_index (const GPtrArray *addresses, nm_assert (addresses && addresses->len); - subnets = g_hash_table_new (NULL, NULL); + subnets = g_hash_table_new (nm_direct_hash, NULL); /* Build a hash table of all addresses per subnet */ for (i = 0; i < addresses->len; i++) { @@ -3312,39 +3478,8 @@ nm_platform_ip4_address_sync (NMPlatform *self, _CHECK_SELF (self, klass, FALSE); - if (known_addresses) { - /* remove all addresses that are already expired. */ - for (i = 0; i < known_addresses->len; i++) { - const NMPObject *o; - - o = known_addresses->pdata[i]; - nm_assert (o); - - known_address = NMP_OBJECT_CAST_IP4_ADDRESS (known_addresses->pdata[i]); - - if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, - now, &lifetime, &preferred)) - goto delete_and_next; - - if (G_UNLIKELY (!known_addresses_idx)) { - known_addresses_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash, - (GEqualFunc) nmp_object_id_equal); - } - if (!nm_g_hash_table_insert (known_addresses_idx, (gpointer) o, (gpointer) o)) { - /* duplicate? Keep only the first instance. */ - goto delete_and_next; - } - - continue; -delete_and_next: - nmp_object_unref (o); - known_addresses->pdata[i] = NULL; - } - - if ( !known_addresses_idx - || g_hash_table_size (known_addresses_idx) == 0) - known_addresses = NULL; - } + if (!_addr_array_clean_expired (AF_INET, ifindex, known_addresses, now, &known_addresses_idx)) + known_addresses = NULL; plat_addresses = nm_platform_lookup_clone (self, nmp_lookup_init_object (&lookup, @@ -3441,8 +3576,9 @@ delete_and_next: known_address = NMP_OBJECT_CAST_IP4_ADDRESS (o); - if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, - now, &lifetime, &preferred)) + lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, + now, &preferred); + if (!lifetime) goto delete_and_next2; if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen, @@ -3464,9 +3600,15 @@ delete_and_next2: * nm_platform_ip6_address_sync: * @self: platform instance * @ifindex: Interface index - * @known_addresses: List of IPv6 addresses, as NMPObject. The list - * is not modified. - * @keep_link_local: Don't remove link-local address + * @known_addresses: List of addresses. The list will be modified and only + * addresses that were successfully added will be kept in the list. + * That means, expired addresses and addresses that could not be added + * will be dropped. + * Hence, the input argument @known_addresses is also an output argument + * telling which addresses were succesfully added. + * Addresses are removed by unrefing the instance via nmp_object_unref() + * and leaving a NULL tombstone. + * @full_sync: Also remove link-local and temporary addresses. * * A convenience function to synchronize addresses for a specific interface * with the least possible disturbance. It simply removes addresses that are @@ -3477,32 +3619,117 @@ delete_and_next2: gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, - const GPtrArray *known_addresses, - gboolean keep_link_local) + GPtrArray *known_addresses, + gboolean full_sync) { gs_unref_ptrarray GPtrArray *plat_addresses = NULL; - NMPlatformIP6Address *address; gint32 now = nm_utils_get_monotonic_timestamp_s (); - guint i; + guint i_plat, i_know; + gs_unref_hashtable GHashTable *known_addresses_idx = NULL; NMPLookup lookup; guint32 ifa_flags; - /* Delete unknown addresses */ + if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx)) + known_addresses = NULL; + + /* @plat_addresses is in decreasing priority order (highest priority addresses first), contrary to + * @known_addresses which is in increasing priority order (lowest priority addresses first). */ plat_addresses = nm_platform_lookup_clone (self, nmp_lookup_init_object (&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex), NULL, NULL); + if (plat_addresses) { - for (i = 0; i < plat_addresses->len; i++) { - address = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[i]); + guint known_addresses_len; + + known_addresses_len = known_addresses ? known_addresses->len : 0; + + /* First, compare every address whether it is still a "known address", that is, whether + * to keep it or to delete it. + * + * If we don't find a matching valid address in @known_addresses, we will delete + * plat_addr. + * + * Certain addresses, like temporary addresses, are ignored by this function + * if not run with full_sync. These addresses are usually not managed by NetworkManager + * directly, or at least, they are not managed via nm_platform_ip6_address_sync(). + * Only in full_sync mode, we really want to get rid of them (usually, when we take + * the interface down). + * + * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */ + for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) { + const NMPObject *plat_obj = plat_addresses->pdata[i_plat]; + const NMPObject *know_obj; + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_obj); + + if (NM_FLAGS_HAS (plat_addr->n_ifa_flags, IFA_F_TEMPORARY)) { + if (!full_sync) { + /* just mark as handled, without actually deleting the address. */ + goto clear_and_next; + } + } else if (known_addresses_idx) { + know_obj = g_hash_table_lookup (known_addresses_idx, plat_obj); + if ( know_obj + && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS (know_obj)->plen) { + /* technically, plen is not part of the ID for IPv6 addresses and thus + * @plat_addr is essentially the same address as @know_addr (regrading + * its identity, not its other attributes). + * However, we cannot modify an existing addresses' plen without + * removing and readding it. Thus, only keep plat_addr, if the plen + * matches. + * + * keep this one, and continue */ + continue; + } + } + + nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); +clear_and_next: + nmp_object_unref (g_steal_pointer (&plat_addresses->pdata[i_plat])); + } - /* Leave link local address management to the kernel */ - if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address)) + /* Next, we must preserve the priority of the routes. That is, source address + * selection will choose addresses in the order as they are reported by kernel. + * Note that the order in @plat_addresses of the remaining matches is highest + * priority first. + * We need to compare this to the order in @known_addresses (which has lowest + * priority first). + * + * If we find a first discrepancy, we need to delete all remaining addresses + * from that point on, because below we must re-add all the addresses in the + * right order to get their priority right. */ + i_plat = plat_addresses->len; + i_know = 0; + while (i_plat > 0) { + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]); + + if (!plat_addr) continue; - if (!array_contains_ip6_address (known_addresses, address, now)) - nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen); + for (; i_know < known_addresses_len; i_know++) { + const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]); + + if (!know_addr) + continue; + + if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) { + /* we have a match. Mark address as handled. */ + i_know++; + goto next_plat; + } + + /* all remainging addresses need to be removed as well, so that we can + * re-add them in the correct order. Signal that, by setting @i_know + * so that the next @i_plat iteration, we won't enter the loop and + * delete the address right away */ + i_know = known_addresses_len; + break; + } + + nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); +next_plat: + ; } } @@ -3513,19 +3740,18 @@ nm_platform_ip6_address_sync (NMPlatform *self, ? IFA_F_NOPREFIXROUTE : 0; - /* Add missing addresses */ - for (i = 0; i < known_addresses->len; i++) { - const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i]); + /* Add missing addresses. New addresses are added by kernel with top + * priority. + */ + for (i_know = 0; i_know < known_addresses->len; i_know++) { + const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]); guint32 lifetime, preferred; - if (NM_FLAGS_HAS (known_address->n_ifa_flags, IFA_F_TEMPORARY)) { - /* Kernel manages these */ + if (!known_address) continue; - } - if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, - now, &lifetime, &preferred)) - continue; + lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, + now, &preferred); if (!nm_platform_ip6_address_add (self, ifindex, known_address->address, known_address->plen, known_address->peer_address, @@ -3553,7 +3779,7 @@ nm_platform_ip_address_flush (NMPlatform *self, if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) success &= nm_platform_ip4_address_sync (self, ifindex, NULL); if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) - success &= nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE); + success &= nm_platform_ip6_address_sync (self, ifindex, NULL, TRUE); return success; } @@ -3692,7 +3918,8 @@ nm_platform_ip_route_sync (NMPlatform *self, for (i_type = 0; routes && i_type < 2; i_type++) { for (i = 0; i < routes->len; i++) { - NMPlatformError plerr; + NMPlatformError plerr, plerr2; + gboolean gateway_route_added = FALSE; conf_o = routes->pdata[i]; @@ -3712,7 +3939,7 @@ nm_platform_ip_route_sync (NMPlatform *self, routes_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal); } - if (!nm_g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) { + if (!g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) { _LOGD ("route-sync: skip adding duplicate route %s", nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1))); continue; @@ -3738,6 +3965,7 @@ nm_platform_ip_route_sync (NMPlatform *self, } } +sync_route_add: plerr = nm_platform_ip_route_add (self, NMP_NLM_FLAG_APPEND | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, @@ -3762,6 +3990,11 @@ nm_platform_ip_route_sync (NMPlatform *self, nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2))); } } + } else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { + _LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)), + nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err))); } else if ( -((int) plerr) == EINVAL && out_temporary_not_available && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) { @@ -3771,25 +4004,66 @@ nm_platform_ip_route_sync (NMPlatform *self, if (!*out_temporary_not_available) *out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref); g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o)); - } else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { - _LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s", + } else if ( !gateway_route_added + && ( ( -((int) plerr) == ENETUNREACH + && vt->is_ip4 + && !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway) + || ( -((int) plerr) == EHOSTUNREACH + && !vt->is_ip4 + && !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))) { + NMPObject oo; + + if (vt->is_ip4) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (conf_o); + + nmp_object_stackinit (&oo, + NMP_OBJECT_TYPE_IP4_ROUTE, + &((NMPlatformIP4Route) { + .network = r->gateway, + .plen = 32, + .metric = r->metric, + .rt_source = r->rt_source, + .table_coerced = r->table_coerced, + })); + } else { + const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (conf_o); + + nmp_object_stackinit (&oo, + NMP_OBJECT_TYPE_IP6_ROUTE, + &((NMPlatformIP6Route) { + .network = r->gateway, + .plen = 128, + .metric = r->metric, + .rt_source = r->rt_source, + .table_coerced = r->table_coerced, + })); + } + + _LOGD ("route-sync: failure to add IPv%c route: %s: %s; try adding direct route to gateway %s", vt->is_ip4 ? '4' : '6', nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)), - nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err))); - } else { - const char *reason = ""; - - if ( -((int) plerr) == ENETUNREACH - && ( vt->is_ip4 - ? !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway - : !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway))) - reason = "; is the gateway directly reachable?"; + nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)), + nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2))); + + plerr2 = nm_platform_ip_route_add (self, + NMP_NLM_FLAG_APPEND + | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, + &oo); + + if (plerr2 != NM_PLATFORM_ERROR_SUCCESS) { + _LOGD ("route-sync: failure to add gateway IPv%c route: %s: %s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)), + nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err))); + } - _LOGW ("route-sync: failure to add IPv%c route: %s: %s%s", + gateway_route_added = TRUE; + goto sync_route_add; + } else { + _LOGW ("route-sync: failure to add IPv%c route: %s: %s", vt->is_ip4 ? '4' : '6', nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)), - nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)), - reason); + nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err))); success = FALSE; } } @@ -4713,6 +4987,7 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi "%s" /* encap limit */ "%s" /* flow label */ "%s" /* proto */ + " flags 0x%x" "", nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)), nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)), @@ -4721,7 +4996,8 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass), nm_sprintf_buf (str_encap, " encap-limit %u", lnk->encap_limit), nm_sprintf_buf (str_flow, " flow-label 0x05%x", lnk->flow_label), - nm_sprintf_buf (str_proto, " proto %u", lnk->proto)); + nm_sprintf_buf (str_proto, " proto %u", lnk->proto), + (guint) lnk->flags); return buf; } @@ -4842,6 +5118,43 @@ nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len } const char * +nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len) +{ + char str_owner[50]; + char str_group[50]; + char str_type[50]; + const char *type; + + if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len)) + return buf; + + if (lnk->type == IFF_TUN) + type = "tun"; + else if (lnk->type == IFF_TAP) + type = "tap"; + else + type = nm_sprintf_buf (str_type, "tun type %u", (guint) lnk->type); + + g_snprintf (buf, len, + "%s" /* type */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + "%s" /* persist */ + "%s" /* owner */ + "%s" /* group */ + "", + type, + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + lnk->persist ? " persist" : "", + lnk->owner_valid ? nm_sprintf_buf (str_owner, " owner %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_buf (str_group, " group %u", (guint) lnk->group) : ""); + return buf; +} + +const char * nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len) { char *b; @@ -5043,10 +5356,10 @@ NM_UTILS_FLAGS2STR_DEFINE (nm_platform_addr_flags2str, unsigned, NM_UTILS_FLAGS2STR (IFA_F_OPTIMISTIC, "optimistic"), NM_UTILS_FLAGS2STR (IFA_F_HOMEADDRESS, "homeaddress"), NM_UTILS_FLAGS2STR (IFA_F_DEPRECATED, "deprecated"), - NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"), NM_UTILS_FLAGS2STR (IFA_F_PERMANENT, "permanent"), NM_UTILS_FLAGS2STR (IFA_F_MANAGETEMPADDR, "mngtmpaddr"), NM_UTILS_FLAGS2STR (IFA_F_NOPREFIXROUTE, "noprefixroute"), + NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"), ); NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int, @@ -5537,7 +5850,8 @@ nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState obj->tclass, obj->encap_limit, obj->proto, - obj->flow_label); + obj->flow_label, + obj->flags); } int @@ -5552,6 +5866,7 @@ nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6 NM_CMP_FIELD (a, b, encap_limit); NM_CMP_FIELD (a, b, flow_label); NM_CMP_FIELD (a, b, proto); + NM_CMP_FIELD (a, b, flags); return 0; } @@ -5670,6 +5985,38 @@ nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b) } void +nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h) +{ + nm_hash_update_vals (h, + obj->type, + obj->owner, + obj->group, + NM_HASH_COMBINE_BOOLS (guint8, + obj->owner_valid, + obj->group_valid, + obj->pi, + obj->vnet_hdr, + obj->multi_queue, + obj->persist)); +} + +int +nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b) +{ + NM_CMP_SELF (a, b); + NM_CMP_FIELD (a, b, type); + NM_CMP_FIELD (a, b, owner); + NM_CMP_FIELD (a, b, group); + NM_CMP_FIELD_BOOL (a, b, owner_valid); + NM_CMP_FIELD_BOOL (a, b, group_valid); + NM_CMP_FIELD_BOOL (a, b, pi); + NM_CMP_FIELD_BOOL (a, b, vnet_hdr); + NM_CMP_FIELD_BOOL (a, b, multi_queue); + NM_CMP_FIELD_BOOL (a, b, persist); + return 0; +} + +void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h) { nm_hash_update_vals (h, @@ -6260,14 +6607,13 @@ nm_platform_cache_update_emit_signal (NMPlatform *self, const NMPObject *o; const NMPClass *klass; - nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED)); + nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED)); ASSERT_nmp_cache_ops (nm_platform_get_cache (self), cache_op, obj_old, obj_new); - nm_assert (NM_IN_SET (nm_platform_netns_get (self), - NULL, - nmp_netns_get_current ())); - NMTST_ASSERT_PLATFORM_NETNS_CURRENT (self); switch (cache_op) { diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index f6bf02bf..e6cef63b 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -25,6 +25,7 @@ #include <linux/if.h> #include <linux/if_addr.h> #include <linux/if_link.h> +#include <linux/ip6_tunnel.h> #include "nm-dbus-interface.h" #include "nm-core-types-internal.h" @@ -32,6 +33,7 @@ #include "nm-core-utils.h" #include "nm-setting-vlan.h" #include "nm-setting-wired.h" +#include "nm-setting-ip-tunnel.h" #define NM_TYPE_PLATFORM (nm_platform_get_type ()) #define NM_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform)) @@ -169,6 +171,34 @@ typedef enum { /*< skip >*/ NM_PLATFORM_ERROR_CANT_SET_MTU, } NMPlatformError; +typedef enum { + + /* match-flags are strictly inclusive. That means, + * by default nothing is matched, but if you enable a particular + * flag, a candidate that matches passes the check. + * + * In other words: adding more flags can only extend the result + * set of matching objects. + * + * Also, the flags form partitions. Like, an address can be either of + * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for + * the ADDRSTATE match types. + */ + NM_PLATFORM_MATCH_WITH_NONE = 0, + + NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL = (1LL << 0), + NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL = (1LL << 1), + NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY = NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL + | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL, + + NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL = (1LL << 2), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE = (1LL << 3), + NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED = (1LL << 4), + NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY = NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE + | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED, +} NMPlatformMatchFlags; + #define NM_PLATFORM_LINK_OTHER_NETNS (-1) #define __NMPlatformObject_COMMON \ @@ -254,6 +284,8 @@ struct _NMPlatformObject { __NMPlatformObject_COMMON; }; +#define NM_PLATFORM_IP_ADDRESS_CAST(address) \ + NM_CONSTCAST (NMPlatformIPAddress, (address), NMPlatformIPXAddress, NMPlatformIP4Address, NMPlatformIP6Address) #define __NMPlatformIPAddress_COMMON \ __NMPlatformObject_COMMON; \ @@ -402,7 +434,7 @@ typedef union { * do not exist from the point-of-view of platform users. * Such a route is not alive, according to nmp_object_is_alive(). * - * XXX: currently we ignore all flags except RTM_F_CLONED + * NOTE: currently we ignore all flags except RTM_F_CLONED * and RTNH_F_ONLINK for IPv4. * We also may not properly consider the flags as part of the ID * in route-cmp. */ \ @@ -604,6 +636,7 @@ typedef struct { guint8 encap_limit; guint8 proto; guint flow_label; + guint32 flags; } NMPlatformLnkIp6Tnl; typedef struct { @@ -651,6 +684,21 @@ typedef struct { } NMPlatformLnkSit; typedef struct { + guint32 owner; + guint32 group; + + guint8 type; + + bool owner_valid:1; + bool group_valid:1; + + bool pi:1; + bool vnet_hdr:1; + bool multi_queue:1; + bool persist:1; +} NMPlatformLnkTun; + +typedef struct { /* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */ guint16 id; NMVlanFlags flags; @@ -677,15 +725,6 @@ typedef struct { bool l3miss:1; } NMPlatformLnkVxlan; -typedef struct { - gint64 owner; - gint64 group; - const char *mode; - bool no_pi:1; - bool vnet_hdr:1; - bool multi_queue:1; -} NMPlatformTunProperties; - typedef enum { NM_PLATFORM_LINK_DUPLEX_UNKNOWN, NM_PLATFORM_LINK_DUPLEX_HALF, @@ -714,6 +753,8 @@ typedef struct { gboolean (*sysctl_set) (NMPlatform *, const char *pathid, int dirfd, const char *path, const char *value); char * (*sysctl_get) (NMPlatform *, const char *pathid, int dirfd, const char *path); + void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type); + gboolean (*link_add) (NMPlatform *, const char *name, NMLinkType type, @@ -809,12 +850,15 @@ typedef struct { const NMPlatformLnkSit *props, const NMPlatformLink **out_link); + gboolean (*link_tun_add) (NMPlatform *platform, + const char *name, + const NMPlatformLnkTun *props, + const NMPlatformLink **out_link, + int *out_fd); + gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link); gboolean (*infiniband_partition_delete) (NMPlatform *, int parent, int p_key); - gboolean (*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); - gboolean (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps); gboolean (*wifi_get_bssid) (NMPlatform *, int ifindex, guint8 *bssid); GByteArray *(*wifi_get_ssid) (NMPlatform *, int ifindex); @@ -1028,6 +1072,8 @@ gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */); int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname); +void nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type); + const NMPObject *nm_platform_link_get_obj (NMPlatform *self, int ifindex, gboolean visible_only); @@ -1071,7 +1117,21 @@ gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex); gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex); guint32 nm_platform_link_get_mtu (NMPlatform *self, int ifindex); gboolean nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex); + gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length); + +static inline GBytes * +nm_platform_link_get_address_as_bytes (NMPlatform *self, int ifindex) +{ + gconstpointer p; + gsize l; + + p = nm_platform_link_get_address (self, ifindex, &l); + return p + ? g_bytes_new (p, l) + : NULL; +} + int nm_platform_link_get_master (NMPlatform *self, int slave); gboolean nm_platform_link_can_assume (NMPlatform *self, int ifindex); @@ -1083,6 +1143,10 @@ const char *nm_platform_link_get_type_name (NMPlatform *self, int ifindex); gboolean nm_platform_link_refresh (NMPlatform *self, int ifindex); void nm_platform_process_events (NMPlatform *self); +const NMPlatformLink *nm_platform_process_events_ensure_link (NMPlatform *self, + int ifindex, + const char *ifname); + gboolean nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmware); gboolean nm_platform_link_set_down (NMPlatform *self, int ifindex); gboolean nm_platform_link_set_arp (NMPlatform *self, int ifindex); @@ -1132,6 +1196,7 @@ const NMPlatformLnkMacsec *nm_platform_link_get_lnk_macsec (NMPlatform *self, in const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); +const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link); @@ -1159,16 +1224,6 @@ NMPlatformError nm_platform_link_vxlan_add (NMPlatform *self, const NMPlatformLnkVxlan *props, const NMPlatformLink **out_link); -NMPlatformError nm_platform_link_tun_add (NMPlatform *self, - const char *name, - gboolean tap, - gint64 owner, - gint64 group, - gboolean pi, - gboolean vnet_hdr, - gboolean multi_queue, - const NMPlatformLink **out_link); - NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self, int parent, int p_key, @@ -1179,7 +1234,9 @@ NMPlatformError nm_platform_link_infiniband_delete (NMPlatform *self, gboolean nm_platform_link_infiniband_get_properties (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode); gboolean nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_peer_ifindex); -gboolean nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *properties); +gboolean nm_platform_link_tun_get_properties (NMPlatform *self, + int ifindex, + NMPlatformLnkTun *out_properties); gboolean nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps); gboolean nm_platform_wifi_get_bssid (NMPlatform *self, int ifindex, guint8 *bssid); @@ -1227,6 +1284,11 @@ NMPlatformError nm_platform_link_sit_add (NMPlatform *self, const char *name, const NMPlatformLnkSit *props, const NMPlatformLink **out_link); +NMPlatformError nm_platform_link_tun_add (NMPlatform *self, + const char *name, + const NMPlatformLnkTun *props, + const NMPlatformLink **out_link, + int *out_fd); const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address); @@ -1251,8 +1313,8 @@ gboolean nm_platform_ip6_address_add (NMPlatform *self, guint32 flags); gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address); gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen); -gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresse); -gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GPtrArray *known_addresses, gboolean keep_link_local); +gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses); +gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses, gboolean full_sync); gboolean nm_platform_ip_address_flush (NMPlatform *self, int addr_family, int ifindex); @@ -1310,6 +1372,7 @@ const char *nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char * const char *nm_platform_lnk_macsec_to_string (const NMPlatformLnkMacsec *lnk, char *buf, gsize len); const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len); const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len); +const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len); const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len); const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len); const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len); @@ -1333,6 +1396,7 @@ int nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpI int nm_platform_lnk_macsec_cmp (const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b); int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b); int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b); +int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b); int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b); int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b); int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b); @@ -1368,6 +1432,7 @@ void nm_platform_lnk_ipip_hash_update (const NMPlatformLnkIpIp *obj, NMHashState void nm_platform_lnk_macsec_hash_update (const NMPlatformLnkMacsec *obj, NMHashState *h); void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHashState *h); void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h); +void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h); void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h); void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h); diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c index d8561aef..f1092fe9 100644 --- a/src/platform/nmp-netns.c +++ b/src/platform/nmp-netns.c @@ -200,8 +200,8 @@ _stack_current_ns_types (NMPNetns *netns, int ns_types) } for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) { - if ( NM_FLAGS_HAS (ns_types, ns_types_check[i]) - && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) { + if ( NM_FLAGS_ANY (ns_types, ns_types_check[i]) + && NM_FLAGS_ANY (info->ns_types, ns_types_check[i])) { res = NM_FLAGS_SET (res, ns_types_check[i]); ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]); } diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c index 1a9b9325..29bb999b 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -325,7 +325,7 @@ _vlan_xgress_qos_mappings_cmp (guint n_map, static void _vlan_xgress_qos_mappings_cpy (guint *dst_n_map, - const NMVlanQosMapping **dst_map, + NMVlanQosMapping **dst_map, guint src_n_map, const NMVlanQosMapping *src_map) { @@ -388,7 +388,7 @@ _nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gb /* The link contains internal fields that are combined by * properties from netlink and udev. Update those properties */ - /* When a link is not in netlink, it's udev fields don't matter. */ + /* When a link is not in netlink, its udev fields don't matter. */ if (obj->_link.netlink.is_in_netlink) { driver = _link_get_driver (obj->_link.udev.device, obj->link.kind, @@ -532,7 +532,7 @@ _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type) } const NMPObject * -nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj) +nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj) { const NMPClass *klass = nmp_class_from_type (obj_type); @@ -865,12 +865,6 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2) return c; } -gboolean -nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2) -{ - return nmp_object_cmp (obj1, obj2) == 0; -} - /* @src is a const object, which is not entirely correct for link types, where * we increase the ref count for src->_link.udev.device. * Hence, nmp_object_copy() can violate the const promise of @src. @@ -922,11 +916,11 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src) { dst->lnk_vlan = src->lnk_vlan; _vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_ingress_qos_map, - &dst->_lnk_vlan.ingress_qos_map, + NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map), src->_lnk_vlan.n_ingress_qos_map, src->_lnk_vlan.ingress_qos_map); _vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_egress_qos_map, - &dst->_lnk_vlan.egress_qos_map, + NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map), src->_lnk_vlan.n_egress_qos_map, src->_lnk_vlan.egress_qos_map); } @@ -1088,7 +1082,7 @@ nmp_object_id_hash (const NMPObject *obj) NMHashState h; if (!obj) - return 0; + return nm_hash_static (914932607u); nm_hash_init (&h, 914932607u); nmp_object_id_hash_update (obj, &h); @@ -1532,8 +1526,6 @@ const NMPLookup * nmp_lookup_init_obj_type (NMPLookup *lookup, NMPObjectType obj_type) { - NMPObject *o; - nm_assert (lookup); switch (obj_type) { @@ -1544,7 +1536,7 @@ nmp_lookup_init_obj_type (NMPLookup *lookup, case NMP_OBJECT_TYPE_IP6_ROUTE: case NMP_OBJECT_TYPE_QDISC: case NMP_OBJECT_TYPE_TFILTER: - o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); + _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE; return _L (lookup); default: @@ -1776,6 +1768,54 @@ nmp_cache_lookup_link_full (const NMPCache *cache, /*****************************************************************************/ +static NMDedupMultiIdxMode +_obj_get_add_mode (const NMPObject *obj) +{ + /* new objects are usually appended to the list. Except for + * addresses, which are prepended during `ip address add`. + * + * Actually, for routes it is more complicated, because depending on + * `ip route append`, `ip route replace`, `ip route prepend`, the object + * will be added at the tail, at the front, or even replace an element + * in the list. However, that is handled separately by nmp_cache_update_netlink_route() + * and of no concern here. */ + if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS)) + return NM_DEDUP_MULTI_IDX_MODE_PREPEND; + return NM_DEDUP_MULTI_IDX_MODE_APPEND; +} + +static void +_idxcache_update_order_for_dump (NMPCache *cache, + const NMDedupMultiEntry *entry) +{ + const NMPClass *klass; + const guint8 *i_idx_type; + const NMDedupMultiEntry *entry2; + + nm_dedup_multi_entry_reorder (entry, NULL, TRUE); + + klass = NMP_OBJECT_GET_CLASS (entry->obj); + for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { + NMPCacheIdType id_type = *i_idx_type; + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) + continue; + + entry2 = nm_dedup_multi_index_lookup_obj (cache->multi_idx, + _idx_type_get (cache, id_type), + entry->obj); + if (!entry2) + continue; + + nm_assert (entry2 != entry); + nm_assert (entry2->obj == entry->obj); + + nm_dedup_multi_entry_reorder (entry2, NULL, TRUE); + } +} + static void _idxcache_update_other_cache_ids (NMPCache *cache, NMPCacheIdType cache_id_type, @@ -1835,7 +1875,7 @@ _idxcache_update_other_cache_ids (NMPCache *cache, obj_new, is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE - : NM_DEDUP_MULTI_IDX_MODE_APPEND, + : _obj_get_add_mode (obj_new), is_dump ? NULL : entry_order, @@ -1913,7 +1953,7 @@ _idxcache_update (NMPCache *cache, obj_new, is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE - : NM_DEDUP_MULTI_IDX_MODE_APPEND, + : _obj_get_add_mode (obj_new), NULL, entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, NULL, @@ -2170,6 +2210,8 @@ nmp_cache_update_netlink (NMPCache *cache, } if (nmp_object_equal (obj_old, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump (cache, entry_old); nm_dedup_multi_entry_set_dirty (entry_old, FALSE); NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; @@ -2243,6 +2285,8 @@ nmp_cache_update_netlink_route (NMPCache *cache, } if (nmp_object_equal (entry_old->obj, obj_hand_over)) { + if (is_dump) + _idxcache_update_order_for_dump (cache, entry_old); nm_dedup_multi_entry_set_dirty (entry_old, FALSE); goto update_done; } @@ -2266,9 +2310,8 @@ update_done: * properly find @obj_replaced. */ resync_required = FALSE; entry_replace = NULL; - if (is_dump) { + if (is_dump) goto out; - } if (!entry_new) { if ( NM_FLAGS_HAS (nlmsgflags, NLM_F_REPLACE) @@ -2283,6 +2326,8 @@ update_done: goto out; } + /* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID + * index. For all other indexes their order becomes messed up. */ entry_cur = _lookup_entry_with_idx_type (cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj); @@ -2735,6 +2780,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_sit_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp, }, + [NMP_OBJECT_TYPE_LNK_TUN - 1] = { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_LNK_TUN, + .sizeof_data = sizeof (NMPObjectLnkTun), + .sizeof_public = sizeof (NMPlatformLnkTun), + .obj_type_name = "tun", + .lnk_link_type = NM_LINK_TYPE_TUN, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_tun_to_string, + .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_tun_hash_update, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_tun_cmp, + }, [NMP_OBJECT_TYPE_LNK_VLAN - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_VLAN, diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h index e3862f2d..f473f462 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -88,7 +88,7 @@ typedef enum { /*< skip >*/ /* Consider all the destination fields of a route, that is, the ID without the ifindex * and gateway (meaning: network/plen,metric). * The reason for this is that `ip route change` can replace an existing route - * and modify it's ifindex/gateway. Effectively, that means it deletes an existing + * and modify its ifindex/gateway. Effectively, that means it deletes an existing * route and adds a different one (as the ID of the route changes). However, it only * sends one RTM_NEWADDR notification without notifying about the deletion. We detect * that by having this index to contain overlapping routes which require special @@ -197,6 +197,10 @@ typedef struct { } NMPObjectLnkSit; typedef struct { + NMPlatformLnkTun _public; +} NMPObjectLnkTun; + +typedef struct { NMPlatformLnkVlan _public; guint n_ingress_qos_map; @@ -265,6 +269,9 @@ struct _NMPObject { NMPlatformLnkSit lnk_sit; NMPObjectLnkSit _lnk_sit; + NMPlatformLnkTun lnk_tun; + NMPObjectLnkTun _lnk_tun; + NMPlatformLnkVlan lnk_vlan; NMPObjectLnkVlan _lnk_vlan; @@ -457,11 +464,12 @@ nmp_object_ref (const NMPObject *obj) return (const NMPObject *) nm_dedup_multi_obj_ref ((const NMDedupMultiObj *) obj); } -static inline const NMPObject * +static inline void nmp_object_unref (const NMPObject *obj) { + nm_assert (!obj || NMP_OBJECT_IS_VALID (obj)); + nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj); - return NULL; } #define nm_clear_nmp_object(ptr) \ @@ -482,7 +490,7 @@ nmp_object_unref (const NMPObject *obj) NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob); NMPObject *nmp_object_new_link (int ifindex); -const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj); +const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj); static inline NMPObject * nmp_object_stackinit_obj (NMPObject *obj, const NMPObject *src) @@ -500,7 +508,13 @@ const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifinde const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size); void nmp_object_hash_update (const NMPObject *obj, NMHashState *h); int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2); -gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2); + +static inline gboolean +nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2) +{ + return nmp_object_cmp (obj1, obj2) == 0; +} + void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only); NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only); @@ -725,6 +739,16 @@ const NMDedupMultiEntry *nm_platform_lookup_entry (NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj); +static inline const NMPObject * +nm_platform_lookup_obj (NMPlatform *platform, + NMPCacheIdType cache_id_type, + const NMPObject *obj) +{ + return nm_dedup_multi_entry_get_obj (nm_platform_lookup_entry (platform, + cache_id_type, + obj)); +} + static inline const NMDedupMultiHeadEntry * nm_platform_lookup_obj_type (NMPlatform *platform, NMPObjectType obj_type) diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build new file mode 100644 index 00000000..0571efac --- /dev/null +++ b/src/platform/tests/meson.build @@ -0,0 +1,37 @@ +test_units = [ + ['test-link-fake', 'test-link.c', 60], + ['test-link-linux', 'test-link.c', 60], + ['test-address-fake', 'test-address.c'], + ['test-address-linux', 'test-address.c'], + ['test-general', 'test-general.c'], + ['test-nmp-object', 'test-nmp-object.c'], + ['test-route-fake', 'test-route.c'], + ['test-route-linux', 'test-route.c'], + ['test-cleanup-fake', 'test-cleanup.c'], + ['test-cleanup-linux', 'test-cleanup.c'], +] + +foreach test_unit: test_units + exe = executable( + 'platform-' + test_unit[0], + test_unit[1], + dependencies: test_nm_dep, + c_args: test_cflags_platform + ) + + test( + 'platform/' + test_unit[0], + test_script, + timeout: test_unit.length() > 2 ? test_unit[2] : 30, + args: test_args + [exe.full_path()] + ) +endforeach + +test = 'monitor' + +executable( + test, + test + '.c', + dependencies: test_nm_dep, + c_args: test_cflags_platform +) diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c index e1220052..f0e3e6cf 100644 --- a/src/platform/tests/monitor.c +++ b/src/platform/tests/monitor.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c index 93851ff7..ddef8853 100644 --- a/src/platform/tests/test-address.c +++ b/src/platform/tests/test-address.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -149,7 +148,7 @@ test_ip6_address_general (void) /* Add address again (aka update) */ nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags); - accept_signals (address_changed, 0, 1); + accept_signals (address_changed, 0, 2); /* Test address listing */ addresses = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex); diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c index 937cd12c..a213b31f 100644 --- a/src/platform/tests/test-cleanup.c +++ b/src/platform/tests/test-cleanup.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c index d56e681e..7885c083 100644 --- a/src/platform/tests/test-common.c +++ b/src/platform/tests/test-common.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -24,6 +23,7 @@ #include <sched.h> #include <sys/wait.h> #include <fcntl.h> +#include <linux/if_tun.h> #include "test-common.h" @@ -628,7 +628,10 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms) const NMPlatformLink * nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms) { - return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms); + return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, + timeout_ms + ? nm_utils_get_monotonic_timestamp_ms () + timeout_ms + : 0); } const NMPlatformLink * @@ -636,6 +639,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType { const NMPlatformLink *plink; gint64 now; + gboolean waited_once = FALSE; _init_platform (&platform, FALSE); @@ -647,29 +651,24 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type)) return plink; - if (until_ms < now) + if (until_ms == 0) { + /* don't wait, don't even poll the socket. */ + return NULL; + } + + if ( waited_once + && until_ms < now) { + /* timeout reached (+ we already waited for a signal at least once). */ return NULL; + } + waited_once = TRUE; + /* regardless of whether timeout is already reached, we poll the netlink + * socket a bit. */ nmtstp_wait_for_signal (platform, until_ms - now); } } -const NMPlatformLink * -nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms) -{ - return nmtstp_assert_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms); -} - -const NMPlatformLink * -nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms) -{ - const NMPlatformLink *plink; - - plink = nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms); - g_assert (plink); - return plink; -} - /*****************************************************************************/ int @@ -1284,6 +1283,10 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform, const NMPlatformLink *pllink = NULL; gboolean success; char buffer[INET6_ADDRSTRLEN]; + char encap[20]; + char tclass[20]; + gboolean encap_ignore; + gboolean tclass_inherit; g_assert (nm_utils_is_valid_iface_name (name, NULL)); @@ -1309,15 +1312,18 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform, g_assert_not_reached (); } - success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x", + encap_ignore = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT); + tclass_inherit = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS); + + success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %s encaplimit %s flowlabel %x", name, mode, dev, nm_utils_inet6_ntop (&lnk->local, NULL), nm_utils_inet6_ntop (&lnk->remote, buffer), lnk->ttl, - lnk->tclass, - lnk->encap_limit, + tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass), + encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit), lnk->flow_label); if (success) pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IP6TNL, 100); @@ -1463,6 +1469,70 @@ nmtstp_link_sit_add (NMPlatform *platform, } const NMPlatformLink * +nmtstp_link_tun_add (NMPlatform *platform, + gboolean external_command, + const char *name, + const NMPlatformLnkTun *lnk, + int *out_fd) +{ + const NMPlatformLink *pllink = NULL; + NMPlatformError plerr; + int err; + + g_assert (nm_utils_is_valid_iface_name (name, NULL)); + g_assert (lnk); + g_assert (NM_IN_SET (lnk->type, IFF_TUN, IFF_TAP)); + g_assert (!out_fd || *out_fd == -1); + + if (!lnk->persist) { + /* ip tuntap does not support non-persistent devices. + * + * Add this device only via NMPlatform. */ + if (external_command == -1) + external_command = FALSE; + } + + external_command = nmtstp_run_command_check_external (external_command); + + _init_platform (&platform, external_command); + + if (external_command) { + g_assert (lnk->persist); + + err = nmtstp_run_command ("ip tuntap add" + " mode %s" + "%s" /* user */ + "%s" /* group */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + " name %s", + lnk->type == IFF_TUN ? "tun" : "tap", + lnk->owner_valid ? nm_sprintf_bufa (100, " user %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_bufa (100, " group %u", (guint) lnk->group) : "", + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + name); + /* Older versions of iproute2 don't support adding devices. + * On failure, fallback to using platform code. */ + if (err == 0) + pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_TUN, 100); + else + g_error ("failure to add tun/tap device via ip-route"); + } else { + g_assert (lnk->persist || out_fd); + plerr = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd); + g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS); + } + + g_assert (pllink); + g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_TUN); + g_assert_cmpstr (pllink->name, ==, name); + return pllink; +} + +const NMPlatformLink * nmtstp_link_vxlan_add (NMPlatform *platform, gboolean external_command, const char *name, diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h index fb406a3f..bd02b0d7 100644 --- a/src/platform/tests/test-common.h +++ b/src/platform/tests/test-common.h @@ -1,3 +1,21 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2016 - 2017 Red Hat, Inc. + */ + #include <stdlib.h> #include <unistd.h> #include <syslog.h> @@ -111,8 +129,11 @@ const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const ch g_assert_not_reached (); \ } G_STMT_END -const NMPlatformLink *nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms); -const NMPlatformLink *nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms); +#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_ms) \ + nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_ms)) + +#define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \ + nmtst_assert_nonnull (nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms)) /*****************************************************************************/ @@ -289,6 +310,11 @@ const NMPlatformLink *nmtstp_link_sit_add (NMPlatform *platform, gboolean external_command, const char *name, const NMPlatformLnkSit *lnk); +const NMPlatformLink *nmtstp_link_tun_add (NMPlatform *platform, + gboolean external_command, + const char *name, + const NMPlatformLnkTun *lnk, + int *out_fd); const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform, gboolean external_command, const char *name, diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c index ef78cc24..dcd600ee 100644 --- a/src/platform/tests/test-link.c +++ b/src/platform/tests/test-link.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -24,6 +23,7 @@ #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> +#include <linux/if_tun.h> #include "platform/nmp-object.h" #include "platform/nmp-netns.h" @@ -698,6 +698,8 @@ test_software_detect (gconstpointer user_data) const NMPObject *lnk; guint i_step; const gboolean ext = test_data->external_command; + NMPlatformLnkTun lnk_tun; + nm_auto_close int tun_fd = -1; nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME); ifindex_parent = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex; @@ -761,13 +763,27 @@ test_software_detect (gconstpointer user_data) gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_tunnel", NULL) != 0; } - lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15"); - lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16"); - lnk_ip6tnl.parent_ifindex = ifindex_parent; - lnk_ip6tnl.tclass = 20; - lnk_ip6tnl.encap_limit = 6; - lnk_ip6tnl.flow_label = 1337; - lnk_ip6tnl.proto = IPPROTO_IPV6; + switch (test_data->test_mode) { + case 0: + lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 20; + lnk_ip6tnl.encap_limit = 6; + lnk_ip6tnl.flow_label = 1337; + lnk_ip6tnl.proto = IPPROTO_IPV6; + break; + case 1: + lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::17"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::18"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 0; + lnk_ip6tnl.encap_limit = 0; + lnk_ip6tnl.flow_label = 1338; + lnk_ip6tnl.proto = IPPROTO_IPV6; + lnk_ip6tnl.flags = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS; + break; + } if (!nmtstp_link_ip6tnl_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { if (gracefully_skip) { @@ -879,6 +895,38 @@ test_software_detect (gconstpointer user_data) g_assert (nmtstp_link_vxlan_add (NULL, ext, DEVICE_NAME, &lnk_vxlan)); break; } + case NM_LINK_TYPE_TUN: { + gboolean owner_valid = nmtst_get_rand_bool (); + gboolean group_valid = nmtst_get_rand_bool (); + + switch (test_data->test_mode) { + case 0: + lnk_tun = (NMPlatformLnkTun) { + .type = nmtst_get_rand_bool () ? IFF_TUN : IFF_TAP, + .owner = owner_valid ? getuid () : 0, + .owner_valid = owner_valid, + .group = group_valid ? getgid () : 0, + .group_valid = group_valid, + .pi = nmtst_get_rand_bool (), + .vnet_hdr = nmtst_get_rand_bool (), + .multi_queue = nmtst_get_rand_bool (), + + /* if we add the device via iproute2 (external), we can only + * create persistent devices. */ + .persist = (ext == 1) ? TRUE : nmtst_get_rand_bool (), + }; + break; + default: + g_assert_not_reached (); + break; + } + + g_assert (nmtstp_link_tun_add (NULL, ext, DEVICE_NAME, &lnk_tun, + (!lnk_tun.persist || nmtst_get_rand_bool ()) + ? &tun_fd + : NULL)); + break; + } default: g_assert_not_reached (); } @@ -909,7 +957,13 @@ test_software_detect (gconstpointer user_data) lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, test_data->link_type, &plink); g_assert (plink); g_assert_cmpint (plink->ifindex, ==, ifindex); - g_assert (lnk); + + if ( !lnk + && test_data->link_type == NM_LINK_TYPE_TUN) { + /* this is ok. Kernel apparently does not support tun properties via netlink. We + * fetch them from sysfs below. */ + } else + g_assert (lnk); switch (test_data->link_type) { case NM_LINK_TYPE_GRE: { @@ -931,15 +985,31 @@ test_software_detect (gconstpointer user_data) case NM_LINK_TYPE_IP6TNL: { const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; - g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL)); - g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent); - nmtst_assert_ip6_address (&plnk->local, "fd01::15"); - nmtst_assert_ip6_address (&plnk->remote, "fd01::16"); - g_assert_cmpint (plnk->ttl, ==, 0); - g_assert_cmpint (plnk->tclass, ==, 20); - g_assert_cmpint (plnk->encap_limit, ==, 6); - g_assert_cmpint (plnk->flow_label, ==, 1337); - g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6); + switch (test_data->test_mode) { + case 0: + g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address (&plnk->local, "fd01::15"); + nmtst_assert_ip6_address (&plnk->remote, "fd01::16"); + g_assert_cmpint (plnk->ttl, ==, 0); + g_assert_cmpint (plnk->tclass, ==, 20); + g_assert_cmpint (plnk->encap_limit, ==, 6); + g_assert_cmpint (plnk->flow_label, ==, 1337); + g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6); + break; + case 1: + g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address (&plnk->local, "fd01::17"); + nmtst_assert_ip6_address (&plnk->remote, "fd01::18"); + g_assert_cmpint (plnk->ttl, ==, 0); + g_assert_cmpint (plnk->flow_label, ==, 1338); + g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6); + g_assert_cmpint (plnk->flags & 0xFFFF, /* ignore kernel internal flags */ + ==, + IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS); + break; + } break; } case NM_LINK_TYPE_IPIP: { @@ -982,6 +1052,27 @@ test_software_detect (gconstpointer user_data) g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE); break; } + case NM_LINK_TYPE_TUN: { + const NMPlatformLnkTun *plnk; + NMPlatformLnkTun lnk_tun2; + + g_assert ((lnk ? &lnk->lnk_tun : NULL) == nm_platform_link_get_lnk_tun (NM_PLATFORM_GET, ifindex, NULL)); + + /* kernel might not expose tun options via netlink. Either way, try + * to read them (either from platform cache, or fallback to sysfs). + * See also: rh#1547213. */ + if (!nm_platform_link_tun_get_properties (NM_PLATFORM_GET, + ifindex, + &lnk_tun2)) + g_assert_not_reached (); + + plnk = lnk ? &lnk->lnk_tun : &lnk_tun2; + if (lnk) + g_assert (memcmp (plnk, &lnk_tun2, sizeof (NMPlatformLnkTun)) == 0); + + g_assert (nm_platform_lnk_tun_cmp (plnk, &lnk_tun) == 0); + break; + } case NM_LINK_TYPE_VLAN: { const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan; @@ -2248,7 +2339,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data) p = pl_base; for (j = nstack; j >= 1; ) { j--; - if (NM_FLAGS_HAS (stack[j].ns_types, ns_type)) { + if (NM_FLAGS_ANY (stack[j].ns_types, ns_type)) { p = stack[j].pl; break; } @@ -2546,11 +2637,13 @@ _nmtstp_setup_tests (void) g_test_add_func ("/link/external", test_external); test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0); - test_software_detect_add ("/link/software/detect/ip6tnl", NM_LINK_TYPE_IP6TNL, 0); + test_software_detect_add ("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0); + test_software_detect_add ("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1); test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0); test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0); test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0); test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0); + test_software_detect_add ("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0); test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0); test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0); test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1); diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c index 3228de83..047d7a85 100644 --- a/src/platform/tests/test-nmp-object.c +++ b/src/platform/tests/test-nmp-object.c @@ -54,27 +54,20 @@ test_obj_base (void) gs_unref_object GCancellable *obj_cancellable = g_cancellable_new (); nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link (10); -#define STATIC_ASSERT(cond) \ - G_STMT_START { \ - G_STATIC_ASSERT (cond); \ - G_STATIC_ASSERT_EXPR (cond); \ - g_assert (cond); \ - } G_STMT_END + g_assert (&g->g_type_instance == (void *) &o->_class); + g_assert (&g->g_type_instance.g_class == (void *) &o->_class); - STATIC_ASSERT (&g->g_type_instance == (void *) &o->_class); - STATIC_ASSERT (&g->g_type_instance.g_class == (void *) &o->_class); + g_assert (sizeof (o->parent.parent) == sizeof (GTypeInstance)); - STATIC_ASSERT (sizeof (o->parent.parent) == sizeof (GTypeInstance)); + g_assert (&c->parent == (void *) c); + g_assert (&c->parent.parent.g_type_class == (void *) c); + g_assert (&c->parent.parent.g_type == (void *) c); + g_assert (&c->parent.parent.g_type == &k->g_type); - STATIC_ASSERT (&c->parent == (void *) c); - STATIC_ASSERT (&c->parent.parent.g_type_class == (void *) c); - STATIC_ASSERT (&c->parent.parent.g_type == (void *) c); - STATIC_ASSERT (&c->parent.parent.g_type == &k->g_type); + g_assert (sizeof (c->parent.parent) == sizeof (GTypeClass)); - STATIC_ASSERT (sizeof (c->parent.parent) == sizeof (GTypeClass)); - - STATIC_ASSERT (&o->parent == (void *) o); - STATIC_ASSERT (&o->parent.klass == (void *) &o->_class); + g_assert (&o->parent == (void *) o); + g_assert (&o->parent.klass == (void *) &o->_class); obj = (NMObjBaseInst *) obj_cancellable; g_assert (!NMP_CLASS_IS_VALID ((NMPClass *) obj->klass)); diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c index 13648f16..85b14b57 100644 --- a/src/platform/tests/test-route.c +++ b/src/platform/tests/test-route.c @@ -1,6 +1,5 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager audit support - * +/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c index a5f25b02..db187a1f 100644 --- a/src/platform/wifi/wifi-utils-nl80211.c +++ b/src/platform/wifi/wifi-utils-nl80211.c @@ -22,17 +22,17 @@ #include "nm-default.h" +#include "wifi-utils-nl80211.h" + #include <errno.h> #include <string.h> #include <sys/ioctl.h> #include <net/ethernet.h> #include <unistd.h> -#include <netlink/netlink.h> -#include <netlink/msg.h> #include <linux/nl80211.h> +#include "platform/nm-netlink.h" #include "wifi-utils-private.h" -#include "wifi-utils-nl80211.h" #include "platform/nm-platform.h" #include "platform/nm-platform-utils.h" #include "nm-utils.h" @@ -46,223 +46,14 @@ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } G_STMT_END -/*****************************************************************************/ - -static int -_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len, - const struct nla_policy *policy) -{ - return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy); -} -#define nla_parse(...) _nl_nla_parse(__VA_ARGS__) - -static int -_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla, - const struct nla_policy *policy) -{ - return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy); -} -#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__) - -/***************************************************************************** - * Copied from libnl3/genl: - *****************************************************************************/ - -static void * -genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family, - int hdrlen, int flags, uint8_t cmd, uint8_t version) -{ - struct nlmsghdr *nlh; - struct genlmsghdr hdr = { - .cmd = cmd, - .version = version, - }; - - nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags); - if (nlh == NULL) - return NULL; - - memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr)); - - return (char *) nlmsg_data (nlh) + GENL_HDRLEN; -} - -static void * -genlmsg_data (const struct genlmsghdr *gnlh) -{ - return ((unsigned char *) gnlh + GENL_HDRLEN); -} - -static void * -genlmsg_user_hdr (const struct genlmsghdr *gnlh) -{ - return genlmsg_data (gnlh); -} - -static struct genlmsghdr * -genlmsg_hdr (struct nlmsghdr *nlh) -{ - return nlmsg_data (nlh); -} - -static void * -genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen) -{ - return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen); -} - -static struct nlattr * -genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen) -{ - return genlmsg_user_data (gnlh, hdrlen); -} - -static int -genlmsg_len (const struct genlmsghdr *gnlh) -{ - const struct nlmsghdr *nlh; - - nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN); - return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN); -} - -static int -genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen) -{ - return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen); -} - -static int -genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen) -{ - struct genlmsghdr *ghdr; - - if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN)) - return 0; - - ghdr = nlmsg_data (nlh); - if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen)) - return 0; - - return 1; -} - -static int -genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[], - int maxtype, const struct nla_policy *policy) -{ - struct genlmsghdr *ghdr; - - if (!genlmsg_valid_hdr (nlh, hdrlen)) - return -NLE_MSG_TOOSHORT; - - ghdr = nlmsg_data (nlh); - return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen), - genlmsg_attrlen (ghdr, hdrlen), policy); -} - -/***************************************************************************** - * Reimplementation of libnl3/genl functions: - *****************************************************************************/ - -static int -probe_response (struct nl_msg *msg, void *arg) -{ - static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = { - [CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 }, - [CTRL_ATTR_FAMILY_NAME] = { .type = NLA_STRING, - .maxlen = GENL_NAMSIZ }, - [CTRL_ATTR_VERSION] = { .type = NLA_U32 }, - [CTRL_ATTR_HDRSIZE] = { .type = NLA_U32 }, - [CTRL_ATTR_MAXATTR] = { .type = NLA_U32 }, - [CTRL_ATTR_OPS] = { .type = NLA_NESTED }, - [CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED }, - }; - struct nlattr *tb[CTRL_ATTR_MAX+1]; - struct nlmsghdr *nlh = nlmsg_hdr (msg); - gint32 *response_data = arg; - - if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy)) - return NL_SKIP; - - if (tb[CTRL_ATTR_FAMILY_ID]) - *response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]); - - return NL_STOP; -} - -static int -genl_ctrl_resolve (struct nl_sock *sk, const char *name) -{ - struct nl_msg *msg; - struct nl_cb *cb, *orig; - int rc; - int result = -NLE_OBJ_NOTFOUND; - gint32 response_data = -1; - - if (!(orig = nl_socket_get_cb (sk))) - goto out; - - cb = nl_cb_clone (orig); - nl_cb_put (orig); - if (!cb) - goto out; - - msg = nlmsg_alloc (); - if (!msg) - goto out_cb_free; - - if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, - 0, 0, CTRL_CMD_GETFAMILY, 1)) - goto out_msg_free; - - if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0) - goto out_msg_free; - - rc = nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, probe_response, &response_data); - if (rc < 0) - goto out_msg_free; - - rc = nl_send_auto_complete (sk, msg); - if (rc < 0) - goto out_msg_free; - - rc = nl_recvmsgs (sk, cb); - if (rc < 0) - goto out_msg_free; - - /* If search was successful, request may be ACKed after data */ - rc = nl_wait_for_ack (sk); - if (rc < 0) - goto out_msg_free; - - if (response_data > 0) - result = response_data; - -out_msg_free: - nlmsg_free (msg); -out_cb_free: - nl_cb_put (cb); -out: - if (result >= 0) - _LOGD (LOGD_WIFI, "genl_ctrl_resolve: resolved \"%s\" as 0x%x", name, result); - else - _LOGE (LOGD_WIFI, "genl_ctrl_resolve: failed resolve \"%s\"", name); - return result; -} - -/***************************************************************************** - * </libn-genl-3> - *****************************************************************************/ - typedef struct { WifiData parent; struct nl_sock *nl_sock; - int id; - struct nl_cb *nl_cb; guint32 *freqs; + int id; int num_freqs; int phy; + bool can_wowlan:1; } WifiDataNl80211; static int @@ -292,19 +83,16 @@ error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) static struct nl_msg * _nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags) { - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nlmsg_alloc (); - if (msg) { - genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0); - NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex); - if (phy != -1) - NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy); - } - return msg; + genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex); + if (phy != -1) + NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy); + return g_steal_pointer (&msg); - nla_put_failure: - nlmsg_free (msg); +nla_put_failure: return NULL; } @@ -314,42 +102,37 @@ nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags) return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags); } -/* NOTE: this function consumes 'msg' */ static int _nl80211_send_and_recv (struct nl_sock *nl_sock, - struct nl_cb *nl_cb, struct nl_msg *msg, int (*valid_handler) (struct nl_msg *, void *), void *valid_data) { - struct nl_cb *cb; - int err, done; + int err; + int done = 0; + const struct nl_cb cb = { + .err_cb = error_handler, + .err_arg = &done, + .finish_cb = finish_handler, + .finish_arg = &done, + .ack_cb = ack_handler, + .ack_arg = &done, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; g_return_val_if_fail (msg != NULL, -ENOMEM); - cb = nl_cb_clone (nl_cb); - if (!cb) { - err = -ENOMEM; - goto out; - } - - err = nl_send_auto_complete (nl_sock, msg); + err = nl_send_auto (nl_sock, msg); if (err < 0) - goto out; - - done = 0; - nl_cb_err (cb, NL_CB_CUSTOM, error_handler, &done); - nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &done); - nl_cb_set (cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &done); - if (valid_handler) - nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data); + return err; /* Loop until one of our NL callbacks says we're done; on success * done will be 1, on error it will be < 0. */ while (!done) { - err = nl_recvmsgs (nl_sock, cb); - if (err && err != -NLE_AGAIN) { + err = nl_recvmsgs (nl_sock, &cb); + if (err < 0 && err != -EAGAIN) { /* Kernel scan list can change while we are dumping it, as new scan * results from H/W can arrive. BSS info is assured to be consistent * and we don't need consistent view of whole scan list. Hence do @@ -364,12 +147,9 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock, break; } } - if (err == 0 && done < 0) - err = done; - out: - nl_cb_put (cb); - nlmsg_free (msg); + if (err >= 0 && done < 0) + err = done; return err; } @@ -379,7 +159,7 @@ nl80211_send_and_recv (WifiDataNl80211 *nl80211, int (*valid_handler) (struct nl_msg *, void *), void *valid_data) { - return _nl80211_send_and_recv (nl80211->nl_sock, nl80211->nl_cb, msg, + return _nl80211_send_and_recv (nl80211->nl_sock, msg, valid_handler, valid_data); } @@ -390,8 +170,6 @@ wifi_nl80211_deinit (WifiData *parent) if (nl80211->nl_sock) nl_socket_free (nl80211->nl_sock); - if (nl80211->nl_cb) - nl_cb_put (nl80211->nl_cb); g_free (nl80211->freqs); } @@ -435,12 +213,12 @@ wifi_nl80211_get_mode (WifiData *data) struct nl80211_iface_info iface_info = { .mode = NM_802_11_MODE_UNKNOWN, }; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0); if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler, - &iface_info) < 0) + &iface_info) < 0) return NM_802_11_MODE_UNKNOWN; return iface_info.mode; @@ -450,7 +228,7 @@ static gboolean wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode) { WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0); @@ -470,7 +248,7 @@ wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode) } err = nl80211_send_and_recv (nl80211, msg, NULL, NULL); - return err ? FALSE : TRUE; + return err >= 0; nla_put_failure: nlmsg_free (msg); @@ -481,14 +259,14 @@ static gboolean wifi_nl80211_set_powersave (WifiData *data, guint32 powersave) { WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0); NLA_PUT_U32 (msg, NL80211_ATTR_PS_STATE, powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED); err = nl80211_send_and_recv (nl80211, msg, NULL, NULL); - return err ? FALSE : TRUE; + return err >= 0; nla_put_failure: nlmsg_free (msg); @@ -518,7 +296,7 @@ struct nl80211_bss_info { gboolean valid; }; -#define WLAN_EID_SSID 0 +#define WLAN_EID_SSID 0 static void find_ssid (guint8 *ies, guint32 ies_len, @@ -618,7 +396,7 @@ static void nl80211_get_bss_info (WifiDataNl80211 *nl80211, struct nl80211_bss_info *bss_info) { - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; memset (bss_info, 0, sizeof (*bss_info)); @@ -742,7 +520,7 @@ static void nl80211_get_ap_info (WifiDataNl80211 *nl80211, struct nl80211_station_info *sta_info) { - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_bss_info bss_info; memset (sta_info, 0, sizeof (*sta_info)); @@ -790,35 +568,38 @@ wifi_nl80211_get_qual (WifiData *data) return sta_info.signal; } -#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS static gboolean wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running) { WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; int err; msg = nl80211_alloc_msg (nl80211, - running ? NL80211_CMD_CRIT_PROTOCOL_START : - NL80211_CMD_CRIT_PROTOCOL_STOP, + running + ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */ + : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */, 0); /* Despite the DHCP name, we're using this for any type of IP addressing, * DHCPv4, DHCPv6, and IPv6 SLAAC. */ - NLA_PUT_U16 (msg, NL80211_ATTR_CRIT_PROT_ID, NL80211_CRIT_PROTO_DHCP); + NLA_PUT_U16 (msg, + 179 /* NL80211_ATTR_CRIT_PROT_ID */, + 1 /* NL80211_CRIT_PROTO_DHCP */); if (running) { /* Give DHCP 5 seconds to complete */ - NLA_PUT_U16 (msg, NL80211_ATTR_MAX_CRIT_PROT_DURATION, 5000); + NLA_PUT_U16 (msg, + 180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */, + 5000); } err = nl80211_send_and_recv (nl80211, msg, NULL, NULL); - return err ? FALSE : TRUE; + return err >= 0; nla_put_failure: nlmsg_free (msg); return FALSE; } -#endif struct nl80211_wowlan_info { gboolean enabled; @@ -847,12 +628,14 @@ static gboolean wifi_nl80211_get_wowlan (WifiData *data) { WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_wowlan_info info; + if (!nl80211->can_wowlan) + return FALSE; + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0); nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info); - return info.enabled; } @@ -1062,8 +845,22 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg) WifiData * wifi_nl80211_init (int ifindex) { + static const WifiDataClass klass = { + .struct_size = sizeof (WifiDataNl80211), + .get_mode = wifi_nl80211_get_mode, + .set_mode = wifi_nl80211_set_mode, + .set_powersave = wifi_nl80211_set_powersave, + .get_freq = wifi_nl80211_get_freq, + .find_freq = wifi_nl80211_find_freq, + .get_bssid = wifi_nl80211_get_bssid, + .get_rate = wifi_nl80211_get_rate, + .get_qual = wifi_nl80211_get_qual, + .get_wowlan = wifi_nl80211_get_wowlan, + .indicate_addressing_running = wifi_nl80211_indicate_addressing_running, + .deinit = wifi_nl80211_deinit, + }; WifiDataNl80211 *nl80211; - struct nl_msg *msg; + nm_auto_nlmsg struct nl_msg *msg = NULL; struct nl80211_device_info device_info = {}; char ifname[IFNAMSIZ]; @@ -1073,19 +870,7 @@ wifi_nl80211_init (int ifindex) nm_sprintf_buf (ifname, "if %d", ifindex); } - nl80211 = wifi_data_new (ifindex, sizeof (*nl80211)); - nl80211->parent.get_mode = wifi_nl80211_get_mode; - nl80211->parent.set_mode = wifi_nl80211_set_mode; - nl80211->parent.set_powersave = wifi_nl80211_set_powersave; - nl80211->parent.get_freq = wifi_nl80211_get_freq; - nl80211->parent.find_freq = wifi_nl80211_find_freq; - nl80211->parent.get_bssid = wifi_nl80211_get_bssid; - nl80211->parent.get_rate = wifi_nl80211_get_rate; - nl80211->parent.get_qual = wifi_nl80211_get_qual; -#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS - nl80211->parent.indicate_addressing_running = wifi_nl80211_indicate_addressing_running; -#endif - nl80211->parent.deinit = wifi_nl80211_deinit; + nl80211 = wifi_data_new (&klass, ifindex); nl80211->nl_sock = nl_socket_alloc (); if (nl80211->nl_sock == NULL) @@ -1095,12 +880,10 @@ wifi_nl80211_init (int ifindex) goto error; nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211"); - if (nl80211->id < 0) - goto error; - - nl80211->nl_cb = nl_cb_alloc (NL_CB_DEFAULT); - if (nl80211->nl_cb == NULL) + if (nl80211->id < 0) { + _LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\""); goto error; + } nl80211->phy = -1; @@ -1153,18 +936,15 @@ wifi_nl80211_init (int ifindex) nl80211->freqs = device_info.freqs; nl80211->num_freqs = device_info.num_freqs; nl80211->parent.caps = device_info.caps; - - if (device_info.can_wowlan) - nl80211->parent.get_wowlan = wifi_nl80211_get_wowlan; + nl80211->can_wowlan = device_info.can_wowlan; _LOGI (LOGD_PLATFORM | LOGD_WIFI, "(%s): using nl80211 for WiFi device control", ifname); - return (WifiData *) nl80211; error: - wifi_utils_deinit ((WifiData *) nl80211); + wifi_utils_unref ((WifiData *) nl80211); return NULL; } diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h index 11a0f060..59386514 100644 --- a/src/platform/wifi/wifi-utils-private.h +++ b/src/platform/wifi/wifi-utils-private.h @@ -24,9 +24,8 @@ #include "nm-dbus-interface.h" #include "wifi-utils.h" -struct WifiData { - int ifindex; - NMDeviceWifiCapabilities caps; +typedef struct { + gsize struct_size; NM80211Mode (*get_mode) (WifiData *data); @@ -66,9 +65,14 @@ struct WifiData { gboolean (*set_mesh_ssid) (WifiData *data, const guint8 *ssid, gsize len); gboolean (*indicate_addressing_running) (WifiData *data, gboolean running); +} WifiDataClass; + +struct WifiData { + const WifiDataClass *klass; + int ifindex; + NMDeviceWifiCapabilities caps; }; -gpointer wifi_data_new (int ifindex, gsize len); -void wifi_data_free (WifiData *data); +gpointer wifi_data_new (const WifiDataClass *klass, int ifindex); #endif /* __WIFI_UTILS_PRIVATE_H__ */ diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c index c4d3c999..c8744f79 100644 --- a/src/platform/wifi/wifi-utils-wext.c +++ b/src/platform/wifi/wifi-utils-wext.c @@ -21,17 +21,14 @@ #include "nm-default.h" +#include "wifi-utils-wext.h" + #include <errno.h> #include <string.h> #include <sys/ioctl.h> #include <net/ethernet.h> #include <unistd.h> -#include "wifi-utils-private.h" -#include "wifi-utils-wext.h" -#include "nm-utils.h" -#include "platform/nm-platform-utils.h" - /* Hacks necessary to #include wireless.h; yay for WEXT */ #ifndef __user #define __user @@ -41,6 +38,10 @@ #include <sys/socket.h> #include <linux/wireless.h> +#include "wifi-utils-private.h" +#include "nm-utils.h" +#include "platform/nm-platform-utils.h" + typedef struct { WifiData parent; int fd; @@ -628,6 +629,21 @@ wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range) WifiData * wifi_wext_init (int ifindex, gboolean check_scan) { + static const WifiDataClass klass = { + .struct_size = sizeof (WifiDataWext), + .get_mode = wifi_wext_get_mode, + .set_mode = wifi_wext_set_mode, + .set_powersave = wifi_wext_set_powersave, + .get_freq = wifi_wext_get_freq, + .find_freq = wifi_wext_find_freq, + .get_bssid = wifi_wext_get_bssid, + .get_rate = wifi_wext_get_rate, + .get_qual = wifi_wext_get_qual, + .deinit = wifi_wext_deinit, + .get_mesh_channel = wifi_wext_get_mesh_channel, + .set_mesh_channel = wifi_wext_set_mesh_channel, + .set_mesh_ssid = wifi_wext_set_mesh_ssid, + }; WifiDataWext *wext; struct iw_range range; guint32 response_len = 0; @@ -642,19 +658,7 @@ wifi_wext_init (int ifindex, gboolean check_scan) return NULL; } - wext = wifi_data_new (ifindex, sizeof (*wext)); - wext->parent.get_mode = wifi_wext_get_mode; - wext->parent.set_mode = wifi_wext_set_mode; - wext->parent.set_powersave = wifi_wext_set_powersave; - wext->parent.get_freq = wifi_wext_get_freq; - wext->parent.find_freq = wifi_wext_find_freq; - wext->parent.get_bssid = wifi_wext_get_bssid; - wext->parent.get_rate = wifi_wext_get_rate; - wext->parent.get_qual = wifi_wext_get_qual; - wext->parent.deinit = wifi_wext_deinit; - wext->parent.get_mesh_channel = wifi_wext_get_mesh_channel; - wext->parent.set_mesh_channel = wifi_wext_set_mesh_channel; - wext->parent.set_mesh_ssid = wifi_wext_set_mesh_ssid; + wext = wifi_data_new (&klass, ifindex); wext->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (wext->fd < 0) @@ -730,7 +734,7 @@ wifi_wext_init (int ifindex, gboolean check_scan) return (WifiData *) wext; error: - wifi_utils_deinit ((WifiData *) wext); + wifi_utils_unref ((WifiData *) wext); return NULL; } diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c index d0052121..8818dc9d 100644 --- a/src/platform/wifi/wifi-utils.c +++ b/src/platform/wifi/wifi-utils.c @@ -38,22 +38,19 @@ #include "platform/nm-platform-utils.h" gpointer -wifi_data_new (int ifindex, gsize len) +wifi_data_new (const WifiDataClass *klass, int ifindex) { WifiData *data; - data = g_malloc0 (len); + nm_assert (klass); + nm_assert (klass->struct_size > sizeof (WifiData)); + + data = g_malloc0 (klass->struct_size); + data->klass = klass; data->ifindex = ifindex; return data; } -void -wifi_data_free (WifiData *data) -{ - memset (data, 0, sizeof (*data)); - g_free (data); -} - /*****************************************************************************/ WifiData * @@ -85,14 +82,14 @@ wifi_utils_get_caps (WifiData *data) { g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE); - return data->caps; + return data->caps; } NM80211Mode wifi_utils_get_mode (WifiData *data) { g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN); - return data->get_mode (data); + return data->klass->get_mode (data); } gboolean @@ -104,7 +101,7 @@ wifi_utils_set_mode (WifiData *data, const NM80211Mode mode) || (mode == NM_802_11_MODE_ADHOC), FALSE); /* nl80211 probably doesn't need this */ - return data->set_mode ? data->set_mode (data, mode) : TRUE; + return data->klass->set_mode ? data->klass->set_mode (data, mode) : TRUE; } gboolean @@ -112,14 +109,14 @@ wifi_utils_set_powersave (WifiData *data, guint32 powersave) { g_return_val_if_fail (data != NULL, FALSE); - return data->set_powersave ? data->set_powersave (data, powersave) : TRUE; + return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE; } guint32 wifi_utils_get_freq (WifiData *data) { g_return_val_if_fail (data != NULL, 0); - return data->get_freq (data); + return data->klass->get_freq (data); } guint32 @@ -127,7 +124,7 @@ wifi_utils_find_freq (WifiData *data, const guint32 *freqs) { g_return_val_if_fail (data != NULL, 0); g_return_val_if_fail (freqs != NULL, 0); - return data->find_freq (data, freqs); + return data->klass->find_freq (data, freqs); } gboolean @@ -137,38 +134,40 @@ wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid) g_return_val_if_fail (out_bssid != NULL, FALSE); memset (out_bssid, 0, ETH_ALEN); - return data->get_bssid (data, out_bssid); + return data->klass->get_bssid (data, out_bssid); } guint32 wifi_utils_get_rate (WifiData *data) { g_return_val_if_fail (data != NULL, 0); - return data->get_rate (data); + return data->klass->get_rate (data); } int wifi_utils_get_qual (WifiData *data) { g_return_val_if_fail (data != NULL, 0); - return data->get_qual (data); + return data->klass->get_qual (data); } gboolean wifi_utils_get_wowlan (WifiData *data) { g_return_val_if_fail (data != NULL, 0); - if (!data->get_wowlan) + + if (!data->klass->get_wowlan) return FALSE; - return data->get_wowlan (data); + return data->klass->get_wowlan (data); } void -wifi_utils_deinit (WifiData *data) +wifi_utils_unref (WifiData *data) { g_return_if_fail (data != NULL); - data->deinit (data); - wifi_data_free (data); + + data->klass->deinit (data); + g_free (data); } gboolean @@ -191,8 +190,8 @@ guint32 wifi_utils_get_mesh_channel (WifiData *data) { g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (data->get_mesh_channel != NULL, FALSE); - return data->get_mesh_channel (data); + g_return_val_if_fail (data->klass->get_mesh_channel != NULL, FALSE); + return data->klass->get_mesh_channel (data); } gboolean @@ -200,24 +199,24 @@ wifi_utils_set_mesh_channel (WifiData *data, guint32 channel) { g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (channel <= 13, FALSE); - g_return_val_if_fail (data->set_mesh_channel != NULL, FALSE); - return data->set_mesh_channel (data, channel); + g_return_val_if_fail (data->klass->set_mesh_channel != NULL, FALSE); + return data->klass->set_mesh_channel (data, channel); } gboolean wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len) { g_return_val_if_fail (data != NULL, FALSE); - g_return_val_if_fail (data->set_mesh_ssid != NULL, FALSE); - return data->set_mesh_ssid (data, ssid, len); + g_return_val_if_fail (data->klass->set_mesh_ssid != NULL, FALSE); + return data->klass->set_mesh_ssid (data, ssid, len); } gboolean wifi_utils_indicate_addressing_running (WifiData *data, gboolean running) { g_return_val_if_fail (data != NULL, FALSE); - if (data->indicate_addressing_running) - return data->indicate_addressing_running (data, running); + if (data->klass->indicate_addressing_running) + return data->klass->indicate_addressing_running (data, running); return FALSE; } diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h index 705717b0..2633e965 100644 --- a/src/platform/wifi/wifi-utils.h +++ b/src/platform/wifi/wifi-utils.h @@ -34,7 +34,7 @@ WifiData *wifi_utils_init (int ifindex, gboolean check_scan); int wifi_utils_get_ifindex (WifiData *data); -void wifi_utils_deinit (WifiData *data); +void wifi_utils_unref (WifiData *data); NMDeviceWifiCapabilities wifi_utils_get_caps (WifiData *data); |