summary refs log tree commit diff
path: root/src/libnm-platform/nm-netlink.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-platform/nm-netlink.c')
-rw-r--r--src/libnm-platform/nm-netlink.c654
1 files changed, 371 insertions, 283 deletions
diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c
index 697ae591..36841d56 100644
--- a/src/libnm-platform/nm-netlink.c
+++ b/src/libnm-platform/nm-netlink.c
@@ -18,10 +18,15 @@
 
 /*****************************************************************************/
 
-#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)
+#define nm_assert_sk(sk)                  \
+    G_STMT_START                          \
+    {                                     \
+        const struct nl_sock *_sk = (sk); \
+                                          \
+        nm_assert(_sk);                   \
+        nm_assert(_sk->s_fd >= 0);        \
+    }                                     \
+    G_STMT_END
 
 #ifndef NETLINK_EXT_ACK
 #define NETLINK_EXT_ACK 11
@@ -33,19 +38,20 @@ struct nl_msg {
     struct sockaddr_nl nm_dst;
     struct ucred       nm_creds;
     struct nlmsghdr   *nm_nlh;
-    size_t             nm_size;
+    uint32_t           nm_size;
     bool               nm_creds_has : 1;
 };
 
 struct nl_sock {
     struct sockaddr_nl s_local;
     struct sockaddr_nl s_peer;
+    size_t             s_bufsize;
     int                s_fd;
     int                s_proto;
     unsigned int       s_seq_next;
     unsigned int       s_seq_expect;
-    int                s_flags;
-    size_t             s_bufsize;
+    bool               s_msg_peek : 1;
+    bool               s_auto_ack : 1;
 };
 
 /*****************************************************************************/
@@ -71,13 +77,62 @@ NM_UTILS_FLAGS2STR_DEFINE(nl_nlmsg_flags2str,
                           NM_UTILS_FLAGS2STR(NLM_F_CREATE, "CREATE"),
                           NM_UTILS_FLAGS2STR(NLM_F_APPEND, "APPEND"), );
 
+static NM_UTILS_LOOKUP_STR_DEFINE(_rtnl_type_to_str,
+                                  guint16,
+                                  NM_UTILS_LOOKUP_DEFAULT(NULL),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETLINK, "RTM_GETLINK"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWLINK, "RTM_NEWLINK"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELLINK, "RTM_DELLINK"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_SETLINK, "RTM_SETLINK"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETADDR, "RTM_GETADDR"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWADDR, "RTM_NEWADDR"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELADDR, "RTM_DELADDR"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETROUTE, "RTM_GETROUTE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWROUTE, "RTM_NEWROUTE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELROUTE, "RTM_DELROUTE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETRULE, "RTM_GETRULE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWRULE, "RTM_NEWRULE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELRULE, "RTM_DELRULE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETQDISC, "RTM_GETQDISC"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWQDISC, "RTM_NEWQDISC"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELQDISC, "RTM_DELQDISC"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_GETTFILTER, "RTM_GETTFILTER"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWTFILTER, "RTM_NEWTFILTER"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(RTM_DELTFILTER, "RTM_DELTFILTER"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NLMSG_NOOP, "NLMSG_NOOP"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NLMSG_ERROR, "NLMSG_ERROR"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NLMSG_DONE, "NLMSG_DONE"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NLMSG_OVERRUN, "NLMSG_OVERRUN"), );
+
+static NM_UTILS_LOOKUP_STR_DEFINE(
+    _genl_ctrl_cmd_to_str,
+    guint8,
+    NM_UTILS_LOOKUP_DEFAULT(NULL),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_UNSPEC, "CTRL_CMD_UNSPEC"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_NEWFAMILY, "CTRL_CMD_NEWFAMILY"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_DELFAMILY, "CTRL_CMD_DELFAMILY"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_GETFAMILY, "CTRL_CMD_GETFAMILY"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_NEWOPS, "CTRL_CMD_NEWOPS"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_DELOPS, "CTRL_CMD_DELOPS"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_GETOPS, "CTRL_CMD_GETOPS"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_NEWMCAST_GRP, "CTRL_CMD_NEWMCAST_GRP"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_DELMCAST_GRP, "CTRL_CMD_DELMCAST_GRP"),
+    NM_UTILS_LOOKUP_STR_ITEM(CTRL_CMD_GETMCAST_GRP, "CTRL_CMD_GETMCAST_GRP"),
+    /* CTRL_CMD_GETPOLICY was added in Linux 5.7 (released on 31 May, 2020),
+     * commit d07dcf9aadd6 ('netlink: add infrastructure to expose policies to userspace') */
+    NM_UTILS_LOOKUP_STR_ITEM(10 /* CTRL_CMD_GETPOLICY */, "CTRL_CMD_GETPOLICY"), );
+
 /*****************************************************************************/
 
 const char *
-nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
+nl_nlmsghdr_to_str(int                    netlink_protocol,
+                   guint32                pktinfo_group,
+                   const struct nlmsghdr *hdr,
+                   char                  *buf,
+                   gsize                  len)
 {
     const char *b;
-    const char *s;
+    const char *s = NULL;
     guint       flags, flags_before;
     const char *prefix;
 
@@ -86,86 +141,46 @@ nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
 
     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";
+    switch (netlink_protocol) {
+    case NETLINK_ROUTE:
+        s = _rtnl_type_to_str(hdr->nlmsg_type);
+        if (s)
+            nm_strbuf_append_str(&buf, &len, s);
+        else
+            nm_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
         break;
     default:
+        nm_assert_not_reached();
+        /* fall-through */
+    case NETLINK_GENERIC:
+        if (pktinfo_group == 0)
+            nm_strbuf_append(&buf, &len, "group:unicast");
+        else
+            nm_strbuf_append(&buf, &len, "group:multicast(%u)", (unsigned) pktinfo_group);
+
         s = NULL;
+        if (hdr->nlmsg_type == GENL_ID_CTRL)
+            s = "GENL_ID_CTRL";
+        if (s)
+            nm_strbuf_append(&buf, &len, ", msg-type:%s", s);
+        else
+            nm_strbuf_append(&buf, &len, ", msg-type:(%u)", (unsigned) hdr->nlmsg_type);
+
+        if (genlmsg_valid_hdr(hdr, 0)) {
+            const struct genlmsghdr *ghdr;
+
+            ghdr = nlmsg_data(hdr);
+            s    = NULL;
+            if (hdr->nlmsg_type == GENL_ID_CTRL)
+                s = _genl_ctrl_cmd_to_str(ghdr->cmd);
+            if (s)
+                nm_strbuf_append(&buf, &len, ", cmd:%s", s);
+            else
+                nm_strbuf_append(&buf, &len, ", cmd:(%u)", (unsigned) ghdr->cmd);
+        }
         break;
     }
 
-    if (s)
-        nm_strbuf_append_str(&buf, &len, s);
-    else
-        nm_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
-
     flags = hdr->nlmsg_flags;
 
     if (!flags) {
@@ -198,27 +213,30 @@ nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
     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;
+    switch (netlink_protocol) {
+    case NETLINK_ROUTE:
+        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
@@ -237,29 +255,32 @@ flags_done:
 /*****************************************************************************/
 
 struct nlmsghdr *
-nlmsg_hdr(struct nl_msg *n)
+nlmsg_hdr(const struct nl_msg *n)
 {
     return n->nm_nlh;
 }
 
 void *
-nlmsg_reserve(struct nl_msg *n, size_t len, int pad)
+nlmsg_reserve(struct nl_msg *n, uint32_t len, uint32_t pad)
 {
-    char  *buf       = (char *) n->nm_nlh;
-    size_t nlmsg_len = n->nm_nlh->nlmsg_len;
-    size_t tlen;
-
-    nm_assert(pad >= 0);
+    char    *buf = (char *) n->nm_nlh;
+    uint32_t tlen;
 
-    if (len > n->nm_size)
-        return NULL;
+    nm_assert(n);
+    nm_assert(pad == 0 || nm_utils_is_power_of_two(pad));
+    nm_assert(n->nm_nlh->nlmsg_len <= n->nm_size);
 
-    tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
+    if (pad != 0) {
+        tlen = (len + (pad - 1u)) & ~(pad - 1u);
+        if (tlen < len)
+            return NULL;
+    } else
+        tlen = len;
 
-    if ((tlen + nlmsg_len) > n->nm_size)
+    if (tlen > n->nm_size - n->nm_nlh->nlmsg_len)
         return NULL;
 
-    buf += nlmsg_len;
+    buf += n->nm_nlh->nlmsg_len;
     n->nm_nlh->nlmsg_len += tlen;
 
     if (tlen > len)
@@ -270,6 +291,52 @@ nlmsg_reserve(struct nl_msg *n, size_t len, int pad)
 
 /*****************************************************************************/
 
+int
+nlmsg_parse_error(const struct nlmsghdr *nlh, const char **out_extack_msg)
+{
+    const struct nlmsgerr *e;
+
+    nm_assert(nlh);
+
+    NM_SET_OUT(out_extack_msg, NULL);
+
+    if (nlh->nlmsg_type != NLMSG_ERROR)
+        return -NME_NL_MSG_INVAL;
+
+    if (nlh->nlmsg_len < nlmsg_size(sizeof(struct nlmsgerr))) {
+        /* Truncated error message, the default action
+         * is to stop parsing. The user may overrule
+         * this action by returning NL_SKIP or
+         * NL_PROCEED (dangerous) */
+        return -NME_NL_MSG_TRUNC;
+    }
+
+    e = nlmsg_data(nlh);
+
+    if (!e->error)
+        return 0;
+
+    if (NM_FLAGS_HAS(nlh->nlmsg_flags, NLM_F_ACK_TLVS) && out_extack_msg
+        && nlh->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) {
+        static const struct nla_policy policy[] = {
+            [NLMSGERR_ATTR_MSG]  = {.type = NLA_STRING},
+            [NLMSGERR_ATTR_OFFS] = {.type = NLA_U32},
+        };
+        struct nlattr *tb[G_N_ELEMENTS(policy)];
+        struct nlattr *tlvs;
+
+        tlvs = (struct nlattr *) ((char *) e + sizeof(*e) + e->msg.nlmsg_len - NLMSG_HDRLEN);
+        if (nla_parse_arr(tb, tlvs, nlh->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len, policy) >= 0) {
+            if (tb[NLMSGERR_ATTR_MSG])
+                *out_extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]);
+        }
+    }
+
+    return -nm_errno_from_native(e->error);
+}
+
+/*****************************************************************************/
+
 struct nlattr *
 nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
 {
@@ -304,6 +371,8 @@ nlmsg_alloc_size(size_t len)
 
     if (len < sizeof(struct nlmsghdr))
         len = sizeof(struct nlmsghdr);
+    else if (len > UINT32_MAX)
+        g_return_val_if_reached(NULL);
 
     nm  = g_slice_new(struct nl_msg);
     *nm = (struct nl_msg){
@@ -341,7 +410,7 @@ nlmsg_alloc_convert(struct nlmsghdr *hdr)
 }
 
 struct nl_msg *
-nlmsg_alloc_simple(int nlmsgtype, int flags)
+nlmsg_alloc_simple(uint16_t nlmsgtype, uint16_t flags)
 {
     struct nl_msg *nm;
     struct nlmsghdr *new;
@@ -366,27 +435,27 @@ nlmsg_free(struct nl_msg *msg)
 /*****************************************************************************/
 
 int
-nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad)
+nlmsg_append(struct nl_msg *n, const void *data, uint32_t len, uint32_t pad)
 {
     void *tmp;
 
     nm_assert(n);
-    nm_assert(data);
-    nm_assert(len > 0);
-    nm_assert(pad >= 0);
+    nm_assert(len == 0 || data);
 
     tmp = nlmsg_reserve(n, len, pad);
-    if (tmp == NULL)
+    if (!tmp)
         return -ENOMEM;
 
-    memcpy(tmp, data, len);
+    if (len > 0)
+        memcpy(tmp, data, len);
+
     return 0;
 }
 
 /*****************************************************************************/
 
 int
-nlmsg_parse(struct nlmsghdr         *nlh,
+nlmsg_parse(const struct nlmsghdr   *nlh,
             int                      hdrlen,
             struct nlattr           *tb[],
             int                      maxtype,
@@ -399,14 +468,17 @@ nlmsg_parse(struct nlmsghdr         *nlh,
 }
 
 struct nlmsghdr *
-nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags)
+nlmsg_put(struct nl_msg *n,
+          uint32_t       pid,
+          uint32_t       seq,
+          uint16_t       type,
+          uint32_t       payload,
+          uint16_t       flags)
 {
-    struct nlmsghdr *nlh;
+    struct nlmsghdr *nlh = (struct nlmsghdr *) n->nm_nlh;
 
-    if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
-        g_return_val_if_reached(NULL);
+    nm_assert(nlh->nlmsg_len >= NLMSG_HDRLEN);
 
-    nlh              = (struct nlmsghdr *) n->nm_nlh;
     nlh->nlmsg_type  = type;
     nlh->nlmsg_flags = flags;
     nlh->nlmsg_pid   = pid;
@@ -547,7 +619,8 @@ nla_nest_start(struct nl_msg *msg, int attrtype)
 static int
 _nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty)
 {
-    size_t pad, len;
+    size_t   len;
+    uint32_t pad;
 
     len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
 
@@ -566,14 +639,18 @@ _nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty)
 
     pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
     if (pad > 0) {
+        void *p;
+
         /*
          * 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))
+        p = nlmsg_reserve(msg, pad, 0);
+        if (!p)
             g_return_val_if_reached(-NME_BUG);
+        memset(p, 0, pad);
     }
 
     return 0;
@@ -588,6 +665,7 @@ nla_nest_end(struct nl_msg *msg, struct nlattr *start)
 static const uint8_t nla_attr_minlen[NLA_TYPE_MAX + 1] = {
     [NLA_U8]     = sizeof(uint8_t),
     [NLA_U16]    = sizeof(uint16_t),
+    [NLA_S32]    = sizeof(int32_t),
     [NLA_U32]    = sizeof(uint32_t),
     [NLA_U64]    = sizeof(uint64_t),
     [NLA_STRING] = 1,
@@ -712,9 +790,9 @@ void *
 genlmsg_put(struct nl_msg *msg,
             uint32_t       port,
             uint32_t       seq,
-            int            family,
-            int            hdrlen,
-            int            flags,
+            uint16_t       family,
+            uint32_t       hdrlen,
+            uint16_t       flags,
             uint8_t        cmd,
             uint8_t        version)
 {
@@ -745,8 +823,8 @@ genlmsg_user_hdr(const struct genlmsghdr *gnlh)
     return genlmsg_data(gnlh);
 }
 
-struct genlmsghdr *
-genlmsg_hdr(struct nlmsghdr *nlh)
+const struct genlmsghdr *
+genlmsg_hdr(const struct nlmsghdr *nlh)
 {
     return nlmsg_data(nlh);
 }
@@ -779,7 +857,7 @@ genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen)
 }
 
 int
-genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
+genlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
 {
     struct genlmsghdr *ghdr;
 
@@ -794,13 +872,13 @@ genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
 }
 
 int
-genlmsg_parse(struct nlmsghdr         *nlh,
+genlmsg_parse(const struct nlmsghdr   *nlh,
               int                      hdrlen,
               struct nlattr           *tb[],
               int                      maxtype,
               const struct nla_policy *policy)
 {
-    struct genlmsghdr *ghdr;
+    const struct genlmsghdr *ghdr;
 
     if (!genlmsg_valid_hdr(nlh, hdrlen))
         return -NME_NL_MSG_TOOSHORT;
@@ -813,23 +891,24 @@ genlmsg_parse(struct nlmsghdr         *nlh,
                      policy);
 }
 
+const struct nla_policy genl_ctrl_policy[CTRL_ATTR_MCAST_GROUPS + 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},
+};
+
 static int
-_genl_parse_getfamily(struct nl_msg *msg, void *arg)
+_genl_parse_getfamily(const 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 nlattr   *tb[G_N_ELEMENTS(genl_ctrl_policy)];
     struct nlmsghdr *nlh           = nlmsg_hdr(msg);
     gint32          *response_data = arg;
 
-    if (genlmsg_parse_arr(nlh, 0, tb, ctrl_policy) < 0)
+    if (genlmsg_parse_arr(nlh, 0, tb, genl_ctrl_policy) < 0)
         return NL_SKIP;
 
     if (tb[CTRL_ATTR_FAMILY_ID])
@@ -879,30 +958,14 @@ genl_ctrl_resolve(struct nl_sock *sk, const char *name)
 
 /*****************************************************************************/
 
-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);
+    nm_close(sk->s_fd);
+    nm_g_slice_free(sk);
 }
 
 int
@@ -928,18 +991,24 @@ nl_socket_set_passcred(struct nl_sock *sk, int state)
 {
     int err;
 
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
+    nm_assert_sk(sk);
 
     err = setsockopt(sk->s_fd, SOL_SOCKET, SO_PASSCRED, &state, sizeof(state));
     if (err < 0)
         return -nm_errno_from_native(errno);
+    return 0;
+}
 
-    if (state)
-        sk->s_flags |= NL_SOCK_PASSCRED;
-    else
-        sk->s_flags &= ~NL_SOCK_PASSCRED;
+int
+nl_socket_set_pktinfo(struct nl_sock *sk, int state)
+{
+    int err;
 
+    nm_assert_sk(sk);
+
+    err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_PKTINFO, &state, sizeof(state));
+    if (err < 0)
+        return -nm_errno_from_native(errno);
     return 0;
 }
 
@@ -960,8 +1029,7 @@ nlmsg_get_dst(struct nl_msg *msg)
 int
 nl_socket_set_nonblocking(const struct nl_sock *sk)
 {
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
+    nm_assert_sk(sk);
 
     if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
         return -nm_errno_from_native(errno);
@@ -974,15 +1042,14 @@ nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf)
 {
     int err;
 
+    nm_assert_sk(sk);
+
     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);
@@ -1002,8 +1069,7 @@ 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;
+    nm_assert_sk(sk);
 
     va_start(ap, group);
 
@@ -1029,85 +1095,96 @@ nl_socket_add_memberships(struct nl_sock *sk, int group, ...)
     return 0;
 }
 
+/*****************************************************************************/
+
 int
-nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable)
+nl_socket_new(struct nl_sock **out_sk,
+              int              protocol,
+              NLSocketFlags    flags,
+              int              bufsize_rx,
+              int              bufsize_tx)
 {
-    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)
+    nm_auto_nlsock struct nl_sock *sk = NULL;
+    nm_auto_close int              fd = -1;
+    time_t                         t;
+    int                            err;
+    int                            nmerr;
+    socklen_t                      addrlen;
+    struct sockaddr_nl             local = {0};
+    int                            i_val;
+
+    nm_assert(out_sk && !*out_sk);
+
+    fd = socket(AF_NETLINK,
+                SOCK_RAW | SOCK_CLOEXEC
+                    | (NM_FLAGS_HAS(flags, NL_SOCKET_FLAGS_NONBLOCK) ? SOCK_NONBLOCK : 0),
+                protocol);
+    if (fd < 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;
-}
+    t = time(NULL);
+
+    sk  = g_slice_new(struct nl_sock);
+    *sk = (struct nl_sock){
+        .s_fd = nm_steal_fd(&fd),
+        .s_local =
+            {
+                .nl_pid    = 0,
+                .nl_family = AF_NETLINK,
+                .nl_groups = 0,
+            },
+        .s_peer =
+            {
+                .nl_pid    = 0,
+                .nl_family = AF_NETLINK,
+                .nl_groups = 0,
+            },
+        .s_seq_expect = t,
+        .s_seq_next   = t,
+        .s_bufsize    = 0,
+        .s_msg_peek   = !NM_FLAGS_HAS(flags, NL_SOCKET_FLAGS_DISABLE_MSG_PEEK),
+        .s_auto_ack   = TRUE,
+    };
 
-int
-nl_connect(struct nl_sock *sk, int protocol)
-{
-    int                err, nmerr;
-    socklen_t          addrlen;
-    struct sockaddr_nl local = {0};
+    nmerr = nl_socket_set_buffer_size(sk, bufsize_rx, bufsize_tx);
+    if (nmerr < 0)
+        return nmerr;
 
-    if (sk->s_fd != -1)
-        return -NME_NL_BAD_SOCK;
+    i_val = 1;
+    (void) setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &i_val, sizeof(i_val));
 
-    sk->s_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
-    if (sk->s_fd < 0) {
-        nmerr = -nm_errno_from_native(errno);
-        goto errout;
+    if (NM_FLAGS_HAS(flags, NL_SOCKET_FLAGS_PASSCRED)) {
+        err = nl_socket_set_passcred(sk, 1);
+        if (err < 0)
+            return err;
     }
 
-    nmerr = nl_socket_set_buffer_size(sk, 0, 0);
-    if (nmerr < 0)
-        goto errout;
-
-    nm_assert(sk->s_local.nl_pid == 0);
+    if (NM_FLAGS_HAS(flags, NL_SOCKET_FLAGS_PKTINFO)) {
+        err = nl_socket_set_pktinfo(sk, 1);
+        if (err < 0)
+            return err;
+    }
 
     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;
-    }
+    if (err != 0)
+        return -nm_errno_from_native(errno);
 
     addrlen = sizeof(local);
     err     = getsockname(sk->s_fd, (struct sockaddr *) &local, &addrlen);
-    if (err < 0) {
-        nmerr = -nm_errno_from_native(errno);
-        goto errout;
-    }
+    if (err < 0)
+        return -nm_errno_from_native(errno);
 
-    if (addrlen != sizeof(local)) {
-        nmerr = -NME_UNSPEC;
-        goto errout;
-    }
+    if (addrlen != sizeof(local))
+        return -NME_UNSPEC;
 
-    if (local.nl_family != AF_NETLINK) {
-        nmerr = -NME_UNSPEC;
-        goto errout;
-    }
+    if (local.nl_family != AF_NETLINK)
+        return -NME_UNSPEC;
 
     sk->s_local = local;
     sk->s_proto = protocol;
 
+    *out_sk = g_steal_pointer(&sk);
     return 0;
-
-errout:
-    if (sk->s_fd != -1) {
-        close(sk->s_fd);
-        sk->s_fd = -1;
-    }
-    return nmerr;
 }
 
 /*****************************************************************************/
@@ -1124,7 +1201,7 @@ _cb_init(struct nl_cb *dst, const struct nl_cb *src)
 }
 
 static int
-ack_wait_handler(struct nl_msg *msg, void *arg)
+ack_wait_handler(const struct nl_msg *msg, void *arg)
 {
     return NL_STOP;
 }
@@ -1176,7 +1253,7 @@ nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb)
     gboolean               creds_has;
 
 continue_reading:
-    n = nl_recv(sk, NULL, 0, &nla, &buf, &creds, &creds_has);
+    n = nl_recv(sk, NULL, 0, &nla, &buf, &creds, &creds_has, NULL, NULL);
     if (n <= 0)
         return n;
 
@@ -1193,7 +1270,7 @@ continue_reading:
         nrecv++;
 
         /* Only do sequence checking if auto-ack mode is enabled */
-        if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+        if (sk->s_auto_ack) {
             if (hdr->nlmsg_seq != sk->s_seq_expect) {
                 nmerr = -NME_NL_SEQ_MISMATCH;
                 goto out;
@@ -1378,7 +1455,7 @@ nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
 
     nlh->nlmsg_flags |= NLM_F_REQUEST;
 
-    if (!(sk->s_flags & NL_NO_AUTO_ACK))
+    if (sk->s_auto_ack)
         nlh->nlmsg_flags |= NLM_F_ACK;
 }
 
@@ -1413,6 +1490,10 @@ nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
  *   on success.
  * @out_creds_has: (out) (allow-none): result indicating whether
  *   @out_creds was filled.
+* @out_pktinfo_group: (out) (allow-none): optional out buffer for NETLINK_PKTINFO
+*    group on success.
+ * @out_pktinfo_has: (out) (allow-none): result indicating whether
+ *   @out_pktinfo_group was filled.
  *
  * If @buf0_len is zero, the function will g_malloc() a new receive buffer of size
  * nl_socket_get_msg_buf_size(). If @buf0_len is larger than zero, then @buf0
@@ -1435,18 +1516,20 @@ nl_recv(struct nl_sock     *sk,
         struct sockaddr_nl *nla,
         unsigned char     **buf,
         struct ucred       *out_creds,
-        gboolean           *out_creds_has)
+        gboolean           *out_creds_has,
+        uint32_t           *out_pktinfo_group,
+        gboolean           *out_pktinfo_has)
 {
-    /* We really expect msg_contol_buf to be large enough and MSG_CTRUNC not
-     * happening. We nm_assert() against that. However, in release builds
-     * we don't assert, so add some extra safety space for the unexpected
-     * case where we might need more than CMSG_SPACE(sizeof(struct ucred)).
-     * It should not hurt and should not be necessary. It's just some
-     * extra defensive space. */
-#define _MSG_CONTROL_BUF_EXTRA_SPACE (NM_MORE_ASSERTS ? 512u : 0u)
     union {
-        struct cmsghdr cmsghdr;
-        char           buf[CMSG_SPACE(sizeof(struct ucred)) + _MSG_CONTROL_BUF_EXTRA_SPACE];
+        struct cmsghdr _dummy_for_alignment;
+        struct {
+            char buf[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(struct nl_pktinfo))];
+
+            /* We really expect that "buf" is large enough end even assert against
+             * that. We don't expect and don't want to handle MSG_CTRUNC error.
+             * Still, add some extra safety. This is on the stack and essentially for free. */
+            char _extra[512];
+        };
     } msg_contol_buf;
     ssize_t       n;
     int           flags = 0;
@@ -1459,17 +1542,16 @@ nl_recv(struct nl_sock     *sk,
         .msg_controllen = 0,
         .msg_control    = NULL,
     };
-    struct ucred tmpcreds;
-    gboolean     tmpcreds_has = FALSE;
-    int          retval;
-    int          errsv;
+    struct cmsghdr *cmsg;
+    int             retval;
+    int             errsv;
 
     nm_assert(nla);
     nm_assert(buf && !*buf);
-    nm_assert(!out_creds_has == !out_creds);
+    nm_assert(!out_creds_has || out_creds);
+    nm_assert(!out_pktinfo_has || out_pktinfo_group);
 
-    if ((sk->s_flags & NL_MSG_PEEK)
-        || (!(sk->s_flags & NL_MSG_PEEK_EXPLICIT) && sk->s_bufsize == 0))
+    if (sk->s_msg_peek)
         flags |= MSG_PEEK | MSG_TRUNC;
 
     if (buf0_len > 0) {
@@ -1480,7 +1562,7 @@ nl_recv(struct nl_sock     *sk,
         iov.iov_base = g_malloc(iov.iov_len);
     }
 
-    if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+    if (out_creds_has || out_pktinfo_has) {
         msg.msg_controllen = sizeof(msg_contol_buf);
         msg.msg_control    = msg_contol_buf.buf;
     }
@@ -1500,11 +1582,14 @@ retry:
         goto abort;
     }
 
+    nm_assert((gsize) n <= G_MAXINT);
+
     /* We really don't expect truncation of ancillary data. We provided a large
     * enough buffer, so this is likely a bug. In the worst case, we might lack
     * the requested credentials and the caller likely will reject the message
     * later. */
     nm_assert(!(msg.msg_flags & MSG_CTRUNC));
+    nm_assert(msg.msg_controllen <= G_STRUCT_OFFSET(typeof(msg_contol_buf), _extra));
 
     if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
         /* respond with error to an incomplete message */
@@ -1533,32 +1618,35 @@ retry:
         goto abort;
     }
 
-    if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
-        struct cmsghdr *cmsg;
-
+    if (out_creds_has || out_pktinfo_has) {
+        NM_SET_OUT(out_creds_has, FALSE);
+        NM_SET_OUT(out_pktinfo_has, FALSE);
         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;
+            switch (cmsg->cmsg_level) {
+            case SOL_SOCKET:
+                if (cmsg->cmsg_type == SCM_CREDENTIALS && out_creds_has) {
+                    memcpy(out_creds, CMSG_DATA(cmsg), sizeof(*out_creds));
+                    *out_creds_has = TRUE;
+                }
+                break;
+            case SOL_NETLINK:
+                if (cmsg->cmsg_type == NETLINK_PKTINFO && out_pktinfo_has) {
+                    struct nl_pktinfo p;
+
+                    memcpy(&p, CMSG_DATA(cmsg), sizeof(p));
+                    *out_pktinfo_group = p.group;
+                    *out_pktinfo_has   = TRUE;
+                }
+                break;
+            }
         }
     }
 
-    retval = n;
+    *buf = iov.iov_base;
+    return (int) n;
 
 abort:
-    if (retval <= 0) {
-        if (iov.iov_base != buf0)
-            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);
+    if (iov.iov_base != buf0)
+        g_free(iov.iov_base);
     return retval;
 }