diff --git a/ell/ell.sym b/ell/ell.sym
index c5d1c2a9edea..1499081cb25e 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -344,8 +344,6 @@ global:
 	l_genl_msg_enter_nested;
 	l_genl_msg_leave_nested;
 	l_genl_attr_init;
-	l_genl_attr_next;
-	l_genl_attr_recurse;
 	l_genl_family_info_can_send;
 	l_genl_family_info_can_dump;
 	l_genl_family_info_has_group;
@@ -445,6 +443,9 @@ global:
 	l_netlink_message_add_header;
 	l_netlink_message_enter_nested;
 	l_netlink_message_leave_nested;
+	l_netlink_attr_init;
+	l_netlink_attr_next;
+	l_netlink_attr_recurse;
 	/* path */
 	l_basename;
 	l_path_find;
diff --git a/ell/genl.c b/ell/genl.c
index 5a8dd371a970..ac10cbf07ef0 100644
--- a/ell/genl.c
+++ b/ell/genl.c
@@ -1559,80 +1559,12 @@ LIB_EXPORT bool l_genl_msg_leave_nested(struct l_genl_msg *msg)
 LIB_EXPORT bool l_genl_attr_init(struct l_genl_attr *attr,
 						struct l_genl_msg *msg)
 {
-	const struct nlattr *nla;
-	uint32_t len;
-
-	if (unlikely(!attr) || unlikely(!msg))
-		return false;
-
-	if (!msg->nlm ||
-			msg->nlm->hdr->nlmsg_len < NLMSG_HDRLEN + GENL_HDRLEN)
-		return false;
-
-	nla = msg->nlm->data + NLMSG_HDRLEN + GENL_HDRLEN;
-	len = msg->nlm->hdr->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
-
-	if (!NLA_OK(nla, len))
-		return false;
-
-	attr->data = NULL;
-	attr->len = 0;
-	attr->next_data = nla;
-	attr->next_len = len;
-
-	return true;
-}
-
-LIB_EXPORT bool l_genl_attr_next(struct l_genl_attr *attr,
-						uint16_t *type,
-						uint16_t *len,
-						const void **data)
-{
-	const struct nlattr *nla;
-
-	if (unlikely(!attr))
-		return false;
-
-	nla = attr->next_data;
-
-	if (!NLA_OK(nla, attr->next_len))
+	if (unlikely(!msg) || unlikely(!msg->nlm))
 		return false;
 
-	if (type)
-		*type = nla->nla_type & NLA_TYPE_MASK;
-
-	if (len)
-		*len = NLA_PAYLOAD(nla);
-
-	if (data)
-		*data = NLA_DATA(nla);
-
-	attr->data = attr->next_data;
-	attr->len = attr->next_len;
-
-	attr->next_data = NLA_NEXT(nla, attr->next_len);
-
-	return true;
-}
-
-LIB_EXPORT bool l_genl_attr_recurse(const struct l_genl_attr *attr,
-						struct l_genl_attr *nested)
-{
-	const struct nlattr *nla;
-
-	if (unlikely(!attr) || unlikely(!nested))
-		return false;
-
-	nla = attr->data;
-	if (!nla)
-		return false;
-
-	nested->data = NULL;
-	nested->len = 0;
-	nested->next_data = NLA_DATA(nla);
-	nested->next_len = NLA_PAYLOAD(nla);
-
-	return true;
+	return l_netlink_attr_init((struct l_netlink_attr *) attr,
+				NLMSG_HDRLEN + GENL_HDRLEN,
+				msg->nlm->data, msg->nlm->hdr->nlmsg_len) == 0;
 }
 
 /**
diff --git a/ell/genl.h b/ell/genl.h
index 3f38fd9d5710..ed5f17c4fd2e 100644
--- a/ell/genl.h
+++ b/ell/genl.h
@@ -91,10 +91,20 @@ bool l_genl_msg_enter_nested(struct l_genl_msg *msg, uint16_t type);
 bool l_genl_msg_leave_nested(struct l_genl_msg *msg);
 
 bool l_genl_attr_init(struct l_genl_attr *attr, struct l_genl_msg *msg);
-bool l_genl_attr_next(struct l_genl_attr *attr, uint16_t *type,
-					uint16_t *len, const void **data);
-bool l_genl_attr_recurse(const struct l_genl_attr *attr,
-				struct l_genl_attr *nested);
+
+static inline bool l_genl_attr_next(struct l_genl_attr *attr, uint16_t *type,
+					uint16_t *len, const void **data)
+{
+	return l_netlink_attr_next((struct l_netlink_attr *) attr,
+					type, len, data) == 0;
+}
+
+static inline bool l_genl_attr_recurse(const struct l_genl_attr *attr,
+				struct l_genl_attr *nested)
+{
+	return l_netlink_attr_recurse((struct l_netlink_attr *) attr,
+					(struct l_netlink_attr *) nested) == 0;
+}
 
 bool l_genl_family_info_has_group(const struct l_genl_family_info *info,
 					const char *group);
diff --git a/ell/netlink.c b/ell/netlink.c
index 121a322fccf5..0be3009c736f 100644
--- a/ell/netlink.c
+++ b/ell/netlink.c
@@ -944,3 +944,78 @@ LIB_EXPORT int l_netlink_message_leave_nested(struct l_netlink_message *message)
 
 	return 0;
 }
+
+LIB_EXPORT int l_netlink_attr_init(struct l_netlink_attr *iter,
+					size_t header_len,
+					const void *data, uint32_t len)
+{
+	const struct nlattr *nla;
+
+	if (unlikely(!iter) || unlikely(!data))
+		return -EINVAL;
+
+	if (len < NLA_ALIGN(header_len))
+		return -EMSGSIZE;
+
+	nla = data + NLA_ALIGN(header_len);
+	len -= NLA_ALIGN(header_len);
+
+	if (!NLA_OK(nla, len))
+		return -EMSGSIZE;
+
+	iter->data = NULL;
+	iter->len = 0;
+	iter->next_data = nla;
+	iter->next_len = len;
+
+	return 0;
+}
+
+LIB_EXPORT int l_netlink_attr_next(struct l_netlink_attr *iter,
+					uint16_t *type, uint16_t *len,
+					const void **data)
+{
+	const struct nlattr *nla;
+
+	if (unlikely(!iter))
+		return -EINVAL;
+
+	nla = iter->next_data;
+	if (!NLA_OK(nla, iter->next_len))
+		return -EMSGSIZE;
+
+	if (type)
+		*type = nla->nla_type & NLA_TYPE_MASK;
+
+	if (len)
+		*len = NLA_PAYLOAD(nla);
+
+	if (data)
+		*data = NLA_DATA(nla);
+
+	iter->data = iter->next_data;
+	iter->len = iter->next_len;
+
+	iter->next_data = NLA_NEXT(nla, iter->next_len);
+	return 0;
+}
+
+LIB_EXPORT int l_netlink_attr_recurse(const struct l_netlink_attr *iter,
+					struct l_netlink_attr *nested)
+{
+	const struct nlattr *nla;
+
+	if (unlikely(!iter) || unlikely(!nested))
+		return -EINVAL;
+
+	nla = iter->data;
+	if (!nla)
+		return false;
+
+	nested->data = NULL;
+	nested->len = 0;
+	nested->next_data = NLA_DATA(nla);
+	nested->next_len = NLA_PAYLOAD(nla);
+
+	return 0;
+}
diff --git a/ell/netlink.h b/ell/netlink.h
index 08aff74fbac2..f09d42043675 100644
--- a/ell/netlink.h
+++ b/ell/netlink.h
@@ -119,6 +119,21 @@ static inline int l_netlink_message_append_mac(struct l_netlink_message *message
 	return l_netlink_message_append(message, type, mac, 6);
 }
 
+struct l_netlink_attr {
+	const struct nlattr *data;
+	uint32_t len;
+	const struct nlattr *next_data;
+	uint32_t next_len;
+};
+
+int l_netlink_attr_init(struct l_netlink_attr *attr, size_t header_len,
+					const void *data, uint32_t len);
+int l_netlink_attr_next(struct l_netlink_attr *attr,
+					uint16_t *type, uint16_t *len,
+					const void **data);
+int l_netlink_attr_recurse(const struct l_netlink_attr *iter,
+					struct l_netlink_attr *nested);
+
 #ifdef __cplusplus
 }
 #endif
