git: 4bf44dd73bc0 - main - ifconfig: switch IPv4/IPv6 address manipulations to Netlink.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Thu, 25 May 2023 12:22:38 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=4bf44dd73bc0a68b73f7ee50d52adf5d7cda3eb8

commit 4bf44dd73bc0a68b73f7ee50d52adf5d7cda3eb8
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-05-20 11:53:46 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-05-25 12:22:18 +0000

    ifconfig: switch IPv4/IPv6 address manipulations to Netlink.
    
    Differential Revision: https://reviews.freebsd.org/D40182
---
 sbin/ifconfig/af_inet.c          | 262 ++++++++++++++++++++++++++++++++++++++-
 sbin/ifconfig/af_inet6.c         | 152 ++++++++++++++++++++++-
 sbin/ifconfig/af_link.c          |   3 +
 sbin/ifconfig/ifconfig.c         | 125 +++++++++++++------
 sbin/ifconfig/ifconfig.h         |  21 +++-
 sbin/ifconfig/ifconfig_netlink.c |  45 ++++++-
 6 files changed, 554 insertions(+), 54 deletions(-)

diff --git a/sbin/ifconfig/af_inet.c b/sbin/ifconfig/af_inet.c
index fdde3bd23595..73decb229d0f 100644
--- a/sbin/ifconfig/af_inet.c
+++ b/sbin/ifconfig/af_inet.c
@@ -55,8 +55,26 @@ static const char rcsid[] =
 #include "ifconfig.h"
 #include "ifconfig_netlink.h"
 
+#ifdef WITHOUT_NETLINK
 static struct in_aliasreq in_addreq;
 static struct ifreq in_ridreq;
+#else
+struct in_px {
+	struct in_addr		addr;
+	int			plen;
+	bool			addrset;
+	bool			maskset;
+};
+struct in_pdata {
+	struct in_px		addr;
+	struct in_px		dst_addr;
+	struct in_px		brd_addr;
+	uint32_t		flags;
+	uint32_t		vhid;
+};
+static struct in_pdata in_add, in_del;
+#endif
+
 static char addr_buf[NI_MAXHOST];	/*for getnameinfo()*/
 extern char *f_inet, *f_addr;
 
@@ -175,6 +193,8 @@ in_status_nl(if_ctx *ctx __unused, if_link_t *link, if_addr_t *ifa)
 }
 #endif
 
+
+#ifdef WITHOUT_NETLINK
 #define SIN(x) ((struct sockaddr_in *) &(x))
 static struct sockaddr_in *sintab[] = {
 	SIN(in_ridreq.ifr_addr), SIN(in_addreq.ifra_addr),
@@ -226,14 +246,234 @@ in_getaddr(const char *s, int which)
 		errx(1, "%s: bad value", s);
 }
 
+#else
+
+static struct in_px *sintab_nl[] = {
+	&in_del.addr,		/* RIDADDR */
+	&in_add.addr,		/* ADDR */
+	NULL,			/* MASK */
+	&in_add.dst_addr,	/* DSTADDR*/
+	&in_add.brd_addr,	/* BRDADDR*/
+};
+
+static void
+in_getip(const char *addr_str, struct in_addr *ip)
+{
+	struct hostent *hp;
+	struct netent *np;
+
+	if (inet_aton(addr_str, ip))
+		return;
+	if ((hp = gethostbyname(addr_str)) != NULL)
+		bcopy(hp->h_addr, (char *)ip,
+		    MIN((size_t)hp->h_length, sizeof(ip)));
+	else if ((np = getnetbyname(addr_str)) != NULL)
+		*ip = inet_makeaddr(np->n_net, INADDR_ANY);
+	else
+		errx(1, "%s: bad value", addr_str);
+}
+
+static void
+in_getaddr(const char *s, int which)
+{
+        struct in_px *px = sintab_nl[which];
+
+	if (which == MASK) {
+		struct in_px *px_addr = sintab_nl[ADDR];
+		struct in_addr mask = {};
+
+		in_getip(s, &mask);
+		px_addr->plen = __bitcount32(mask.s_addr);
+		px_addr->maskset = true;
+		return;
+	}
+
+	if (which == ADDR) {
+		char *p = NULL;
+
+		if((p = strrchr(s, '/')) != NULL) {
+			const char *errstr;
+			/* address is `name/masklen' */
+			int masklen;
+			*p = '\0';
+			if (!isdigit(*(p + 1)))
+				errstr = "invalid";
+			else
+				masklen = (int)strtonum(p + 1, 0, 32, &errstr);
+			if (errstr != NULL) {
+				*p = '/';
+				errx(1, "%s: bad value (width %s)", s, errstr);
+			}
+			px->plen = masklen;
+			px->maskset = true;
+		}
+	}
+
+	in_getip(s, &px->addr);
+	px->addrset = true;
+}
+
+/*
+ * Deletes the first found IPv4 interface address for the interface.
+ *
+ * This function provides SIOCDIFADDR semantics missing in Netlink.
+ * When no valid IPv4 address is specified (sin_family or sin_len is wrong) to
+ * the SIOCDIFADDR call, it deletes the first found IPv4 address on the interface.
+ * 'ifconfig IFNAME inet addr/prefix' relies on that behavior, as it
+ *  executes empty SIOCDIFADDR before adding a new address.
+ */
+static int
+in_delete_first_nl(if_ctx *ctx)
+{
+	struct nlmsghdr *hdr;
+	struct ifaddrmsg *ifahdr;
+	uint32_t nlmsg_seq;
+	struct in_addr addr;
+	struct snl_writer nw = {};
+	struct snl_errmsg_data e = {};
+	struct snl_state *ss = ctx->io_ss;
+	bool found = false;
+
+	uint32_t ifindex = if_nametoindex_nl(ss, name);
+	if (ifindex == 0) {
+		/* No interface with the desired name, nothing to delete */
+		return (EADDRNOTAVAIL);
+	}
+
+	snl_init_writer(ss, &nw);
+	hdr = snl_create_msg_request(&nw, NL_RTM_GETADDR);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+	ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
+	ifahdr->ifa_family = AF_INET;
+	ifahdr->ifa_index = ifindex;
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (EINVAL);
+
+	nlmsg_seq = hdr->nlmsg_seq;
+	while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) {
+		struct snl_parsed_addr attrs = {};
+		if (snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &attrs)) {
+			addr = satosin(attrs.ifa_local)->sin_addr;
+			ifindex = attrs.ifa_index;
+			found = true;
+			break;
+		} else
+			return (EINVAL);
+	}
+	if (e.error != 0) {
+		if (e.error_str != NULL)
+			warnx("%s(): %s", __func__, e.error_str);
+		return (e.error);
+	}
+
+	if (!found)
+		return (0);
+
+	/* Try to delete the found address */
+	snl_init_writer(ss, &nw);
+	hdr = snl_create_msg_request(&nw, NL_RTM_DELADDR);
+	ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
+	ifahdr->ifa_family = AF_INET;
+	ifahdr->ifa_index = ifindex;
+	snl_add_msg_attr_ip4(&nw, IFA_LOCAL, &addr);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (EINVAL);
+	memset(&e, 0, sizeof(e));
+	snl_read_reply_code(ss, hdr->nlmsg_seq, &e);
+	if (e.error_str != NULL)
+		warnx("%s(): %s", __func__, e.error_str);
+
+	return (e.error);
+}
+
+
+static int
+in_exec_nl(if_ctx *ctx, unsigned long action, void *data)
+{
+	struct in_pdata *pdata = (struct in_pdata *)data;
+	struct snl_writer nw = {};
+
+	if (action == NL_RTM_DELADDR && !pdata->addr.addrset)
+		return (in_delete_first_nl(ctx));
+
+	snl_init_writer(ctx->io_ss, &nw);
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, action);
+	struct ifaddrmsg *ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
+
+	ifahdr->ifa_family = AF_INET;
+	ifahdr->ifa_prefixlen = pdata->addr.plen;
+	ifahdr->ifa_index = if_nametoindex_nl(ctx->io_ss, name);
+
+	snl_add_msg_attr_ip4(&nw, IFA_LOCAL, &pdata->addr.addr);
+	if (action == NL_RTM_NEWADDR && pdata->dst_addr.addrset)
+		snl_add_msg_attr_ip4(&nw, IFA_ADDRESS, &pdata->dst_addr.addr);
+	if (action == NL_RTM_NEWADDR && pdata->brd_addr.addrset)
+		snl_add_msg_attr_ip4(&nw, IFA_BROADCAST, &pdata->brd_addr.addr);
+
+	int off = snl_add_msg_attr_nested(&nw, IFA_FREEBSD);
+	snl_add_msg_attr_u32(&nw, IFAF_FLAGS, pdata->flags);
+	if (pdata->vhid != 0)
+		snl_add_msg_attr_u32(&nw, IFAF_VHID, pdata->vhid);
+	snl_end_attr_nested(&nw, off);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ctx->io_ss, hdr))
+		return (0);
+
+	struct snl_errmsg_data e = {};
+	snl_read_reply_code(ctx->io_ss, hdr->nlmsg_seq, &e);
+	if (e.error_str != NULL)
+		warnx("%s(): %s", __func__, e.error_str);
+
+	return (e.error);
+}
+
+static void
+in_setdefaultmask_nl(void)
+{
+        struct in_px *px = sintab_nl[ADDR];
+
+	in_addr_t i = ntohl(px->addr.s_addr);
+
+	/*
+	 * If netmask isn't supplied, use historical default.
+	 * This is deprecated for interfaces other than loopback
+	 * or point-to-point; warn in other cases.  In the future
+	 * we should return an error rather than warning.
+	 */
+	if (IN_CLASSA(i))
+		px->plen = IN_CLASSA_NSHIFT;
+	else if (IN_CLASSB(i))
+		px->plen = IN_CLASSB_NSHIFT;
+	else
+		px->plen = IN_CLASSC_NSHIFT;
+	px->maskset = true;
+}
+#endif
+
+static void
+warn_nomask(int ifflags)
+{
+    if ((ifflags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) {
+	warnx("WARNING: setting interface address without mask "
+	    "is deprecated,\ndefault mask may not be correct.");
+    }
+}
+
 static void
 in_postproc(if_ctx *ctx __unused, int newaddr, int ifflags)
 {
-	if (sintab[ADDR]->sin_len != 0 && sintab[MASK]->sin_len == 0 &&
-	    newaddr && (ifflags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) {
-		warnx("WARNING: setting interface address without mask "
-		    "is deprecated,\ndefault mask may not be correct.");
+#ifdef WITHOUT_NETLINK
+	if (sintab[ADDR]->sin_len != 0 && sintab[MASK]->sin_len == 0 && newaddr) {
+		warn_nomask(ifflags);
+	}
+#else
+	if (sintab_nl[ADDR]->addrset && !sintab_nl[ADDR]->maskset && newaddr) {
+		warn_nomask(ifflags);
+	    in_setdefaultmask_nl();
 	}
+#endif
 }
 
 static void
@@ -281,10 +521,13 @@ in_set_tunnel(int s, struct addrinfo *srcres, struct addrinfo *dstres)
 static void
 in_set_vhid(int vhid)
 {
+#ifdef WITHOUT_NETLINK
 	in_addreq.ifra_vhid = vhid;
+#else
+	in_add.vhid = (uint32_t)vhid;
+#endif
 }
 
-
 static struct afswtch af_inet = {
 	.af_name	= "inet",
 	.af_af		= AF_INET,
@@ -298,10 +541,19 @@ static struct afswtch af_inet = {
 	.af_status_tunnel = in_status_tunnel,
 	.af_settunnel	= in_set_tunnel,
 	.af_setvhid	= in_set_vhid,
+#ifdef WITHOUT_NETLINK
 	.af_difaddr	= SIOCDIFADDR,
 	.af_aifaddr	= SIOCAIFADDR,
 	.af_ridreq	= &in_ridreq,
 	.af_addreq	= &in_addreq,
+	.af_exec	= af_exec_ioctl,
+#else
+	.af_difaddr	= NL_RTM_DELADDR,
+	.af_aifaddr	= NL_RTM_NEWADDR,
+	.af_ridreq	= &in_del,
+	.af_addreq	= &in_add,
+	.af_exec	= in_exec_nl,
+#endif
 };
 
 static __constructor void
diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c
index 8e2c4946e962..d5418b827789 100644
--- a/sbin/ifconfig/af_inet6.c
+++ b/sbin/ifconfig/af_inet6.c
@@ -59,10 +59,30 @@ static const char rcsid[] =
 #include "ifconfig.h"
 #include "ifconfig_netlink.h"
 
+#ifndef WITHOUT_NETLINK
+struct in6_px {
+	struct in6_addr		addr;
+	int			plen;
+	bool			set;
+};
+struct in6_pdata {
+	struct in6_px		addr;
+	struct in6_px		dst_addr;
+	struct in6_addrlifetime	lifetime;
+	uint32_t		flags;
+	uint32_t		vhid;
+};
+
+static struct in6_pdata in6_del;
+static struct in6_pdata in6_add = {
+	.lifetime = { 0, 0, ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME },
+};
+#else
 static	struct in6_ifreq in6_ridreq;
 static	struct in6_aliasreq in6_addreq =
   { .ifra_flags = 0,
     .ifra_lifetime = { 0, 0, ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME } };
+#endif
 static	int ip6lifetime;
 
 #ifdef WITHOUT_NETLINK
@@ -81,10 +101,18 @@ static	char addr_buf[NI_MAXHOST];	/*for getnameinfo()*/
 static void
 setifprefixlen(if_ctx *ctx, const char *addr, int dummy __unused)
 {
+#ifdef WITHOUT_NETLINK
 	const struct afswtch *afp = ctx->afp;
 
         if (afp->af_getprefix != NULL)
                 afp->af_getprefix(addr, MASK);
+#else
+	int plen = strtol(addr, NULL, 10);
+
+	if ((plen < 0) || (plen > 128))
+		errx(1, "%s: bad value", addr);
+	in6_add.addr.plen = plen;
+#endif
 	explicit_prefix = 1;
 }
 
@@ -96,10 +124,17 @@ setip6flags(if_ctx *ctx, const char *dummyaddr __unused, int flag)
 	if (afp->af_af != AF_INET6)
 		err(1, "address flags can be set only for inet6 addresses");
 
+#ifdef WITHOUT_NETLINK
 	if (flag < 0)
 		in6_addreq.ifra_flags &= ~(-flag);
 	else
 		in6_addreq.ifra_flags |= flag;
+#else
+	if (flag < 0)
+		in6_add.flags &= ~(-flag);
+	else
+		in6_add.flags |= flag;
+#endif
 }
 
 static void
@@ -109,6 +144,11 @@ setip6lifetime(if_ctx *ctx, const char *cmd, const char *val)
 	struct timespec now;
 	time_t newval;
 	char *ep;
+#ifdef WITHOUT_NETLINK
+	struct in6_addrlifetime *lifetime = &in6_addreq.ifra_lifetime;
+#else
+	struct in6_addrlifetime *lifetime = &in6_add.lifetime;
+#endif
 
 	clock_gettime(CLOCK_MONOTONIC_FAST, &now);
 	newval = (time_t)strtoul(val, &ep, 0);
@@ -117,11 +157,11 @@ setip6lifetime(if_ctx *ctx, const char *cmd, const char *val)
 	if (afp->af_af != AF_INET6)
 		errx(1, "%s not allowed for the AF", cmd);
 	if (strcmp(cmd, "vltime") == 0) {
-		in6_addreq.ifra_lifetime.ia6t_expire = now.tv_sec + newval;
-		in6_addreq.ifra_lifetime.ia6t_vltime = newval;
+		lifetime->ia6t_expire = now.tv_sec + newval;
+		lifetime->ia6t_vltime = newval;
 	} else if (strcmp(cmd, "pltime") == 0) {
-		in6_addreq.ifra_lifetime.ia6t_preferred = now.tv_sec + newval;
-		in6_addreq.ifra_lifetime.ia6t_pltime = newval;
+		lifetime->ia6t_preferred = now.tv_sec + newval;
+		lifetime->ia6t_pltime = newval;
 	}
 }
 
@@ -148,7 +188,11 @@ setip6eui64(if_ctx *ctx, const char *cmd, int dummy __unused)
 
 	if (afp->af_af != AF_INET6)
 		errx(EXIT_FAILURE, "%s not allowed for the AF", cmd);
+#ifdef WITHOUT_NETLINK
  	in6 = (struct in6_addr *)&in6_addreq.ifra_addr.sin6_addr;
+#else
+	in6 = &in6_add.addr.addr;
+#endif
 	if (memcmp(&in6addr_any.s6_addr[8], &in6->s6_addr[8], 8) != 0)
 		errx(EXIT_FAILURE, "interface index is already filled");
 	if (getifaddrs(&ifap) != 0)
@@ -369,8 +413,92 @@ in6_status_nl(if_ctx *ctx __unused, if_link_t *link, if_addr_t *ifa)
 
 	putchar('\n');
 }
+
+static struct in6_px *sin6tab_nl[] = {
+        &in6_del.addr,          /* RIDADDR */
+        &in6_add.addr,          /* ADDR */
+        NULL,                   /* MASK */
+        &in6_add.dst_addr,      /* DSTADDR*/
+};
+
+static void
+in6_getaddr(const char *addr_str, int which)
+{
+        struct in6_px *px = sin6tab_nl[which];
+
+        newaddr &= 1;
+
+        px->set = true;
+        px->plen = 128;
+        if (which == ADDR) {
+                char *p = NULL;
+                if((p = strrchr(addr_str, '/')) != NULL) {
+                        *p = '\0';
+                        int plen = strtol(p + 1, NULL, 10);
+			if (plen < 0 || plen > 128)
+                                errx(1, "%s: bad value", p + 1);
+                        px->plen = plen;
+                        explicit_prefix = 1;
+                }
+        }
+
+        struct addrinfo hints = { .ai_family = AF_INET6 };
+        struct addrinfo *res;
+
+        int error = getaddrinfo(addr_str, NULL, &hints, &res);
+        if (error != 0) {
+                if (inet_pton(AF_INET6, addr_str, &px->addr) != 1)
+                        errx(1, "%s: bad value", addr_str);
+        } else {
+                struct sockaddr_in6 *sin6;
+
+                sin6 = (struct sockaddr_in6 *)(void *)res->ai_addr;
+                px->addr = sin6->sin6_addr;
+                freeaddrinfo(res);
+        }
+}
+
+static int
+in6_exec_nl(if_ctx *ctx, unsigned long action, void *data)
+{
+	struct in6_pdata *pdata = (struct in6_pdata *)data;
+	struct snl_writer nw = {};
+
+	snl_init_writer(ctx->io_ss, &nw);
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, action);
+	struct ifaddrmsg *ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg);
+
+	ifahdr->ifa_family = AF_INET6;
+	ifahdr->ifa_prefixlen = pdata->addr.plen;
+	ifahdr->ifa_index = if_nametoindex_nl(ctx->io_ss, name);
+
+	snl_add_msg_attr_ip6(&nw, IFA_LOCAL, &pdata->addr.addr);
+	if (action == NL_RTM_NEWADDR && pdata->dst_addr.set)
+		snl_add_msg_attr_ip6(&nw, IFA_ADDRESS, &pdata->dst_addr.addr);
+
+	struct ifa_cacheinfo ci = {
+		.ifa_prefered = pdata->lifetime.ia6t_pltime,
+		.ifa_valid =  pdata->lifetime.ia6t_vltime,
+	};
+	snl_add_msg_attr(&nw, IFA_CACHEINFO, sizeof(ci), &ci);
+
+	int off = snl_add_msg_attr_nested(&nw, IFA_FREEBSD);
+	snl_add_msg_attr_u32(&nw, IFAF_FLAGS, pdata->flags);
+	if (pdata->vhid != 0)
+		snl_add_msg_attr_u32(&nw, IFAF_VHID, pdata->vhid);
+	snl_end_attr_nested(&nw, off);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ctx->io_ss, hdr))
+		return (0);
+
+	struct snl_errmsg_data e = {};
+	snl_read_reply_code(ctx->io_ss, hdr->nlmsg_seq, &e);
+
+	return (e.error);
+}
 #endif
 
+#ifdef WITHOUT_NETLINK
 static struct	sockaddr_in6 *sin6tab[] = {
 	&in6_ridreq.ifr_addr, &in6_addreq.ifra_addr,
 	&in6_addreq.ifra_prefixmask, &in6_addreq.ifra_dstaddr
@@ -434,7 +562,6 @@ in6_getaddr(const char *s, int which)
 	}
 }
 
-#ifdef WITHOUT_NETLINK
 static int
 prefix(void *val, int size)
 {
@@ -554,7 +681,11 @@ in6_set_tunnel(int s, struct addrinfo *srcres, struct addrinfo *dstres)
 static void
 in6_set_vhid(int vhid)
 {
+#ifdef WITHOUT_NETLINK
 	in6_addreq.ifra_vhid = vhid;
+#else
+	in6_add.vhid = (uint32_t)vhid;
+#endif
 }
 
 static struct cmd inet6_cmds[] = {
@@ -602,16 +733,27 @@ static struct afswtch af_inet6 = {
 	.af_status	= in6_status_nl,
 #endif
 	.af_getaddr	= in6_getaddr,
+#ifdef WITHOUT_NETLINK
 	.af_getprefix	= in6_getprefix,
+#endif
 	.af_other_status = nd6_status,
 	.af_postproc	= in6_postproc,
 	.af_status_tunnel = in6_status_tunnel,
 	.af_settunnel	= in6_set_tunnel,
 	.af_setvhid	= in6_set_vhid,
+#ifdef WITHOUT_NETLINK
 	.af_difaddr	= SIOCDIFADDR_IN6,
 	.af_aifaddr	= SIOCAIFADDR_IN6,
 	.af_ridreq	= &in6_addreq,
 	.af_addreq	= &in6_addreq,
+	.af_exec	= af_exec_ioctl,
+#else
+	.af_difaddr	= NL_RTM_DELADDR,
+	.af_aifaddr	= NL_RTM_NEWADDR,
+	.af_ridreq	= &in6_add,
+	.af_addreq	= &in6_add,
+	.af_exec	= in6_exec_nl,
+#endif
 };
 
 static void
diff --git a/sbin/ifconfig/af_link.c b/sbin/ifconfig/af_link.c
index 978006c217de..8fd5e05044e2 100644
--- a/sbin/ifconfig/af_link.c
+++ b/sbin/ifconfig/af_link.c
@@ -228,6 +228,7 @@ static struct afswtch af_link = {
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
+	.af_exec	= af_exec_ioctl,
 };
 static struct afswtch af_ether = {
 	.af_name	= "ether",
@@ -240,6 +241,7 @@ static struct afswtch af_ether = {
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
+	.af_exec	= af_exec_ioctl,
 };
 static struct afswtch af_lladdr = {
 	.af_name	= "lladdr",
@@ -252,6 +254,7 @@ static struct afswtch af_lladdr = {
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
+	.af_exec	= af_exec_ioctl,
 };
 
 static __constructor void
diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c
index ba14f84f7e7e..8ae7920e6ea9 100644
--- a/sbin/ifconfig/ifconfig.c
+++ b/sbin/ifconfig/ifconfig.c
@@ -119,6 +119,7 @@ static	void status(struct ifconfig_args *args, const struct sockaddr_dl *sdl,
 		struct ifaddrs *ifa);
 #endif
 static _Noreturn void usage(void);
+static void Perrorc(const char *cmd, int error);
 
 static int getifflags(const char *ifname, int us, bool err_ok);
 
@@ -560,6 +561,34 @@ args_parse(struct ifconfig_args *args, int argc, char *argv[])
 	verbose = args->verbose;
 }
 
+static int
+ifconfig_wrapper(struct ifconfig_args *args, int iscreate,
+    const struct afswtch *uafp)
+{
+#ifdef WITHOUT_NETLINK
+	struct ifconfig_context ctx = {
+		.args = args,
+		.io_s = -1,
+	};
+
+	return (ifconfig(&ctx, iscreate, uafp));
+#else
+	return (ifconfig_wrapper_nl(args, iscreate, uafp));
+#endif
+}
+
+static bool
+isargcreate(const char *arg)
+{
+	if (arg == NULL)
+		return (false);
+
+	if (strcmp(arg, "create") == 0 || strcmp(arg, "plumb") == 0)
+		return (true);
+
+	return (false);
+}
+
 int
 main(int ac, char *av[])
 {
@@ -613,13 +642,12 @@ main(int ac, char *av[])
 			 * right here as we would otherwise fail when trying
 			 * to find the interface.
 			 */
-			if (arg != NULL && (strcmp(arg, "create") == 0 ||
-			    strcmp(arg, "plumb") == 0)) {
+			if (isargcreate(arg)) {
 				iflen = strlcpy(name, args->ifname, sizeof(name));
 				if (iflen >= sizeof(name))
 					errx(1, "%s: cloning name too long",
 					    args->ifname);
-				ifconfig(args->argc, args->argv, 1, NULL);
+				ifconfig_wrapper(args, 1, NULL);
 				exit(exit_code);
 			}
 #ifdef JAIL
@@ -633,7 +661,7 @@ main(int ac, char *av[])
 				if (iflen >= sizeof(name))
 					errx(1, "%s: interface name too long",
 					    args->ifname);
-				ifconfig(args->argc, args->argv, 0, NULL);
+				ifconfig_wrapper(args, 0, NULL);
 				exit(exit_code);
 			}
 #endif
@@ -643,8 +671,7 @@ main(int ac, char *av[])
 			 * Do not allow use `create` command as hostname if
 			 * address family is not specified.
 			 */
-			if (arg != NULL && (strcmp(arg, "create") == 0 ||
-			    strcmp(arg, "plumb") == 0)) {
+			if (isargcreate(arg)) {
 				if (args->argc == 1)
 					errx(1, "interface %s already exists",
 					    args->ifname);
@@ -674,7 +701,7 @@ main(int ac, char *av[])
 			if (!(((flags & IFF_CANTCONFIG) != 0) ||
 				(args->downonly && (flags & IFF_UP) != 0) ||
 				(args->uponly && (flags & IFF_UP) == 0)))
-				ifconfig(args->argc, args->argv, 0, args->afp);
+				ifconfig_wrapper(args, 0, args->afp);
 		}
 		goto done;
 	}
@@ -802,7 +829,7 @@ list_interfaces_ioctl(struct ifconfig_args *args)
 		ifindex++;
 
 		if (args->argc > 0)
-			ifconfig(args->argc, args->argv, 0, args->afp);
+			ifconfig_wrapper(args, 0, args->afp);
 		else
 			status(args, sdl, ifa);
 	}
@@ -1001,31 +1028,42 @@ static void setifdstaddr(if_ctx *ctx, const char *addr, int param __unused);
 static const struct cmd setifdstaddr_cmd =
 	DEF_CMD("ifdstaddr", 0, setifdstaddr);
 
+int
+af_exec_ioctl(if_ctx *ctx, unsigned long action, void *data)
+{
+	struct ifreq *req = (struct ifreq *)data;
+
+	strlcpy(req->ifr_name, name, sizeof(req->ifr_name));
+	if (ioctl_ctx(ctx, action, req) == 0)
+		return (0);
+	return (errno);
+}
+
 static void
-delifaddr(int s, const struct afswtch *afp)
+delifaddr(if_ctx *ctx, const struct afswtch *afp)
 {
-	if (afp->af_ridreq == NULL || afp->af_difaddr == 0) {
+	int error;
+
+	if (afp->af_exec == NULL) {
 		warnx("interface %s cannot change %s addresses!",
 		    name, afp->af_name);
 		clearaddr = 0;
 		return;
 	}
 
-	strlcpy(((struct ifreq *)afp->af_ridreq)->ifr_name, name,
-		sizeof ifr.ifr_name);
-	int ret = ioctl(s, afp->af_difaddr, afp->af_ridreq);
-	if (ret < 0) {
-		if (errno == EADDRNOTAVAIL && (doalias >= 0)) {
+	error = afp->af_exec(ctx, afp->af_difaddr, afp->af_ridreq);
+	if (error != 0) {
+		if (error == EADDRNOTAVAIL && (doalias >= 0)) {
 			/* means no previous address for interface */
 		} else
-			Perror("ioctl (SIOCDIFADDR)");
+			Perrorc("ioctl (SIOCDIFADDR)", error);
 	}
 }
 
 static void
-addifaddr(int s, const struct afswtch *afp)
+addifaddr(if_ctx *ctx, const struct afswtch *afp)
 {
-	if (afp->af_addreq == NULL || afp->af_aifaddr == 0) {
+	if (afp->af_exec == NULL) {
 		warnx("interface %s cannot change %s addresses!",
 		      name, afp->af_name);
 		newaddr = 0;
@@ -1033,21 +1071,26 @@ addifaddr(int s, const struct afswtch *afp)
 	}
 
 	if (setaddr || setmask) {
-		strlcpy(((struct ifreq *)afp->af_addreq)->ifr_name, name,
-			sizeof ifr.ifr_name);
-		if (ioctl(s, afp->af_aifaddr, afp->af_addreq) < 0)
-			Perror("ioctl (SIOCAIFADDR)");
+		int error = afp->af_exec(ctx, afp->af_aifaddr, afp->af_addreq);
+		if (error != 0)
+			Perrorc("ioctl (SIOCAIFADDR)", error);
 	}
 }
 
 int
-ifconfig(int argc, char *const *argv, int iscreate, const struct afswtch *uafp)
+ifconfig(if_ctx *orig_ctx, int iscreate, const struct afswtch *uafp)
 {
-	struct ifconfig_context ctx = {};
 	const struct afswtch *afp, *nafp;
 	const struct cmd *p;
 	struct callback *cb;
 	int s;
+	int argc = orig_ctx->args->argc;
+	char *const *argv = orig_ctx->args->argv;
+	struct ifconfig_context _ctx = {
+		.args = orig_ctx->args,
+		.io_ss = orig_ctx->io_ss,
+	};
+	struct ifconfig_context *ctx = &_ctx;
 
 	strlcpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
 	afp = NULL;
@@ -1085,8 +1128,8 @@ top:
 	     (s = socket(AF_LOCAL, SOCK_DGRAM, 0)) < 0))
 		err(1, "socket(family %u,SOCK_DGRAM)", ifr.ifr_addr.sa_family);
 
-	ctx.io_s = s;
-	ctx.afp = afp;
+	ctx->io_s = s;
+	ctx->afp = afp;
 
 	while (argc > 0) {
 		p = cmd_lookup(*argv, iscreate);
@@ -1132,22 +1175,22 @@ top:
 			if (argv[1] == NULL)
 				errx(1, "'%s' requires argument",
 				    p->c_name);
-			p->c_u.c_func(&ctx, argv[1], 0);
+			p->c_u.c_func(ctx, argv[1], 0);
 			argc--, argv++;
 		} else if (p->c_parameter == OPTARG && p->c_u.c_func) {
-			p->c_u.c_func(&ctx, argv[1], 0);
+			p->c_u.c_func(ctx, argv[1], 0);
 			if (argv[1] != NULL)
 				argc--, argv++;
 		} else if (p->c_parameter == NEXTARG2 && p->c_u.c_func2) {
 			if (argc < 3)
 				errx(1, "'%s' requires 2 arguments",
 				    p->c_name);
-			p->c_u.c_func2(&ctx, argv[1], argv[2]);
+			p->c_u.c_func2(ctx, argv[1], argv[2]);
 			argc -= 2, argv += 2;
 		} else if (p->c_parameter == SPARAM && p->c_u.c_func3) {
-			p->c_u.c_func3(&ctx, *argv, p->c_sparameter);
+			p->c_u.c_func3(ctx, *argv, p->c_sparameter);
 		} else if (p->c_u.c_func)
-			p->c_u.c_func(&ctx, *argv, p->c_parameter);
+			p->c_u.c_func(ctx, *argv, p->c_parameter);
 		argc--, argv++;
 	}
 
@@ -1155,7 +1198,7 @@ top:
 	 * Do any post argument processing required by the address family.
 	 */
 	if (afp->af_postproc != NULL)
-		afp->af_postproc(&ctx, newaddr, getifflags(name, s, true));
+		afp->af_postproc(ctx, newaddr, getifflags(name, s, true));
 	/*
 	 * Do deferred callbacks registered while processing
 	 * command-line arguments.
@@ -1166,9 +1209,9 @@ top:
 	 * Do deferred operations.
 	 */
 	if (clearaddr)
-		delifaddr(s, afp);
+		delifaddr(ctx, afp);
 	if (newaddr)
-		addifaddr(s, afp);
+		addifaddr(ctx, afp);
 
 	close(s);
 	return(0);
@@ -1276,7 +1319,7 @@ setifbroadaddr(if_ctx *ctx, const char *addr, int dummy __unused)
 	const struct afswtch *afp = ctx->afp;
 
 	if (afp->af_getaddr != NULL)
-		afp->af_getaddr(addr, DSTADDR);
+		afp->af_getaddr(addr, BRDADDR);
 }
 
 static void
@@ -1749,8 +1792,8 @@ tunnel_status(int s)
 	af_all_tunnel_status(s);
 }
 
-void
-Perror(const char *cmd)
+static void
+Perrorc(const char *cmd, int error)
 {
 	switch (errno) {
 
@@ -1763,10 +1806,16 @@ Perror(const char *cmd)
 		break;
 
 	default:
-		err(1, "%s", cmd);
+		errc(1, error, "%s", cmd);
 	}
 }
 
+void
+Perror(const char *cmd)
+{
+	Perrorc(cmd, errno);
+}
+
 /*
  * Print a value a la the %b format of the kernel's printf
  */
diff --git a/sbin/ifconfig/ifconfig.h b/sbin/ifconfig/ifconfig.h
index 994596f02bcb..62dc0bdfe1f4 100644
--- a/sbin/ifconfig/ifconfig.h
+++ b/sbin/ifconfig/ifconfig.h
@@ -152,10 +152,15 @@ struct ifaddrs;
 struct addrinfo;
 
 enum {
-	RIDADDR,
-	ADDR,
-	MASK,
-	DSTADDR,
+	RIDADDR = 0,
+	ADDR = 1,
+	MASK = 2,
+	DSTADDR = 3,
+#ifdef WITHOUT_NETLINK
+	BRDADDR = 3,
+#else
+	BRDADDR = 4,
+#endif
 };
 
 struct snl_parsed_addr;
@@ -168,6 +173,7 @@ typedef	void af_status_nl_f(if_ctx *ctx, if_link_t *link, if_addr_t *ifa);
 typedef void af_status_f(if_ctx *ctx, const struct ifaddrs *);
 typedef void af_other_status_f(if_ctx *ctx);
 typedef void af_postproc_f(if_ctx *ctx, int newaddr, int ifflags);
+typedef	int af_exec_f(if_ctx *ctx, unsigned long action, void *data);
 
 struct afswtch {
 	const char	*af_name;	/* as given on cmd line, e.g. "inet" */
@@ -192,6 +198,7 @@ struct afswtch {
 	void		(*af_getprefix)(const char *, int);
 	af_postproc_f	*af_postproc;
 	af_setvhid_f	*af_setvhid;	/* Set CARP vhid for an address */
+	af_exec_f	*af_exec;	/* Handler to interact with kernel */
 	u_long		af_difaddr;	/* set dst if address ioctl */
 	u_long		af_aifaddr;	/* set if address ioctl */
 	void		*af_ridreq;	/* */
@@ -204,6 +211,7 @@ struct afswtch {
 				struct addrinfo *dstres);
 };
 void	af_register(struct afswtch *);
+int	af_exec_ioctl(if_ctx *ctx, unsigned long action, void *data);
 
 struct ifconfig_args {
 	bool all;		/* Match everything */
@@ -262,7 +270,7 @@ void	sfp_status(int s, struct ifreq *ifr, int verbose);
 struct sockaddr_dl;
 bool	match_ether(const struct sockaddr_dl *sdl);
 bool	match_if_flags(struct ifconfig_args *args, int if_flags);
-int	ifconfig(int argc, char *const *argv, int iscreate, const struct afswtch *uafp);
+int	ifconfig(if_ctx *ctx, int iscreate, const struct afswtch *uafp);
 bool	group_member(const char *ifname, const char *match, const char *nomatch);
 void	print_ifcap(struct ifconfig_args *args, int s);
 void	tunnel_status(int s);
@@ -273,6 +281,9 @@ void	print_metric(int s);
 
 /* Netlink-related functions */
 void	list_interfaces_nl(struct ifconfig_args *args);
+int	ifconfig_wrapper_nl(struct ifconfig_args *args, int iscreate,
+		const struct afswtch *uafp);
+uint32_t if_nametoindex_nl(struct snl_state *ss, const char *ifname);
 
 /*
  * XXX expose this so modules that neeed to know of any pending
diff --git a/sbin/ifconfig/ifconfig_netlink.c b/sbin/ifconfig/ifconfig_netlink.c
index 140808e18681..ebebea33f3f6 100644
--- a/sbin/ifconfig/ifconfig_netlink.c
+++ b/sbin/ifconfig/ifconfig_netlink.c
@@ -122,6 +122,26 @@ nl_init_socket(struct snl_state *ss)
 	err(1, "unable to open netlink socket");
 }
 
+int
+ifconfig_wrapper_nl(struct ifconfig_args *args, int iscreate,
+    const struct afswtch *uafp)
+{
+	struct snl_state ss = {};
+	struct ifconfig_context ctx = {
+		.args = args,
+		.io_s = -1,
+		.io_ss = &ss,
+	};
+
*** 51 LINES SKIPPED ***