summaryrefslogtreecommitdiff
path: root/src/libnm-platform/nm-netlink.c
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2021-02-18 08:13:35 +0100
committerThomas Haller <thaller@redhat.com>2021-02-24 12:48:17 +0100
commit24393744572316d4371ce2ff2c112fb6c3226448 (patch)
tree67887fd4983f5c0ef702ee534becc5f446d08629 /src/libnm-platform/nm-netlink.c
parent39225258d6a8fb0612c4576a75ac24d572742372 (diff)
downloadNetworkManager-24393744572316d4371ce2ff2c112fb6c3226448.tar.gz
build: move "shared/nm-platform" to "src/libnm-platform"
Diffstat (limited to 'src/libnm-platform/nm-netlink.c')
-rw-r--r--src/libnm-platform/nm-netlink.c1518
1 files changed, 1518 insertions, 0 deletions
diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c
new file mode 100644
index 0000000000..7a6d7e045b
--- /dev/null
+++ b/src/libnm-platform/nm-netlink.c
@@ -0,0 +1,1518 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-netlink.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+/*****************************************************************************/
+
+#ifndef SOL_NETLINK
+ #define SOL_NETLINK 270
+#endif
+
+/*****************************************************************************/
+
+#define NL_SOCK_PASSCRED (1 << 1)
+#define NL_MSG_PEEK (1 << 3)
+#define NL_MSG_PEEK_EXPLICIT (1 << 4)
+#define NL_NO_AUTO_ACK (1 << 5)
+
+#ifndef NETLINK_EXT_ACK
+ #define NETLINK_EXT_ACK 11
+#endif
+
+struct nl_msg {
+ int nm_protocol;
+ struct sockaddr_nl nm_src;
+ struct sockaddr_nl nm_dst;
+ struct ucred nm_creds;
+ struct nlmsghdr * nm_nlh;
+ size_t nm_size;
+ bool nm_creds_has : 1;
+};
+
+struct nl_sock {
+ struct sockaddr_nl s_local;
+ struct sockaddr_nl s_peer;
+ int s_fd;
+ int s_proto;
+ unsigned int s_seq_next;
+ unsigned int s_seq_expect;
+ int s_flags;
+ size_t s_bufsize;
+};
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE(nl_nlmsgtype2str,
+ int,
+ NM_UTILS_ENUM2STR(NLMSG_NOOP, "NOOP"),
+ NM_UTILS_ENUM2STR(NLMSG_ERROR, "ERROR"),
+ NM_UTILS_ENUM2STR(NLMSG_DONE, "DONE"),
+ NM_UTILS_ENUM2STR(NLMSG_OVERRUN, "OVERRUN"), );
+
+NM_UTILS_FLAGS2STR_DEFINE(nl_nlmsg_flags2str,
+ int,
+ NM_UTILS_FLAGS2STR(NLM_F_REQUEST, "REQUEST"),
+ NM_UTILS_FLAGS2STR(NLM_F_MULTI, "MULTI"),
+ NM_UTILS_FLAGS2STR(NLM_F_ACK, "ACK"),
+ NM_UTILS_FLAGS2STR(NLM_F_ECHO, "ECHO"),
+ NM_UTILS_FLAGS2STR(NLM_F_ROOT, "ROOT"),
+ NM_UTILS_FLAGS2STR(NLM_F_MATCH, "MATCH"),
+ NM_UTILS_FLAGS2STR(NLM_F_ATOMIC, "ATOMIC"),
+ NM_UTILS_FLAGS2STR(NLM_F_REPLACE, "REPLACE"),
+ NM_UTILS_FLAGS2STR(NLM_F_EXCL, "EXCL"),
+ NM_UTILS_FLAGS2STR(NLM_F_CREATE, "CREATE"),
+ NM_UTILS_FLAGS2STR(NLM_F_APPEND, "APPEND"), );
+
+/*****************************************************************************/
+
+const char *
+nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
+{
+ const char *b;
+ const char *s;
+ guint flags, flags_before;
+ const char *prefix;
+
+ if (!nm_utils_to_string_buffer_init_null(hdr, &buf, &len))
+ return buf;
+
+ b = buf;
+
+ switch (hdr->nlmsg_type) {
+ case RTM_GETLINK:
+ s = "RTM_GETLINK";
+ break;
+ case RTM_NEWLINK:
+ s = "RTM_NEWLINK";
+ break;
+ case RTM_DELLINK:
+ s = "RTM_DELLINK";
+ break;
+ case RTM_SETLINK:
+ s = "RTM_SETLINK";
+ break;
+ case RTM_GETADDR:
+ s = "RTM_GETADDR";
+ break;
+ case RTM_NEWADDR:
+ s = "RTM_NEWADDR";
+ break;
+ case RTM_DELADDR:
+ s = "RTM_DELADDR";
+ break;
+ case RTM_GETROUTE:
+ s = "RTM_GETROUTE";
+ break;
+ case RTM_NEWROUTE:
+ s = "RTM_NEWROUTE";
+ break;
+ case RTM_DELROUTE:
+ s = "RTM_DELROUTE";
+ break;
+ case RTM_GETRULE:
+ s = "RTM_GETRULE";
+ break;
+ case RTM_NEWRULE:
+ s = "RTM_NEWRULE";
+ break;
+ case RTM_DELRULE:
+ s = "RTM_DELRULE";
+ break;
+ case RTM_GETQDISC:
+ s = "RTM_GETQDISC";
+ break;
+ case RTM_NEWQDISC:
+ s = "RTM_NEWQDISC";
+ break;
+ case RTM_DELQDISC:
+ s = "RTM_DELQDISC";
+ break;
+ case RTM_GETTFILTER:
+ s = "RTM_GETTFILTER";
+ break;
+ case RTM_NEWTFILTER:
+ s = "RTM_NEWTFILTER";
+ break;
+ case RTM_DELTFILTER:
+ s = "RTM_DELTFILTER";
+ break;
+ case NLMSG_NOOP:
+ s = "NLMSG_NOOP";
+ break;
+ case NLMSG_ERROR:
+ s = "NLMSG_ERROR";
+ break;
+ case NLMSG_DONE:
+ s = "NLMSG_DONE";
+ break;
+ case NLMSG_OVERRUN:
+ s = "NLMSG_OVERRUN";
+ break;
+ default:
+ s = NULL;
+ break;
+ }
+
+ if (s)
+ nm_utils_strbuf_append_str(&buf, &len, s);
+ else
+ nm_utils_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+ flags = hdr->nlmsg_flags;
+
+ if (!flags) {
+ nm_utils_strbuf_append_str(&buf, &len, ", flags 0");
+ goto flags_done;
+ }
+
+#define _F(f, n) \
+ G_STMT_START \
+ { \
+ if (NM_FLAGS_ALL(flags, f)) { \
+ flags &= ~(f); \
+ nm_utils_strbuf_append(&buf, &len, "%s%s", prefix, n); \
+ if (!flags) \
+ goto flags_done; \
+ prefix = ","; \
+ } \
+ } \
+ G_STMT_END
+
+ prefix = ", flags ";
+ flags_before = flags;
+ _F(NLM_F_REQUEST, "request");
+ _F(NLM_F_MULTI, "multi");
+ _F(NLM_F_ACK, "ack");
+ _F(NLM_F_ECHO, "echo");
+ _F(NLM_F_DUMP_INTR, "dump_intr");
+ _F(0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+ if (flags_before != flags)
+ prefix = ";";
+
+ switch (hdr->nlmsg_type) {
+ case RTM_NEWLINK:
+ case RTM_NEWADDR:
+ case RTM_NEWROUTE:
+ case RTM_NEWQDISC:
+ case RTM_NEWTFILTER:
+ _F(NLM_F_REPLACE, "replace");
+ _F(NLM_F_EXCL, "excl");
+ _F(NLM_F_CREATE, "create");
+ _F(NLM_F_APPEND, "append");
+ break;
+ case RTM_GETLINK:
+ case RTM_GETADDR:
+ case RTM_GETROUTE:
+ case RTM_DELQDISC:
+ case RTM_DELTFILTER:
+ _F(NLM_F_DUMP, "dump");
+ _F(NLM_F_ROOT, "root");
+ _F(NLM_F_MATCH, "match");
+ _F(NLM_F_ATOMIC, "atomic");
+ break;
+ }
+
+#undef _F
+
+ if (flags_before != flags)
+ prefix = ";";
+ nm_utils_strbuf_append(&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+ nm_utils_strbuf_append(&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+ return b;
+}
+
+/*****************************************************************************/
+
+struct nlmsghdr *
+nlmsg_hdr(struct nl_msg *n)
+{
+ return n->nm_nlh;
+}
+
+void *
+nlmsg_reserve(struct nl_msg *n, size_t len, int pad)
+{
+ char * buf = (char *) n->nm_nlh;
+ size_t nlmsg_len = n->nm_nlh->nlmsg_len;
+ size_t tlen;
+
+ nm_assert(pad >= 0);
+
+ if (len > n->nm_size)
+ return NULL;
+
+ tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
+
+ if ((tlen + nlmsg_len) > n->nm_size)
+ return NULL;
+
+ buf += nlmsg_len;
+ n->nm_nlh->nlmsg_len += tlen;
+
+ if (tlen > len)
+ memset(buf + len, 0, tlen - len);
+
+ return buf;
+}
+
+/*****************************************************************************/
+
+struct nlattr *
+nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
+{
+ struct nlattr *nla;
+ int tlen;
+
+ if (attrlen < 0)
+ return NULL;
+
+ tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
+
+ if (tlen > msg->nm_size)
+ return NULL;
+
+ nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+ nla->nla_type = attrtype;
+ nla->nla_len = nla_attr_size(attrlen);
+
+ if (attrlen)
+ memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+ msg->nm_nlh->nlmsg_len = tlen;
+
+ return nla;
+}
+
+/*****************************************************************************/
+
+struct nl_msg *
+nlmsg_alloc_size(size_t len)
+{
+ struct nl_msg *nm;
+
+ if (len < sizeof(struct nlmsghdr))
+ len = sizeof(struct nlmsghdr);
+
+ nm = g_slice_new(struct nl_msg);
+ *nm = (struct nl_msg){
+ .nm_protocol = -1,
+ .nm_size = len,
+ .nm_nlh = g_malloc0(len),
+ };
+ nm->nm_nlh->nlmsg_len = nlmsg_total_size(0);
+ return nm;
+}
+
+/**
+ * Allocate a new netlink message with the default maximum payload size.
+ *
+ * Allocates a new netlink message without any further payload. The
+ * maximum payload size defaults to PAGESIZE or as otherwise specified
+ * with nlmsg_set_default_size().
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
+struct nl_msg *
+nlmsg_alloc(void)
+{
+ return nlmsg_alloc_size(nm_utils_getpagesize());
+}
+
+struct nl_msg *
+nlmsg_alloc_convert(struct nlmsghdr *hdr)
+{
+ struct nl_msg *nm;
+
+ nm = nlmsg_alloc_size(NLMSG_ALIGN(hdr->nlmsg_len));
+ memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+ return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_simple(int nlmsgtype, int flags)
+{
+ struct nl_msg *nm;
+ struct nlmsghdr *new;
+
+ nm = nlmsg_alloc();
+ new = nm->nm_nlh;
+ new->nlmsg_type = nlmsgtype;
+ new->nlmsg_flags = flags;
+ return nm;
+}
+
+void
+nlmsg_free(struct nl_msg *msg)
+{
+ if (!msg)
+ return;
+
+ g_free(msg->nm_nlh);
+ g_slice_free(struct nl_msg, msg);
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad)
+{
+ void *tmp;
+
+ nm_assert(n);
+ nm_assert(data);
+ nm_assert(len > 0);
+ nm_assert(pad >= 0);
+
+ tmp = nlmsg_reserve(n, len, pad);
+ if (tmp == NULL)
+ return -ENOMEM;
+
+ memcpy(tmp, data, len);
+ return 0;
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_parse(struct nlmsghdr * nlh,
+ int hdrlen,
+ struct nlattr * tb[],
+ int maxtype,
+ const struct nla_policy *policy)
+{
+ if (!nlmsg_valid_hdr(nlh, hdrlen))
+ return -NME_NL_MSG_TOOSHORT;
+
+ return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy);
+}
+
+struct nlmsghdr *
+nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags)
+{
+ struct nlmsghdr *nlh;
+
+ if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
+ g_return_val_if_reached(NULL);
+
+ nlh = (struct nlmsghdr *) n->nm_nlh;
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_flags = flags;
+ nlh->nlmsg_pid = pid;
+ nlh->nlmsg_seq = seq;
+
+ if (payload > 0 && nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
+ return NULL;
+
+ return nlh;
+}
+
+size_t
+nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize)
+{
+ const char *src;
+ size_t srclen;
+ size_t len;
+
+ /* - Always writes @dstsize bytes to @dst
+ * - Copies the first non-NUL characters to @dst.
+ * Any characters after the first NUL bytes in @nla are ignored.
+ * - If the string @nla is longer than @dstsize, the string
+ * gets truncated. @dst will always be NUL terminated. */
+
+ if (G_UNLIKELY(dstsize <= 1)) {
+ if (dstsize == 1)
+ dst[0] = '\0';
+ if (nla && (srclen = nla_len(nla)) > 0)
+ return strnlen(nla_data(nla), srclen);
+ return 0;
+ }
+
+ nm_assert(dst);
+
+ if (nla) {
+ srclen = nla_len(nla);
+ if (srclen > 0) {
+ src = nla_data(nla);
+ srclen = strnlen(src, srclen);
+ if (srclen > 0) {
+ len = NM_MIN(dstsize - 1, srclen);
+ memcpy(dst, src, len);
+ memset(&dst[len], 0, dstsize - len);
+ return srclen;
+ }
+ }
+ }
+
+ memset(dst, 0, dstsize);
+ return 0;
+}
+
+size_t
+nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize)
+{
+ size_t len;
+ int srclen;
+
+ if (!nla)
+ return 0;
+
+ srclen = nla_len(nla);
+
+ if (srclen <= 0) {
+ nm_assert(srclen == 0);
+ return 0;
+ }
+
+ len = NM_MIN((size_t) srclen, dstsize);
+ if (len > 0) {
+ /* there is a crucial difference between nla_strlcpy() and nla_memcpy().
+ * The former always write @dstsize bytes (akin to strncpy()), here, we only
+ * write the bytes that we actually have (leaving the remainder undefined). */
+ memcpy(dst, nla_data(nla), len);
+ }
+
+ return srclen;
+}
+
+int
+nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data)
+{
+ struct nlattr *nla;
+
+ nla = nla_reserve(msg, attrtype, datalen);
+ if (!nla) {
+ if (datalen < 0)
+ g_return_val_if_reached(-NME_BUG);
+
+ return -ENOMEM;
+ }
+
+ if (datalen > 0)
+ memcpy(nla_data(nla), data, datalen);
+
+ return 0;
+}
+
+struct nlattr *
+nla_find(const struct nlattr *head, int len, int attrtype)
+{
+ const struct nlattr *nla;
+ int rem;
+
+ nla_for_each_attr (nla, head, len, rem) {
+ if (nla_type(nla) == attrtype)
+ return (struct nlattr *) nla;
+ }
+
+ return NULL;
+}
+
+void
+nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr)
+{
+ ssize_t len;
+
+ len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
+ if (len < 0)
+ g_return_if_reached();
+ else if (len > 0) {
+ msg->nm_nlh->nlmsg_len -= len;
+ memset(nlmsg_tail(msg->nm_nlh), 0, len);
+ }
+}
+
+struct nlattr *
+nla_nest_start(struct nl_msg *msg, int attrtype)
+{
+ struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+
+ if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0)
+ return NULL;
+
+ return start;
+}
+
+static int
+_nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty)
+{
+ size_t pad, len;
+
+ len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
+
+ if (len > USHRT_MAX || (!keep_empty && len == NLA_HDRLEN)) {
+ /*
+ * Max nlattr size exceeded or empty nested attribute, trim the
+ * attribute header again
+ */
+ nla_nest_cancel(msg, start);
+
+ /* Return error only if nlattr size was exceeded */
+ return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE;
+ }
+
+ start->nla_len = len;
+
+ pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+ if (pad > 0) {
+ /*
+ * Data inside attribute does not end at a alignment boundary.
+ * Pad accordingly and account for the additional space in
+ * the message. nlmsg_reserve() may never fail in this situation,
+ * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
+ */
+ if (!nlmsg_reserve(msg, pad, 0))
+ g_return_val_if_reached(-NME_BUG);
+ }
+
+ return 0;
+}
+
+int
+nla_nest_end(struct nl_msg *msg, struct nlattr *start)
+{
+ return _nest_end(msg, start, 0);
+}
+
+static const uint16_t nla_attr_minlen[NLA_TYPE_MAX + 1] = {
+ [NLA_U8] = sizeof(uint8_t),
+ [NLA_U16] = sizeof(uint16_t),
+ [NLA_U32] = sizeof(uint32_t),
+ [NLA_U64] = sizeof(uint64_t),
+ [NLA_STRING] = 1,
+ [NLA_FLAG] = 0,
+};
+
+static int
+validate_nla(const struct nlattr *nla, int maxtype, const struct nla_policy *policy)
+{
+ const struct nla_policy *pt;
+ unsigned int minlen = 0;
+ int type = nla_type(nla);
+
+ if (type < 0 || type > maxtype)
+ return 0;
+
+ pt = &policy[type];
+
+ if (pt->type > NLA_TYPE_MAX)
+ g_return_val_if_reached(-NME_BUG);
+
+ if (pt->minlen)
+ minlen = pt->minlen;
+ else if (pt->type != NLA_UNSPEC)
+ minlen = nla_attr_minlen[pt->type];
+
+ if (nla_len(nla) < minlen)
+ return -NME_UNSPEC;
+
+ if (pt->maxlen && nla_len(nla) > pt->maxlen)
+ return -NME_UNSPEC;
+
+ if (pt->type == NLA_STRING) {
+ const char *data;
+
+ nm_assert(minlen > 0);
+
+ data = nla_data(nla);
+ if (data[nla_len(nla) - 1] != '\0')
+ return -NME_UNSPEC;
+ }
+
+ return 0;
+}
+
+int
+nla_parse(struct nlattr * tb[],
+ int maxtype,
+ struct nlattr * head,
+ int len,
+ const struct nla_policy *policy)
+{
+ struct nlattr *nla;
+ int rem, nmerr;
+
+ memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
+
+ nla_for_each_attr (nla, head, len, rem) {
+ int type = nla_type(nla);
+
+ if (type > maxtype)
+ continue;
+
+ if (policy) {
+ nmerr = validate_nla(nla, maxtype, policy);
+ if (nmerr < 0)
+ return nmerr;
+ }
+
+ tb[type] = nla;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_get_proto(struct nl_msg *msg)
+{
+ return msg->nm_protocol;
+}
+
+void
+nlmsg_set_proto(struct nl_msg *msg, int protocol)
+{
+ msg->nm_protocol = protocol;
+}
+
+void
+nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr)
+{
+ memcpy(&msg->nm_src, addr, sizeof(*addr));
+}
+
+struct ucred *
+nlmsg_get_creds(struct nl_msg *msg)
+{
+ if (msg->nm_creds_has)
+ return &msg->nm_creds;
+ return NULL;
+}
+
+void
+nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds)
+{
+ if (creds) {
+ memcpy(&msg->nm_creds, creds, sizeof(*creds));
+ msg->nm_creds_has = TRUE;
+ } else
+ msg->nm_creds_has = FALSE;
+}
+
+/*****************************************************************************/
+
+void *
+genlmsg_put(struct nl_msg *msg,
+ uint32_t port,
+ uint32_t seq,
+ int family,
+ int hdrlen,
+ int flags,
+ uint8_t cmd,
+ uint8_t version)
+{
+ struct nlmsghdr * nlh;
+ struct genlmsghdr hdr = {
+ .cmd = cmd,
+ .version = version,
+ };
+
+ nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
+ if (nlh == NULL)
+ return NULL;
+
+ memcpy(nlmsg_data(nlh), &hdr, sizeof(hdr));
+
+ return (char *) nlmsg_data(nlh) + GENL_HDRLEN;
+}
+
+void *
+genlmsg_data(const struct genlmsghdr *gnlh)
+{
+ return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+void *
+genlmsg_user_hdr(const struct genlmsghdr *gnlh)
+{
+ return genlmsg_data(gnlh);
+}
+
+struct genlmsghdr *
+genlmsg_hdr(struct nlmsghdr *nlh)
+{
+ return nlmsg_data(nlh);
+}
+
+void *
+genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen)
+{
+ return (char *) genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
+}
+
+struct nlattr *
+genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen)
+{
+ return genlmsg_user_data(gnlh, hdrlen);
+}
+
+int
+genlmsg_len(const struct genlmsghdr *gnlh)
+{
+ const struct nlmsghdr *nlh;
+
+ nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
+ return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
+}
+
+int
+genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen)
+{
+ return genlmsg_len(gnlh) - NLMSG_ALIGN(hdrlen);
+}
+
+int
+genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
+{
+ struct genlmsghdr *ghdr;
+
+ if (!nlmsg_valid_hdr(nlh, GENL_HDRLEN))
+ return 0;
+
+ ghdr = nlmsg_data(nlh);
+ if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
+ return 0;
+
+ return 1;
+}
+
+int
+genlmsg_parse(struct nlmsghdr * nlh,
+ int hdrlen,
+ struct nlattr * tb[],
+ int maxtype,
+ const struct nla_policy *policy)
+{
+ struct genlmsghdr *ghdr;
+
+ if (!genlmsg_valid_hdr(nlh, hdrlen))
+ return -NME_NL_MSG_TOOSHORT;
+
+ ghdr = nlmsg_data(nlh);
+ return nla_parse(tb,
+ maxtype,
+ genlmsg_attrdata(ghdr, hdrlen),
+ genlmsg_attrlen(ghdr, hdrlen),
+ policy);
+}
+
+static int
+_genl_parse_getfamily(struct nl_msg *msg, void *arg)
+{
+ static const struct nla_policy ctrl_policy[] = {
+ [CTRL_ATTR_FAMILY_ID] = {.type = NLA_U16},
+ [CTRL_ATTR_FAMILY_NAME] = {.type = NLA_STRING, .maxlen = GENL_NAMSIZ},
+ [CTRL_ATTR_VERSION] = {.type = NLA_U32},
+ [CTRL_ATTR_HDRSIZE] = {.type = NLA_U32},
+ [CTRL_ATTR_MAXATTR] = {.type = NLA_U32},
+ [CTRL_ATTR_OPS] = {.type = NLA_NESTED},
+ [CTRL_ATTR_MCAST_GROUPS] = {.type = NLA_NESTED},
+ };
+ struct nlattr * tb[G_N_ELEMENTS(ctrl_policy)];
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ gint32 * response_data = arg;
+
+ if (genlmsg_parse_arr(nlh, 0, tb, ctrl_policy) < 0)
+ return NL_SKIP;
+
+ if (tb[CTRL_ATTR_FAMILY_ID])
+ *response_data = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+
+ return NL_STOP;
+}
+
+int
+genl_ctrl_resolve(struct nl_sock *sk, const char *name)
+{
+ nm_auto_nlmsg struct nl_msg *msg = NULL;
+ int nmerr;
+ gint32 response_data = -1;
+ const struct nl_cb cb = {
+ .valid_cb = _genl_parse_getfamily,
+ .valid_arg = &response_data,
+ };
+
+ msg = nlmsg_alloc();
+
+ if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY, 1))
+ return -ENOMEM;
+
+ nmerr = nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, name);
+ if (nmerr < 0)
+ return nmerr;
+
+ nmerr = nl_send_auto(sk, msg);
+ if (nmerr < 0)
+ return nmerr;
+
+ nmerr = nl_recvmsgs(sk, &cb);
+ if (nmerr < 0)
+ return nmerr;
+
+ /* If search was successful, request may be ACKed after data */
+ nmerr = nl_wait_for_ack(sk, NULL);
+ if (nmerr < 0)
+ return nmerr;
+
+ if (response_data < 0)
+ return -NME_UNSPEC;
+
+ return response_data;
+}
+
+/*****************************************************************************/
+
+struct nl_sock *
+nl_socket_alloc(void)
+{
+ struct nl_sock *sk;
+
+ sk = g_slice_new0(struct nl_sock);
+
+ sk->s_fd = -1;
+ sk->s_local.nl_family = AF_NETLINK;
+ sk->s_peer.nl_family = AF_NETLINK;
+ sk->s_seq_expect = sk->s_seq_next = time(NULL);
+
+ return sk;
+}
+
+void
+nl_socket_free(struct nl_sock *sk)
+{
+ if (!sk)
+ return;
+
+ if (sk->s_fd >= 0)
+ nm_close(sk->s_fd);
+ g_slice_free(struct nl_sock, sk);
+}
+
+int
+nl_socket_get_fd(const struct nl_sock *sk)
+{
+ return sk->s_fd;
+}
+
+uint32_t
+nl_socket_get_local_port(const struct nl_sock *sk)
+{
+ return sk->s_local.nl_pid;
+}
+
+size_t
+nl_socket_get_msg_buf_size(struct nl_sock *sk)
+{
+ return sk->s_bufsize;
+}
+
+int
+nl_socket_set_passcred(struct nl_sock *sk, int state)
+{
+ int err;
+
+ if (sk->s_fd == -1)
+ return -NME_NL_BAD_SOCK;
+
+ err = setsockopt(sk->s_fd, SOL_SOCKET, SO_PASSCRED, &state, sizeof(state));
+ if (err < 0)
+ return -nm_errno_from_native(errno);
+
+ if (state)
+ sk->s_flags |= NL_SOCK_PASSCRED;
+ else
+ sk->s_flags &= ~NL_SOCK_PASSCRED;
+
+ return 0;
+}
+
+int
+nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize)
+{
+ sk->s_bufsize = bufsize;
+
+ return 0;
+}
+
+struct sockaddr_nl *
+nlmsg_get_dst(struct nl_msg *msg)
+{
+ return &msg->nm_dst;
+}
+
+int
+nl_socket_set_nonblocking(const struct nl_sock *sk)
+{
+ if (sk->s_fd == -1)
+ return -NME_NL_BAD_SOCK;
+
+ if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
+ return -nm_errno_from_native(errno);
+
+ return 0;
+}
+
+int
+nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf)
+{
+ int err;
+
+ if (rxbuf <= 0)
+ rxbuf = 32768;
+
+ if (txbuf <= 0)
+ txbuf = 32768;
+
+ if (sk->s_fd == -1)
+ return -NME_NL_BAD_SOCK;
+
+ err = setsockopt(sk->s_fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof(txbuf));
+ if (err < 0) {
+ return -nm_errno_from_native(errno);
+ }
+
+ err = setsockopt(sk->s_fd, SOL_SOCKET, SO_RCVBUF, &rxbuf, sizeof(rxbuf));
+ if (err < 0) {
+ return -nm_errno_from_native(errno);
+ }
+
+ return 0;
+}
+
+int
+nl_socket_add_memberships(struct nl_sock *sk, int group, ...)
+{
+ int err;
+ va_list ap;
+
+ if (sk->s_fd == -1)
+ return -NME_NL_BAD_SOCK;
+
+ va_start(ap, group);
+
+ while (group != 0) {
+ if (group < 0) {
+ va_end(ap);
+ g_return_val_if_reached(-NME_BUG);
+ }
+
+ err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
+ if (err < 0) {
+ int errsv = errno;
+
+ va_end(ap);
+ return -nm_errno_from_native(errsv);
+ }
+
+ group = va_arg(ap, int);
+ }
+
+ va_end(ap);
+
+ return 0;
+}
+
+int
+nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable)
+{
+ int err, val;
+
+ if (sk->s_fd == -1)
+ return -NME_NL_BAD_SOCK;
+
+ val = !!enable;
+ err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof(val));
+ if (err < 0)
+ return -nm_errno_from_native(errno);
+
+ return 0;
+}
+
+void
+nl_socket_disable_msg_peek(struct nl_sock *sk)
+{
+ sk->s_flags |= NL_MSG_PEEK_EXPLICIT;
+ sk->s_flags &= ~NL_MSG_PEEK;
+}
+
+int
+nl_connect(struct nl_sock *sk, int protocol)
+{
+ int err, nmerr;
+ socklen_t addrlen;
+ struct sockaddr_nl local = {0};
+
+ if (sk->s_fd != -1)
+ return -NME_NL_BAD_SOCK;
+
+ sk->s_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+ if (sk->s_fd < 0) {
+ nmerr = -nm_errno_from_native(errno);
+ goto errout;
+ }
+
+ nmerr = nl_socket_set_buffer_size(sk, 0, 0);
+ if (nmerr < 0)
+ goto errout;
+
+ nm_assert(sk->s_local.nl_pid == 0);
+
+ err = bind(sk->s_fd, (struct sockaddr *) &sk->s_local, sizeof(sk->s_local));
+ if (err != 0) {
+ nmerr = -nm_errno_from_native(errno);
+ goto errout;
+ }
+
+ addrlen = sizeof(local);
+ err = getsockname(sk->s_fd, (struct sockaddr *) &local, &addrlen);
+ if (err < 0) {
+ nmerr = -nm_errno_from_native(errno);
+ goto errout;
+ }
+
+ if (addrlen != sizeof(local)) {
+ nmerr = -NME_UNSPEC;
+ goto errout;
+ }
+
+ if (local.nl_family != AF_NETLINK) {
+ nmerr = -NME_UNSPEC;
+ goto errout;
+ }
+
+ sk->s_local = local;
+ sk->s_proto = protocol;
+
+ return 0;
+
+errout:
+ if (sk->s_fd != -1) {
+ close(sk->s_fd);
+ sk->s_fd = -1;
+ }
+ return nmerr;
+}
+
+/*****************************************************************************/
+
+static void
+_cb_init(struct nl_cb *dst, const struct nl_cb *src)
+{
+ nm_assert(dst);
+
+ if (src)
+ *dst = *src;
+ else
+ memset(dst, 0, sizeof(*dst));
+}
+
+static int
+ack_wait_handler(struct nl_msg *msg, void *arg)
+{
+ return NL_STOP;
+}
+
+int
+nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb)
+{
+ struct nl_cb cb2;
+
+ _cb_init(&cb2, cb);
+ cb2.ack_cb = ack_wait_handler;
+ return nl_recvmsgs(sk, &cb2);
+}
+
+#define NL_CB_CALL(cb, type, msg) \
+ do { \
+ const struct nl_cb *_cb = (cb); \
+ \
+ if (_cb && _cb->type##_cb) { \
+ /* the returned value here must be either a negative
+ * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
+ nmerr = _cb->type##_cb((msg), _cb->type##_arg); \
+ switch (nmerr) { \
+ case NL_OK: \
+ nm_assert(nmerr == 0); \
+ break; \
+ case NL_SKIP: \
+ goto skip; \
+ case NL_STOP: \
+ goto stop; \
+ default: \
+ if (nmerr >= 0) { \
+ nm_assert_not_reached(); \
+ nmerr = -NME_BUG; \
+ } \
+ goto out; \
+ } \
+ } \
+ } while (0)
+
+int
+nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb)
+{
+ int n, nmerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
+ gs_free unsigned char *buf = NULL;
+ struct nlmsghdr * hdr;
+ struct sockaddr_nl nla = {0};
+ struct ucred creds;
+ gboolean creds_has;
+
+continue_reading:
+ n = nl_recv(sk, &nla, &buf, &creds, &creds_has);
+ if (n <= 0)
+ return n;
+
+ hdr = (struct nlmsghdr *) buf;
+ while (nlmsg_ok(hdr, n)) {
+ nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+ msg = nlmsg_alloc_convert(hdr);
+
+ nlmsg_set_proto(msg, sk->s_proto);
+ nlmsg_set_src(msg, &nla);
+ nlmsg_set_creds(msg, creds_has ? &creds : NULL);
+
+ nrecv++;
+
+ /* Only do sequence checking if auto-ack mode is enabled */
+ if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+ if (hdr->nlmsg_seq != sk->s_seq_expect) {
+ nmerr = -NME_NL_SEQ_MISMATCH;
+ goto out;
+ }
+ }
+
+ if (hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR
+ || hdr->nlmsg_type == NLMSG_NOOP || hdr->nlmsg_type == NLMSG_OVERRUN) {
+ /* We can't check for !NLM_F_MULTI since some netlink
+ * users in the kernel are broken. */
+ sk->s_seq_expect++;
+ }
+
+ if (hdr->nlmsg_flags & NLM_F_MULTI)
+ multipart = 1;
+
+ if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+ /*
+ * We have to continue reading to clear
+ * all messages until a NLMSG_DONE is
+ * received and report the inconsistency.
+ */
+ interrupted = 1;
+ }
+
+ /* messages terminates a multipart message, this is
+ * usually the end of a message and therefore we slip
+ * out of the loop by default. the user may overrule
+ * this action by skipping this packet. */
+ if (hdr->nlmsg_type == NLMSG_DONE) {
+ multipart = 0;
+ NL_CB_CALL(cb, finish, msg);
+ }
+
+ /* Message to be ignored, the default action is to
+ * skip this message if no callback is specified. The
+ * user may overrule this action by returning
+ * NL_PROCEED. */
+ else if (hdr->nlmsg_type == NLMSG_NOOP)
+ goto skip;
+
+ /* Data got lost, report back to user. The default action is to
+ * quit parsing. The user may overrule this action by returning
+ * NL_SKIP or NL_PROCEED (dangerous) */
+ else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+ nmerr = -NME_NL_MSG_OVERFLOW;
+ goto out;
+ }
+
+ /* Message carries a nlmsgerr */
+ else if (hdr->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *e = nlmsg_data(hdr);
+
+ if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
+ /* Truncated error message, the default action
+ * is to stop parsing. The user may overrule
+ * this action by returning NL_SKIP or
+ * NL_PROCEED (dangerous) */
+ nmerr = -NME_NL_MSG_TRUNC;
+ goto out;
+ }
+ if (e->error) {
+ /* Error message reported back from kernel. */
+ if (cb && cb->err_cb) {
+ /* the returned value here must be either a negative
+ * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */
+ nmerr = cb->err_cb(&nla, e, cb->err_arg);
+ if (nmerr < 0)
+ goto out;
+ else if (nmerr == NL_SKIP)
+ goto skip;
+ else if (nmerr == NL_STOP) {
+ nmerr = -nm_errno_from_native(e->error);
+ goto out;
+ }
+ nm_assert(nmerr == NL_OK);
+ } else {
+ nmerr = -nm_errno_from_native(e->error);
+ goto out;
+ }
+ } else
+ NL_CB_CALL(cb, ack, msg);
+ } else {
+ /* Valid message (not checking for MULTIPART bit to
+ * get along with broken kernels. NL_SKIP has no
+ * effect on this. */
+ NL_CB_CALL(cb, valid, msg);
+ }
+skip:
+ nmerr = 0;
+ hdr = nlmsg_next(hdr, &n);
+ }
+
+ if (multipart) {
+ /* Multipart message not yet complete, continue reading */
+ nm_clear_g_free(&buf);
+
+ nmerr = 0;
+ goto continue_reading;
+ }
+
+stop:
+ nmerr = 0;
+
+out:
+ if (interrupted)
+ nmerr = -NME_NL_DUMP_INTR;
+
+ nm_assert(nmerr <= 0);
+ return nmerr ?: nrecv;
+}
+
+int
+nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
+{
+ int ret;
+
+ if (sk->s_fd < 0)
+ return -NME_NL_BAD_SOCK;
+
+ nlmsg_set_src(msg, &sk->s_local);
+
+ ret = sendmsg(sk->s_fd, hdr, 0);
+ if (ret < 0)
+ return -nm_errno_from_native(errno);
+
+ return ret;
+}
+
+int
+nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
+{
+ struct sockaddr_nl *dst;
+ struct ucred * creds;
+ struct msghdr hdr = {
+ .msg_name = (void *) &sk->s_peer,
+ .msg_namelen = sizeof(struct sockaddr_nl),
+ .msg_iov = iov,
+ .msg_iovlen = iovlen,
+ };
+ char buf[CMSG_SPACE(sizeof(struct ucred))];
+
+ /* Overwrite destination if specified in the message itself, defaults
+ * to the peer address of the socket.
+ */
+ dst = nlmsg_get_dst(msg);
+ if (dst->nl_family == AF_NETLINK)
+ hdr.msg_name = dst;
+
+ /* Add credentials if present. */
+ creds = nlmsg_get_creds(msg);
+ if (creds != NULL) {
+ struct cmsghdr *cmsg;
+
+ hdr.msg_control = buf;
+ hdr.msg_controllen = sizeof(buf);
+
+ cmsg = CMSG_FIRSTHDR(&hdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ memcpy(CMSG_DATA(cmsg), creds, sizeof(struct ucred));
+ }
+
+ return nl_sendmsg(sk, msg, &hdr);
+}
+
+void
+nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
+{
+ struct nlmsghdr *nlh;
+
+ nlh = nlmsg_hdr(msg);
+ if (nlh->nlmsg_pid == NL_AUTO_PORT)
+ nlh->nlmsg_pid = nl_socket_get_local_port(sk);
+
+ if (nlh->nlmsg_seq == NL_AUTO_SEQ)
+ nlh->nlmsg_seq = sk->s_seq_next++;
+
+ if (msg->nm_protocol == -1)
+ msg->nm_protocol = sk->s_proto;
+
+ nlh->nlmsg_flags |= NLM_F_REQUEST;
+
+ if (!(sk->s_flags & NL_NO_AUTO_ACK))
+ nlh->nlmsg_flags |= NLM_F_ACK;
+}
+
+int
+nl_send(struct nl_sock *sk, struct nl_msg *msg)
+{
+ struct iovec iov = {
+ .iov_base = (void *) nlmsg_hdr(msg),
+ .iov_len = nlmsg_hdr(msg)->nlmsg_len,
+ };
+
+ return nl_send_iovec(sk, msg, &iov, 1);
+}
+
+int
+nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
+{
+ nl_complete_msg(sk, msg);
+
+ return nl_send(sk, msg);
+}
+
+int
+nl_recv(struct nl_sock * sk,
+ struct sockaddr_nl *nla,
+ unsigned char ** buf,
+ struct ucred * out_creds,
+ gboolean * out_creds_has)
+{
+ ssize_t n;
+ int flags = 0;
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_name = (void *) nla,
+ .msg_namelen = sizeof(struct sockaddr_nl),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ struct ucred tmpcreds;
+ gboolean tmpcreds_has = FALSE;
+ int retval;
+ int errsv;
+
+ nm_assert(nla);
+ nm_assert(buf && !*buf);
+ nm_assert(!out_creds_has == !out_creds);
+
+ if ((sk->s_flags & NL_MSG_PEEK)
+ || (!(sk->s_flags & NL_MSG_PEEK_EXPLICIT) && sk->s_bufsize == 0))
+ flags |= MSG_PEEK | MSG_TRUNC;
+
+ iov.iov_len = sk->s_bufsize ?: (((size_t) nm_utils_getpagesize()) * 4u);
+ iov.iov_base = g_malloc(iov.iov_len);
+
+ if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+ msg.msg_controllen = 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) {
+ errsv = errno;
+ if (errsv == EINTR)
+ goto retry;
+ retval = -nm_errno_from_native(errsv);
+ goto abort;
+ }
+
+ if (msg.msg_flags & MSG_CTRUNC) {
+ if (msg.msg_controllen == 0) {
+ retval = -NME_NL_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 = -NME_NL_MSG_TRUNC;
+ goto abort;
+ }
+
+ /* Provided buffer is not long enough, enlarge it
+ * to size of n (which should be total length of the message)
+ * and try again. */
+ iov.iov_base = g_realloc(iov.iov_base, n);
+ iov.iov_len = n;
+ flags = 0;
+ goto retry;
+ }
+
+ if (flags != 0) {
+ /* Buffer is big enough, do the actual reading */
+ flags = 0;
+ goto retry;
+ }
+
+ if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
+ retval = -NME_UNSPEC;
+ goto abort;
+ }
+
+ if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET)
+ continue;
+ if (cmsg->cmsg_type != SCM_CREDENTIALS)
+ continue;
+ memcpy(&tmpcreds, CMSG_DATA(cmsg), sizeof(tmpcreds));
+ tmpcreds_has = TRUE;
+ break;
+ }
+ }
+
+ retval = n;
+
+abort:
+ g_free(msg.msg_control);
+
+ if (retval <= 0) {
+ g_free(iov.iov_base);
+ return retval;
+ }
+
+ *buf = iov.iov_base;
+ if (out_creds && tmpcreds_has)
+ *out_creds = tmpcreds;
+ NM_SET_OUT(out_creds_has, tmpcreds_has);
+ return retval;
+}