git: 80f03e63d67e - main - netlink: improve interface handling

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Wed, 14 Dec 2022 19:53:15 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=80f03e63d67ede8fedbed4bd6bf6b12ec2ab2cfb

commit 80f03e63d67ede8fedbed4bd6bf6b12ec2ab2cfb
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2022-12-14 19:45:01 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2022-12-14 19:52:35 +0000

    netlink: improve interface handling
    
    * Separate interface creation from interface modification code
    * Support setting some interface attributes (ifdescr, mtu, up/down, promisc)
    * Improve interaction with the cloners requiring to parse/write custom
     interface attributes
    * Add bitmask-based way of checking if the attribute is present in the
    message
    * Don't use multipart RTM_GETLINK replies when searching for the
    specific interface names
    * Use ENODEV instead of ENOENT in case of failed RTM_GETLINK search
    * Add python netlink test helpers
    * Add some netlink interface tests
    
    Differential Revision: https://reviews.freebsd.org/D37668
---
 etc/mtree/BSD.tests.dist             |    2 +
 share/man/man4/rtnetlink.4           |    2 +
 sys/netlink/netlink_message_parser.c |   20 +-
 sys/netlink/netlink_message_parser.h |   28 +-
 sys/netlink/route/iface.c            |  208 ++++-
 sys/netlink/route/iface_drivers.c    |  100 ++-
 sys/netlink/route/interface.h        |    2 +-
 sys/netlink/route/route_var.h        |   15 +-
 tests/atf_python/sys/net/Makefile    |    2 +-
 tests/atf_python/sys/net/netlink.py  | 1495 ++++++++++++++++++++++++++++++++++
 tests/sys/netlink/Makefile           |   14 +
 tests/sys/netlink/test_rtnl_iface.py |  281 +++++++
 12 files changed, 2116 insertions(+), 53 deletions(-)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 5fbcd3c31669..7724138d3298 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -826,6 +826,8 @@
             tunnel
             ..
         ..
+        netlink
+        ..
         netmap
         ..
         netpfil
diff --git a/share/man/man4/rtnetlink.4 b/share/man/man4/rtnetlink.4
index 9f20671719f0..a06807809691 100644
--- a/share/man/man4/rtnetlink.4
+++ b/share/man/man4/rtnetlink.4
@@ -309,6 +309,8 @@ IFLA_ALT_IFNAME	interface name
 (binary) (readonly) Link-level broadcast address.
 .It Dv IFLA_IFNAME
 (string) New interface name.
+.It Dv IFLA_IFALIAS
+(string) Interface description.
 .It Dv IFLA_LINK
 (uint32_t) (readonly) Interface index.
 .It Dv IFLA_MASTER
diff --git a/sys/netlink/netlink_message_parser.c b/sys/netlink/netlink_message_parser.c
index d33eddb800e4..451d9d497491 100644
--- a/sys/netlink/netlink_message_parser.c
+++ b/sys/netlink/netlink_message_parser.c
@@ -147,17 +147,23 @@ nl_parse_attrs_raw(struct nlattr *nla_head, int len, const struct nlattr_parser
 	return (0);
 }
 
-int
-nl_parse_attrs(struct nlmsghdr *hdr, int hdrlen, struct nlattr_parser *ps, int pslen,
-    struct nl_pstate *npt, void *target)
+void
+nl_get_attrs_bmask_raw(struct nlattr *nla_head, int len, struct nlattr_bmask *bm)
 {
-	int off = NLMSG_HDRLEN + NETLINK_ALIGN(hdrlen);
-	int len = hdr->nlmsg_len - off;
-	struct nlattr *nla_head = (struct nlattr *)((char *)hdr + off);
+	struct nlattr *nla = NULL;
+
+	bzero(bm->mask, sizeof(bm->mask));
 
-	return (nl_parse_attrs_raw(nla_head, len, ps, pslen, npt, target));
+	NLA_FOREACH(nla, nla_head, len) {
+		if (nla->nla_len < sizeof(struct nlattr))
+			return;
+		int nla_type = nla->nla_type & NLA_TYPE_MASK;
+		if (nla_type <= sizeof(bm->mask) * 8)
+			bm->mask[nla_type / 8] |= 1 << (nla_type % 8);
+	}
 }
 
+
 int
 nlattr_get_flag(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
 {
diff --git a/sys/netlink/netlink_message_parser.h b/sys/netlink/netlink_message_parser.h
index b23b223ef80e..96fd1c7337b7 100644
--- a/sys/netlink/netlink_message_parser.h
+++ b/sys/netlink/netlink_message_parser.h
@@ -152,15 +152,21 @@ static const struct nlhdr_parser _name = {		\
 	.np_size = NL_ARRAY_LEN(_np),			\
 }
 
-struct nlarr_hdr {
-	int num_items;
-	int max_items;
+struct nlattr_bmask {
+	uint64_t			mask[2];
 };
 
+static inline bool
+nl_has_attr(const struct nlattr_bmask *bm, unsigned int attr_type)
+{
+	MPASS(attr_type < sizeof(bm->mask) * 8);
+
+	return ((bm->mask[attr_type / 8] & (1 << (attr_type % 8))));
+}
+void nl_get_attrs_bmask_raw(struct nlattr *nla_head, int len, struct nlattr_bmask *bm);
+
 int nl_parse_attrs_raw(struct nlattr *nla_head, int len, const struct nlattr_parser *ps,
     int pslen, struct nl_pstate *npt, void *target);
-int nl_parse_attrs(struct nlmsghdr *hdr, int hdrlen, struct nlattr_parser *ps,
-    int pslen, struct nl_pstate *npt, void *target);
 
 int nlattr_get_flag(struct nlattr *nla, struct nl_pstate *npt,
     const void *arg, void *target);
@@ -270,5 +276,17 @@ nl_parse_nlmsg(struct nlmsghdr *hdr, const struct nlhdr_parser *parser,
 	return (nl_parse_header(hdr + 1, hdr->nlmsg_len - sizeof(*hdr), parser, npt, target));
 }
 
+static inline void
+nl_get_attrs_bmask_nlmsg(struct nlmsghdr *hdr, const struct nlhdr_parser *parser,
+    struct nlattr_bmask *bm)
+{
+	struct nlattr *nla_head;
+
+	nla_head = (struct nlattr *)((char *)(hdr + 1) + parser->nl_hdr_off);
+	int len = hdr->nlmsg_len - sizeof(*hdr) - parser->nl_hdr_off;
+
+	nl_get_attrs_bmask_raw(nla_head, len, bm);
+}
+
 #endif
 #endif
diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c
index 579869e9662c..b033ba71009d 100644
--- a/sys/netlink/route/iface.c
+++ b/sys/netlink/route/iface.c
@@ -75,6 +75,8 @@ static SLIST_HEAD(, nl_cloner) nl_cloners = SLIST_HEAD_INITIALIZER(nl_cloners);
 static struct sx rtnl_cloner_lock;
 SX_SYSINIT(rtnl_cloner_lock, &rtnl_cloner_lock, "rtnl cloner lock");
 
+static struct nl_cloner *rtnl_iface_find_cloner_locked(const char *name);
+
 /*
  * RTM_GETLINK request
  * sendto(3, {{len=32, type=RTM_GETLINK, flags=NLM_F_REQUEST|NLM_F_DUMP, seq=1641940952, pid=0},
@@ -286,11 +288,23 @@ dump_iface(struct nl_writer *nw, struct ifnet *ifp, const struct nlmsghdr *hdr,
         nlattr_add_u32(nw, IFLA_MAX_MTU, 9000);
         nlattr_add_u32(nw, IFLA_GROUP, 0);
 */
+
+	if (ifp->if_description != NULL)
+		nlattr_add_string(nw, IFLA_IFALIAS, ifp->if_description);
+
 	get_stats(nw, ifp);
 
 	uint32_t val = (ifp->if_flags & IFF_PROMISC) != 0;
         nlattr_add_u32(nw, IFLA_PROMISCUITY, val);
 
+	sx_slock(&rtnl_cloner_lock);
+	struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(ifp->if_dname);
+	if (cloner != NULL && cloner->dump_f != NULL) {
+		/* Ignore any dump error */
+		cloner->dump_f(ifp, nw);
+	}
+	sx_sunlock(&rtnl_cloner_lock);
+
         if (nlmsg_end(nw))
 		return (true);
 
@@ -320,6 +334,8 @@ check_ifmsg(void *hdr, struct nl_pstate *npt)
 static const struct nlfield_parser nlf_p_if[] = {
 	{ .off_in = _IN(ifi_type), .off_out = _OUT(ifi_type), .cb = nlf_get_u16 },
 	{ .off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = nlf_get_u32 },
+	{ .off_in = _IN(ifi_flags), .off_out = _OUT(ifi_flags), .cb = nlf_get_u32 },
+	{ .off_in = _IN(ifi_change), .off_out = _OUT(ifi_change), .cb = nlf_get_u32 },
 };
 
 static const struct nlattr_parser nla_p_linfo[] = {
@@ -333,6 +349,7 @@ static const struct nlattr_parser nla_p_if[] = {
 	{ .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = nlattr_get_uint32 },
 	{ .type = IFLA_LINK, .off = _OUT(ifi_index), .cb = nlattr_get_uint32 },
 	{ .type = IFLA_LINKINFO, .arg = &linfo_parser, .cb = nlattr_get_nested },
+	{ .type = IFLA_IFALIAS, .off = _OUT(ifla_ifalias), .cb = nlattr_get_string },
 	{ .type = IFLA_GROUP, .off = _OUT(ifla_group), .cb = nlattr_get_string },
 	{ .type = IFLA_ALT_IFNAME, .off = _OUT(ifla_ifname), .cb = nlattr_get_string },
 };
@@ -379,28 +396,39 @@ rtnl_handle_getlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
 		.nw = npt->nw,
 		.hdr.nlmsg_pid = hdr->nlmsg_pid,
 		.hdr.nlmsg_seq = hdr->nlmsg_seq,
-		.hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
+		.hdr.nlmsg_flags = hdr->nlmsg_flags,
 		.hdr.nlmsg_type = NL_RTM_NEWLINK,
 	};
 
-	/* Fast track for an interface w/ explicit index match */
-	if (attrs.ifi_index != 0) {
-		NET_EPOCH_ENTER(et);
-		ifp = ifnet_byindex_ref(attrs.ifi_index);
-		NET_EPOCH_EXIT(et);
-		NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching index %u", attrs.ifi_index);
+	/* Fast track for an interface w/ explicit name or index match */
+	if ((attrs.ifi_index != 0) || (attrs.ifla_ifname != NULL)) {
+		if (attrs.ifi_index != 0) {
+			NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching index %u",
+			    attrs.ifi_index);
+			NET_EPOCH_ENTER(et);
+			ifp = ifnet_byindex_ref(attrs.ifi_index);
+			NET_EPOCH_EXIT(et);
+		} else {
+			NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching name %s",
+			    attrs.ifla_ifname);
+			ifp = ifunit_ref(attrs.ifla_ifname);
+		}
+
 		if (ifp != NULL) {
 			if (match_iface(&attrs, ifp)) {
 				if (!dump_iface(wa.nw, ifp, &wa.hdr, 0))
 					error = ENOMEM;
 			} else
-				error = ESRCH;
+				error = ENODEV;
 			if_rele(ifp);
 		} else
-			error = ESRCH;
+			error = ENODEV;
 		return (error);
 	}
 
+	/* Always treat non-direct-match as a multipart message */
+	wa.hdr.nlmsg_flags |= NLM_F_MULTI;
+
 	/*
 	 * Fetching some link properties require performing ioctl's that may be blocking.
 	 * Address it by saving referenced pointers of the matching links,
@@ -504,48 +532,146 @@ rtnl_handle_dellink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
 	return (error);
 }
 
+/*
+ * New link:
+ * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1668185590, pid=0},
+ *   {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}
+ *    [
+ *     {{nla_len=8, nla_type=IFLA_MTU}, 123},
+ *     {{nla_len=10, nla_type=IFLA_IFNAME}, "vlan1"},
+ *     {{nla_len=24, nla_type=IFLA_LINKINFO},
+ *      [
+ *       {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...},
+ *       {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x7b\x00\x00\x00"}]}]}
+ *
+ * Update link:
+ * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1668185923, pid=0},
+ * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=if_nametoindex("lo"), ifi_flags=0, ifi_change=0},
+ * {{nla_len=8, nla_type=IFLA_MTU}, 123}}
+ *
+ *
+ * Check command availability:
+ * type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK, seq=0, pid=0},
+ *  {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}
+ */
+
+
 static int
-rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+create_link(struct nlmsghdr *hdr, struct nl_parsed_link *lattrs,
+    struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt)
 {
-	struct nl_cloner *cloner;
-	int error;
+	if (lattrs->ifla_ifname == NULL || strlen(lattrs->ifla_ifname) == 0) {
+		NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_IFNAME attribute");
+		return (EINVAL);
+	}
+	if (lattrs->ifla_cloner == NULL || strlen(lattrs->ifla_cloner) == 0) {
+		NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_INFO_KIND attribute");
+		return (EINVAL);
+	}
 
-	struct nl_parsed_link attrs = {};
-	error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs);
-	if (error != 0)
-		return (error);
+	bool found = false;
+	int error = 0;
+
+	sx_slock(&rtnl_cloner_lock);
+	struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(lattrs->ifla_cloner);
+	if (cloner != NULL) {
+		found = true;
+		error = cloner->create_f(lattrs, bm, nlp, npt);
+	}
+	sx_sunlock(&rtnl_cloner_lock);
+
+	if (!found)
+		error = generic_cloner.create_f(lattrs, bm, nlp, npt);
+
+	return (error);
+}
+
+static int
+modify_link(struct nlmsghdr *hdr, struct nl_parsed_link *lattrs,
+    struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+	struct ifnet *ifp = NULL;
+	struct epoch_tracker et;
 
-	if (attrs.ifla_ifname == NULL || strlen(attrs.ifla_ifname) == 0) {
-		/* Applications like ip(8) verify RTM_NEWLINK existance
-		 * by calling it with empty arguments. Always return "innocent"
-		 * error.
+	if (lattrs->ifi_index == 0 && lattrs->ifla_ifname == NULL) {
+		/*
+		 * Applications like ip(8) verify RTM_NEWLINK command
+		 * existence by calling it with empty arguments. Always
+		 * return "innocent" error in that case.
 		 */
-		NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_IFNAME attribute");
+		NLMSG_REPORT_ERR_MSG(npt, "empty ifi_index field");
 		return (EPERM);
 	}
 
-	if (attrs.ifla_cloner == NULL || strlen(attrs.ifla_cloner) == 0) {
-		NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_INFO_KIND attribute");
-		return (EINVAL);
+	if (lattrs->ifi_index != 0) {
+		NET_EPOCH_ENTER(et);
+		ifp = ifnet_byindex_ref(lattrs->ifi_index);
+		NET_EPOCH_EXIT(et);
+		if (ifp == NULL) {
+			NLMSG_REPORT_ERR_MSG(npt, "unable to find interface #%u",
+			    lattrs->ifi_index);
+			return (ENOENT);
+		}
 	}
 
-	sx_slock(&rtnl_cloner_lock);
-	SLIST_FOREACH(cloner, &nl_cloners, next) {
-		if (!strcmp(attrs.ifla_cloner, cloner->name)) {
-			error = cloner->create_f(&attrs, nlp, npt);
-			sx_sunlock(&rtnl_cloner_lock);
-			return (error);
+	if (ifp == NULL && lattrs->ifla_ifname != NULL) {
+		ifp = ifunit_ref(lattrs->ifla_ifname);
+		if (ifp == NULL) {
+			NLMSG_REPORT_ERR_MSG(npt, "unable to find interface %s",
+			    lattrs->ifla_ifname);
+			return (ENOENT);
 		}
 	}
+
+	MPASS(ifp != NULL);
+
+	/*
+	 * There can be multiple kinds of interfaces:
+	 * 1) cloned, with additional options
+	 * 2) cloned, but w/o additional options
+	 * 3) non-cloned (e.g. "physical).
+	 *
+	 * Thus, try to find cloner-specific callback and fallback to the
+	 * "default" handler if not found.
+	 */
+	bool found = false;
+	int error = 0;
+
+	sx_slock(&rtnl_cloner_lock);
+	struct nl_cloner *cloner = rtnl_iface_find_cloner_locked(ifp->if_dname);
+	if (cloner != NULL) {
+		found = true;
+		error = cloner->modify_f(ifp, lattrs, bm, nlp, npt);
+	}
 	sx_sunlock(&rtnl_cloner_lock);
 
-	/* TODO: load cloner module if not exists & privilege permits */
-	NLMSG_REPORT_ERR_MSG(npt, "interface type %s not supported", attrs.ifla_cloner);
-	return (ENOTSUP);
+	if (!found)
+		error = generic_cloner.modify_f(ifp, lattrs, bm, nlp, npt);
+
+	if_rele(ifp);
 
 	return (error);
 }
 
+
+static int
+rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+	struct nlattr_bmask bm;
+	int error;
+
+	struct nl_parsed_link attrs = {};
+	error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs);
+	if (error != 0)
+		return (error);
+	nl_get_attrs_bmask_nlmsg(hdr, &ifmsg_parser, &bm);
+
+	if (hdr->nlmsg_flags & NLM_F_CREATE)
+		return (create_link(hdr, &attrs, &bm, nlp, npt));
+	else
+		return (modify_link(hdr, &attrs, &bm, nlp, npt));
+}
+
 /*
 
 {ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")},
@@ -863,13 +989,27 @@ rtnl_iface_add_cloner(struct nl_cloner *cloner)
 	sx_xunlock(&rtnl_cloner_lock);
 }
 
-void rtnl_iface_del_cloner(struct nl_cloner *cloner)
+void
+rtnl_iface_del_cloner(struct nl_cloner *cloner)
 {
 	sx_xlock(&rtnl_cloner_lock);
 	SLIST_REMOVE(&nl_cloners, cloner, nl_cloner, next);
 	sx_xunlock(&rtnl_cloner_lock);
 }
 
+static struct nl_cloner *
+rtnl_iface_find_cloner_locked(const char *name)
+{
+	struct nl_cloner *cloner;
+
+	SLIST_FOREACH(cloner, &nl_cloners, next) {
+		if (!strcmp(name, cloner->name))
+			return (cloner);
+	}
+
+	return (NULL);
+}
+
 void
 rtnl_ifaces_init(void)
 {
diff --git a/sys/netlink/route/iface_drivers.c b/sys/netlink/route/iface_drivers.c
index ccc8f2184fa3..7f098b808743 100644
--- a/sys/netlink/route/iface_drivers.c
+++ b/sys/netlink/route/iface_drivers.c
@@ -58,6 +58,95 @@ __FBSDID("$FreeBSD$");
 #include <netlink/netlink_debug.h>
 _DECLARE_DEBUG(LOG_DEBUG);
 
+/*
+ * Generic modification interface handler.
+ * Responsible for changing network stack interface attributes
+ * such as state, mtu or description.
+ */
+static int
+modify_generic(struct ifnet *ifp, struct nl_parsed_link *lattrs,
+    const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+	int error;
+
+	if (lattrs->ifla_ifalias != NULL) {
+		if (nlp_has_priv(nlp, PRIV_NET_SETIFDESCR)) {
+			int len = strlen(lattrs->ifla_ifalias) + 1;
+			char *buf = if_allocdescr(len, true);
+
+			memcpy(buf, lattrs->ifla_ifalias, len);
+			if_setdescr(ifp, buf);
+			getmicrotime(&ifp->if_lastchange);
+		} else {
+			nlmsg_report_err_msg(npt, "Not enough privileges to set descr");
+			return (EPERM);
+		}
+	}
+
+	if ((lattrs->ifi_change & IFF_UP) && (lattrs->ifi_flags & IFF_UP) == 0) {
+		/* Request to down the interface */
+		if_down(ifp);
+	}
+
+	if (lattrs->ifla_mtu > 0) {
+		if (nlp_has_priv(nlp, PRIV_NET_SETIFMTU)) {
+			struct ifreq ifr = { .ifr_mtu = lattrs->ifla_mtu };
+			error = ifhwioctl(SIOCSIFMTU, ifp, (char *)&ifr, curthread);
+		} else {
+			nlmsg_report_err_msg(npt, "Not enough privileges to set mtu");
+			return (EPERM);
+		}
+	}
+
+	if (lattrs->ifi_change & IFF_PROMISC) {
+		error = ifpromisc(ifp, lattrs->ifi_flags & IFF_PROMISC);
+		if (error != 0) {
+			nlmsg_report_err_msg(npt, "unable to set promisc");
+			return (error);
+		}
+	}
+
+	return (0);
+}
+
+/*
+ * Generic creation interface handler.
+ * Responsible for creating interfaces w/o parameters and setting
+ * misc attributes such as state, mtu or description.
+ */
+static int
+create_generic(struct nl_parsed_link *lattrs, const struct nlattr_bmask *bm,
+    struct nlpcb *nlp, struct nl_pstate *npt)
+{
+	int error = 0;
+
+	struct ifc_data ifd = {};
+	struct ifnet *ifp = NULL;
+	error = ifc_create_ifp(lattrs->ifla_ifname, &ifd, &ifp);
+
+	NLP_LOG(LOG_DEBUG2, nlp, "clone for %s returned %d", lattrs->ifla_ifname, error);
+
+	if (error == 0) {
+		struct epoch_tracker et;
+
+		NET_EPOCH_ENTER(et);
+		bool success = if_try_ref(ifp);
+		NET_EPOCH_EXIT(et);
+		if (!success)
+			return (EINVAL);
+		error = modify_generic(ifp, lattrs, bm, nlp, npt);
+		if_rele(ifp);
+	}
+
+	return (error);
+}
+
+struct nl_cloner generic_cloner = {
+	.name = "_default_",
+	.create_f = create_generic,
+	.modify_f = modify_generic,
+};
+
 /*
  *
  * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0},
@@ -87,7 +176,8 @@ static const struct nlattr_parser nla_p_vlan[] = {
 NL_DECLARE_ATTR_PARSER(vlan_parser, nla_p_vlan);
 
 static int
-create_vlan(struct nl_parsed_link *lattrs, struct nlpcb *nlp, struct nl_pstate *npt)
+create_vlan(struct nl_parsed_link *lattrs, const struct nlattr_bmask *bm,
+    struct nlpcb *nlp, struct nl_pstate *npt)
 {
 	struct epoch_tracker et;
         struct ifnet *ifp;
@@ -147,9 +237,17 @@ create_vlan(struct nl_parsed_link *lattrs, struct nlpcb *nlp, struct nl_pstate *
 	return (error);
 }
 
+static int
+dump_vlan(struct ifnet *ifp, struct nl_writer *nw)
+{
+	return (0);
+}
+
 static struct nl_cloner vlan_cloner = {
 	.name = "vlan",
 	.create_f = create_vlan,
+	.modify_f = modify_generic,
+	.dump_f = dump_vlan,
 
 };
 
diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h
index 1b8f1cf7b53d..12a8aa718993 100644
--- a/sys/netlink/route/interface.h
+++ b/sys/netlink/route/interface.h
@@ -92,7 +92,7 @@ enum {
 #define IFLA_LINKINFO IFLA_LINKINFO
 	IFLA_NET_NS_PID	= 19,	/* u32: vnet id (not supported) */
 #define	IFLA_NET_NS_PID IFLA_NET_NS_PID
-	IFLA_IFALIAS	= 20,	/* not supported */
+	IFLA_IFALIAS	= 20,	/* string: interface description */
 #define	IFLA_IFALIAS IFLA_IFALIAS
 	IFLA_NUM_VF	= 21,	/* not supported */
 #define	IFLA_NUM_VF IFLA_NUM_VF
diff --git a/sys/netlink/route/route_var.h b/sys/netlink/route/route_var.h
index 0bcfcc962020..f1e522c7ae05 100644
--- a/sys/netlink/route/route_var.h
+++ b/sys/netlink/route/route_var.h
@@ -66,24 +66,31 @@ struct nl_parsed_link {
 	char		*ifla_group;
 	char		*ifla_ifname;
 	char		*ifla_cloner;
+	char		*ifla_ifalias;
 	struct nlattr	*ifla_idata;
 	unsigned short	ifi_type;
 	int		ifi_index;
 	uint32_t	ifla_mtu;
+	uint32_t	ifi_flags;
+	uint32_t	ifi_change;
 };
 
-typedef int rtnl_iface_create_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp,
-    struct nl_pstate *npt);
-typedef int rtnl_iface_modify_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp,
-    struct nl_pstate *npt);
+typedef int rtnl_iface_create_f(struct nl_parsed_link *lattrs,
+    const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt);
+typedef int rtnl_iface_modify_f(struct ifnet *ifp, struct nl_parsed_link *lattrs,
+    const struct nlattr_bmask *bm, struct nlpcb *nlp, struct nl_pstate *npt);
+typedef int rtnl_iface_dump_f(struct ifnet *ifp, struct nl_writer *nw);
 
 struct nl_cloner {
 	const char		*name;
 	rtnl_iface_create_f	*create_f;
 	rtnl_iface_modify_f	*modify_f;
+	rtnl_iface_dump_f	*dump_f;
 	SLIST_ENTRY(nl_cloner)	next;
 };
 
+extern struct nl_cloner	generic_cloner;
+
 void rtnl_ifaces_init(void);
 void rtnl_ifaces_destroy(void);
 void rtnl_iface_add_cloner(struct nl_cloner *cloner);
diff --git a/tests/atf_python/sys/net/Makefile b/tests/atf_python/sys/net/Makefile
index 05b1d8afe863..63efefd27142 100644
--- a/tests/atf_python/sys/net/Makefile
+++ b/tests/atf_python/sys/net/Makefile
@@ -2,7 +2,7 @@
 
 .PATH:	${.CURDIR}
 
-FILES=	__init__.py rtsock.py tools.py vnet.py
+FILES=	__init__.py netlink.py rtsock.py tools.py vnet.py
 
 .include <bsd.own.mk>
 FILESDIR=	${TESTSBASE}/atf_python/sys/net
diff --git a/tests/atf_python/sys/net/netlink.py b/tests/atf_python/sys/net/netlink.py
new file mode 100644
index 000000000000..046519ce0343
--- /dev/null
+++ b/tests/atf_python/sys/net/netlink.py
@@ -0,0 +1,1495 @@
+#!/usr/local/bin/python3
+import os
+import socket
+import struct
+import sys
+import unittest
+from ctypes import c_int
+from ctypes import c_ubyte
+from ctypes import c_uint
+from ctypes import c_ushort
+from ctypes import sizeof
+from ctypes import Structure
+from enum import auto
+from enum import Enum
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import NamedTuple
+
+
+def roundup2(val: int, num: int) -> int:
+    if val % num:
+        return (val | (num - 1)) + 1
+    else:
+        return val
+
+
+def align4(val: int) -> int:
+    return roundup2(val, 4)
+
+
+class SockaddrNl(Structure):
+    _fields_ = [
+        ("nl_len", c_ubyte),
+        ("nl_family", c_ubyte),
+        ("nl_pad", c_ushort),
+        ("nl_pid", c_uint),
+        ("nl_groups", c_uint),
+    ]
+
+
+class Nlmsghdr(Structure):
+    _fields_ = [
+        ("nlmsg_len", c_uint),
+        ("nlmsg_type", c_ushort),
+        ("nlmsg_flags", c_ushort),
+        ("nlmsg_seq", c_uint),
+        ("nlmsg_pid", c_uint),
+    ]
+
+
+class Nlmsgerr(Structure):
+    _fields_ = [
+        ("error", c_int),
+        ("msg", Nlmsghdr),
+    ]
+
+
+class NlErrattrType(Enum):
+    NLMSGERR_ATTR_UNUSED = 0
+    NLMSGERR_ATTR_MSG = auto()
+    NLMSGERR_ATTR_OFFS = auto()
+    NLMSGERR_ATTR_COOKIE = auto()
+    NLMSGERR_ATTR_POLICY = auto()
+
+
+class RtattrType(Enum):
+    RTA_UNSPEC = 0
+    RTA_DST = auto()
+    RTA_SRC = auto()
+    RTA_IIF = auto()
+    RTA_OIF = auto()
+    RTA_GATEWAY = auto()
+    RTA_PRIORITY = auto()
+    RTA_PREFSRC = auto()
+    RTA_METRICS = auto()
+    RTA_MULTIPATH = auto()
+    RTA_PROTOINFO = auto()
+    RTA_FLOW = auto()
+    RTA_CACHEINFO = auto()
+    RTA_SESSION = auto()
+    RTA_MP_ALGO = auto()
+    RTA_TABLE = auto()
+    RTA_MARK = auto()
+    RTA_MFC_STATS = auto()
+    RTA_VIA = auto()
+    RTA_NEWDST = auto()
+    RTA_PREF = auto()
+    RTA_ENCAP_TYPE = auto()
+    RTA_ENCAP = auto()
+    RTA_EXPIRES = auto()
+    RTA_PAD = auto()
+    RTA_UID = auto()
+    RTA_TTL_PROPAGATE = auto()
+    RTA_IP_PROTO = auto()
+    RTA_SPORT = auto()
+    RTA_DPORT = auto()
+    RTA_NH_ID = auto()
+
+
+class NlMsgType(Enum):
+    NLMSG_NOOP = 1
+    NLMSG_ERROR = 2
+    NLMSG_DONE = 3
+    NLMSG_OVERRUN = 4
+
+
+class NlRtMsgType(Enum):
+    RTM_NEWLINK = 16
+    RTM_DELLINK = 17
+    RTM_GETLINK = 18
+    RTM_SETLINK = 19
+    RTM_NEWADDR = 20
+    RTM_DELADDR = 21
+    RTM_GETADDR = 22
+    RTM_NEWROUTE = 24
+    RTM_DELROUTE = 25
+    RTM_GETROUTE = 26
+    RTM_NEWNEIGH = 28
+    RTM_DELNEIGH = 27
+    RTM_GETNEIGH = 28
+    RTM_NEWRULE = 32
+    RTM_DELRULE = 33
+    RTM_GETRULE = 34
+    RTM_NEWQDISC = 36
+    RTM_DELQDISC = 37
+    RTM_GETQDISC = 38
+    RTM_NEWTCLASS = 40
+    RTM_DELTCLASS = 41
+    RTM_GETTCLASS = 42
+    RTM_NEWTFILTER = 44
+    RTM_DELTFILTER = 45
+    RTM_GETTFILTER = 46
+    RTM_NEWACTION = 48
+    RTM_DELACTION = 49
+    RTM_GETACTION = 50
+    RTM_NEWPREFIX = 52
+    RTM_GETMULTICAST = 58
+    RTM_GETANYCAST = 62
+    RTM_NEWNEIGHTBL = 64
+    RTM_GETNEIGHTBL = 66
+    RTM_SETNEIGHTBL = 67
+    RTM_NEWNDUSEROPT = 68
+    RTM_NEWADDRLABEL = 72
+    RTM_DELADDRLABEL = 73
+    RTM_GETADDRLABEL = 74
+    RTM_GETDCB = 78
+    RTM_SETDCB = 79
+    RTM_NEWNETCONF = 80
+    RTM_GETNETCONF = 82
+    RTM_NEWMDB = 84
+    RTM_DELMDB = 85
+    RTM_GETMDB = 86
+    RTM_NEWNSID = 88
+    RTM_DELNSID = 89
+    RTM_GETNSID = 90
+    RTM_NEWSTATS = 92
+    RTM_GETSTATS = 94
+
+
+class RtAttr(Structure):
+    _fields_ = [
+        ("rta_len", c_ushort),
+        ("rta_type", c_ushort),
+    ]
+
+
+class RtMsgHdr(Structure):
+    _fields_ = [
+        ("rtm_family", c_ubyte),
+        ("rtm_dst_len", c_ubyte),
+        ("rtm_src_len", c_ubyte),
+        ("rtm_tos", c_ubyte),
+        ("rtm_table", c_ubyte),
+        ("rtm_protocol", c_ubyte),
+        ("rtm_scope", c_ubyte),
+        ("rtm_type", c_ubyte),
+        ("rtm_flags", c_uint),
+    ]
+
+
+class RtMsgFlags(Enum):
+    RTM_F_NOTIFY = 0x100
+    RTM_F_CLONED = 0x200
+    RTM_F_EQUALIZE = 0x400
+    RTM_F_PREFIX = 0x800
+    RTM_F_LOOKUP_TABLE = 0x1000
+    RTM_F_FIB_MATCH = 0x2000
+    RTM_F_OFFLOAD = 0x4000
+    RTM_F_TRAP = 0x8000
+    RTM_F_OFFLOAD_FAILED = 0x20000000
+
+
+class AddressFamilyLinux(Enum):
+    AF_INET = socket.AF_INET
+    AF_INET6 = socket.AF_INET6
+    AF_NETLINK = 16
+
+
+class AddressFamilyBsd(Enum):
+    AF_INET = socket.AF_INET
+    AF_INET6 = socket.AF_INET6
+    AF_NETLINK = 38
+
+
+class NlmBaseFlags(Enum):
+    NLM_F_REQUEST = 0x01
+    NLM_F_MULTI = 0x02
+    NLM_F_ACK = 0x04
+    NLM_F_ECHO = 0x08
+    NLM_F_DUMP_INTR = 0x10
+    NLM_F_DUMP_FILTERED = 0x20
+
+
+# XXX: in python3.8 it is possible to
+# class NlmGetFlags(Enum, NlmBaseFlags):
+
+
+class NlmGetFlags(Enum):
+    NLM_F_ROOT = 0x100
+    NLM_F_MATCH = 0x200
+    NLM_F_ATOMIC = 0x400
+
+
+class NlmNewFlags(Enum):
+    NLM_F_REPLACE = 0x100
+    NLM_F_EXCL = 0x200
+    NLM_F_CREATE = 0x400
+    NLM_F_APPEND = 0x800
+
+
+class NlmDeleteFlags(Enum):
+    NLM_F_NONREC = 0x100
+
+
+class NlmAckFlags(Enum):
+    NLM_F_CAPPED = 0x100
+    NLM_F_ACK_TLVS = 0x200
+
+
+class RtScope(Enum):
+    RT_SCOPE_UNIVERSE = 0
+    RT_SCOPE_SITE = 200
+    RT_SCOPE_LINK = 253
+    RT_SCOPE_HOST = 254
+    RT_SCOPE_NOWHERE = 255
+
+
+class RtType(Enum):
+    RTN_UNSPEC = 0
+    RTN_UNICAST = auto()
+    RTN_LOCAL = auto()
+    RTN_BROADCAST = auto()
+    RTN_ANYCAST = auto()
+    RTN_MULTICAST = auto()
+    RTN_BLACKHOLE = auto()
+    RTN_UNREACHABLE = auto()
+    RTN_PROHIBIT = auto()
+    RTN_THROW = auto()
+    RTN_NAT = auto()
+    RTN_XRESOLVE = auto()
+
+
+class RtProto(Enum):
+    RTPROT_UNSPEC = 0
+    RTPROT_REDIRECT = 1
+    RTPROT_KERNEL = 2
+    RTPROT_BOOT = 3
+    RTPROT_STATIC = 4
+    RTPROT_GATED = 8
+    RTPROT_RA = 9
+    RTPROT_MRT = 10
+    RTPROT_ZEBRA = 11
+    RTPROT_BIRD = 12
+    RTPROT_DNROUTED = 13
+    RTPROT_XORP = 14
+    RTPROT_NTK = 15
+    RTPROT_DHCP = 16
+    RTPROT_MROUTED = 17
+    RTPROT_KEEPALIVED = 18
+    RTPROT_BABEL = 42
+    RTPROT_OPENR = 99
+    RTPROT_BGP = 186
+    RTPROT_ISIS = 187
+    RTPROT_OSPF = 188
+    RTPROT_RIP = 189
+    RTPROT_EIGRP = 192
+
+
+class NlRtaxType(Enum):
+    RTAX_UNSPEC = 0
+    RTAX_LOCK = auto()
+    RTAX_MTU = auto()
+    RTAX_WINDOW = auto()
+    RTAX_RTT = auto()
+    RTAX_RTTVAR = auto()
+    RTAX_SSTHRESH = auto()
+    RTAX_CWND = auto()
+    RTAX_ADVMSS = auto()
+    RTAX_REORDERING = auto()
+    RTAX_HOPLIMIT = auto()
+    RTAX_INITCWND = auto()
+    RTAX_FEATURES = auto()
+    RTAX_RTO_MIN = auto()
+    RTAX_INITRWND = auto()
+    RTAX_QUICKACK = auto()
+    RTAX_CC_ALGO = auto()
+    RTAX_FASTOPEN_NO_COOKIE = auto()
+
+
+class NlRtGroup(Enum):
+    RTNLGRP_NONE = 0
+    RTNLGRP_LINK = auto()
+    RTNLGRP_NOTIFY = auto()
+    RTNLGRP_NEIGH = auto()
+    RTNLGRP_TC = auto()
+    RTNLGRP_IPV4_IFADDR = auto()
+    RTNLGRP_IPV4_MROUTE = auto()
+    RTNLGRP_IPV4_ROUTE = auto()
+    RTNLGRP_IPV4_RULE = auto()
+    RTNLGRP_IPV6_IFADDR = auto()
+    RTNLGRP_IPV6_MROUTE = auto()
+    RTNLGRP_IPV6_ROUTE = auto()
+    RTNLGRP_IPV6_IFINFO = auto()
+    RTNLGRP_DECnet_IFADDR = auto()
+    RTNLGRP_NOP2 = auto()
+    RTNLGRP_DECnet_ROUTE = auto()
+    RTNLGRP_DECnet_RULE = auto()
+    RTNLGRP_NOP4 = auto()
+    RTNLGRP_IPV6_PREFIX = auto()
+    RTNLGRP_IPV6_RULE = auto()
+    RTNLGRP_ND_USEROPT = auto()
+    RTNLGRP_PHONET_IFADDR = auto()
+    RTNLGRP_PHONET_ROUTE = auto()
*** 1468 LINES SKIPPED ***