git: 4c91a5dfe483 - main - ifconfig: make interface and address listing use Netlink as transport

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Tue, 16 May 2023 19:39:32 UTC
The branch main has been updated by melifaro:

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

commit 4c91a5dfe48349c9238eeea6a5b9c11daca4e70d
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-05-10 13:58:21 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-05-16 19:39:13 +0000

    ifconfig: make interface and address listing use Netlink as transport
    
    Differential Revision: https://reviews.freebsd.org/D40044
---
 sbin/ifconfig/Makefile           |   6 +
 sbin/ifconfig/af_inet.c          |  57 ++++++
 sbin/ifconfig/af_inet6.c         |  67 ++++++
 sbin/ifconfig/af_link.c          |  53 +++++
 sbin/ifconfig/ifconfig.c         |  42 ++--
 sbin/ifconfig/ifconfig.h         |  31 +++
 sbin/ifconfig/ifconfig_netlink.c | 427 +++++++++++++++++++++++++++++++++++++++
 sbin/ifconfig/ifconfig_netlink.h |  37 ++++
 8 files changed, 702 insertions(+), 18 deletions(-)

diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile
index c48375b8c6d9..9b9df6ab9f78 100644
--- a/sbin/ifconfig/Makefile
+++ b/sbin/ifconfig/Makefile
@@ -71,6 +71,12 @@ LIBADD+=	jail
 .endif
 LIBADD+=	nv
 
+.if ${MK_NETLINK_SUPPORT} != "no"
+SRCS+=	ifconfig_netlink.c
+.else
+CFLAGS+=-DWITHOUT_NETLINK
+.endif
+
 MAN=	ifconfig.8
 
 CFLAGS+= -Wall -Wmissing-prototypes -Wcast-qual -Wwrite-strings -Wnested-externs
diff --git a/sbin/ifconfig/af_inet.c b/sbin/ifconfig/af_inet.c
index 6ce11fa2d673..4569c9c362e9 100644
--- a/sbin/ifconfig/af_inet.c
+++ b/sbin/ifconfig/af_inet.c
@@ -53,6 +53,7 @@ static const char rcsid[] =
 #include <netdb.h>
 
 #include "ifconfig.h"
+#include "ifconfig_netlink.h"
 
 static struct in_aliasreq in_addreq;
 static struct ifreq in_ridreq;
@@ -80,6 +81,7 @@ print_addr(struct sockaddr_in *sin)
 	printf("\tinet %s", addr_buf);
 }
 
+#ifdef WITHOUT_NETLINK
 static void
 in_status(int s __unused, const struct ifaddrs *ifa)
 {
@@ -129,6 +131,57 @@ in_status(int s __unused, const struct ifaddrs *ifa)
 	putchar('\n');
 }
 
+#else
+static struct in_addr
+get_mask(int plen)
+{
+	struct in_addr a;
+
+	a.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0);
+
+	return (a);
+}
+
+static struct sockaddr_in *
+satosin(struct sockaddr *sa)
+{
+	return ((struct sockaddr_in *)(void *)sa);
+}
+
+static void
+in_status_nl(struct ifconfig_args *args __unused, struct io_handler *h,
+    if_link_t *link, if_addr_t *ifa)
+{
+	struct sockaddr_in *sin = satosin(ifa->ifa_local);
+	int plen = ifa->ifa_prefixlen;
+
+	print_addr(sin);
+
+	if (link->ifi_flags & IFF_POINTOPOINT) {
+		struct sockaddr_in *dst = satosin(ifa->ifa_address);
+
+		printf(" --> %s", inet_ntoa(dst->sin_addr));
+	}
+	if (f_inet != NULL && strcmp(f_inet, "cidr") == 0) {
+		printf("/%d", plen);
+	} else if (f_inet != NULL && strcmp(f_inet, "dotted") == 0)
+		printf(" netmask %s", inet_ntoa(get_mask(plen)));
+	else
+		printf(" netmask 0x%lx", (unsigned long)ntohl(get_mask(plen).s_addr));
+
+	if ((link->ifi_flags & IFF_BROADCAST) && plen != 0)  {
+		struct sockaddr_in *brd = satosin(ifa->ifa_broadcast);
+		if (brd != NULL)
+			printf(" broadcast %s", inet_ntoa(brd->sin_addr));
+	}
+
+	if (ifa->ifaf_vhid != 0)
+		printf(" vhid %d", ifa->ifaf_vhid);
+
+	putchar('\n');
+}
+#endif
+
 #define SIN(x) ((struct sockaddr_in *) &(x))
 static struct sockaddr_in *sintab[] = {
 	SIN(in_ridreq.ifr_addr), SIN(in_addreq.ifra_addr),
@@ -235,7 +288,11 @@ in_set_tunnel(int s, struct addrinfo *srcres, struct addrinfo *dstres)
 static struct afswtch af_inet = {
 	.af_name	= "inet",
 	.af_af		= AF_INET,
+#ifdef WITHOUT_NETLINK
 	.af_status	= in_status,
+#else
+	.af_status_nl	= in_status_nl,
+#endif
 	.af_getaddr	= in_getaddr,
 	.af_postproc	= in_postproc,
 	.af_status_tunnel = in_status_tunnel,
diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c
index 49049ba2c376..0f4e0e75e44a 100644
--- a/sbin/ifconfig/af_inet6.c
+++ b/sbin/ifconfig/af_inet6.c
@@ -57,6 +57,7 @@ static const char rcsid[] =
 #include <netinet6/nd6.h>	/* Define ND6_INFINITE_LIFETIME */
 
 #include "ifconfig.h"
+#include "ifconfig_netlink.h"
 
 static	struct in6_ifreq in6_ridreq;
 static	struct in6_aliasreq in6_addreq =
@@ -242,6 +243,7 @@ print_lifetime(const char *prepend, time_t px_time, struct timespec *now)
 	printf(" %s", px_time < now->tv_sec ? "0" : sec2str(px_time - now->tv_sec));
 }
 
+#ifdef WITHOUT_NETLINK
 static void
 in6_status(int s __unused, const struct ifaddrs *ifa)
 {
@@ -313,6 +315,67 @@ in6_status(int s __unused, const struct ifaddrs *ifa)
 	putchar('\n');
 }
 
+#else
+static void
+show_lifetime(struct ifa_cacheinfo *ci)
+{
+	struct timespec now;
+	uint32_t pl, vl;
+
+	if (ci == NULL)
+		return;
+
+	int count = ci->ifa_prefered != ND6_INFINITE_LIFETIME;
+	count += ci->ifa_valid != ND6_INFINITE_LIFETIME;
+	if (count == 0)
+		return;
+
+	pl = (ci->ifa_prefered == ND6_INFINITE_LIFETIME) ? 0 : ci->ifa_prefered;
+	vl = (ci->ifa_valid == ND6_INFINITE_LIFETIME) ? 0 : ci->ifa_valid;
+
+	clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+	print_lifetime("pltime", pl + now.tv_sec, &now);
+	print_lifetime("vltime", vl + now.tv_sec, &now);
+}
+
+static struct sockaddr_in6 *
+satosin6(struct sockaddr *sa)
+{
+	return ((struct sockaddr_in6 *)(void *)sa);
+}
+
+static void
+in6_status_nl(struct ifconfig_args *args __unused, struct io_handler *h,
+    if_link_t *link, if_addr_t *ifa)
+{
+	int plen = ifa->ifa_prefixlen;
+	uint32_t scopeid;
+
+	if (ifa->ifa_local == NULL) {
+		/* Non-P2P address */
+		scopeid = satosin6(ifa->ifa_address)->sin6_scope_id;
+		print_addr(satosin6(ifa->ifa_address));
+	} else {
+		scopeid = satosin6(ifa->ifa_local)->sin6_scope_id;
+		print_addr(satosin6(ifa->ifa_local));
+		print_p2p(satosin6(ifa->ifa_address));
+	}
+
+	print_mask(plen);
+	print_flags(ifa->ifaf_flags);
+
+	if (scopeid != 0)
+		printf(" scopeid 0x%x", scopeid);
+
+	show_lifetime(ifa->ifa_cacheinfo);
+
+	if (ifa->ifaf_vhid != 0)
+		printf(" vhid %d", ifa->ifaf_vhid);
+
+	putchar('\n');
+}
+#endif
+
 #define	SIN6(x) ((struct sockaddr_in6 *) &(x))
 static struct	sockaddr_in6 *sin6tab[] = {
 	SIN6(in6_ridreq.ifr_addr), SIN6(in6_addreq.ifra_addr),
@@ -531,7 +594,11 @@ static struct cmd inet6_cmds[] = {
 static struct afswtch af_inet6 = {
 	.af_name	= "inet6",
 	.af_af		= AF_INET6,
+#ifdef WITHOUT_NETLINK
 	.af_status	= in6_status,
+#else
+	.af_status_nl	= in6_status_nl,
+#endif
 	.af_getaddr	= in6_getaddr,
 	.af_getprefix	= in6_getprefix,
 	.af_other_status = nd6_status,
diff --git a/sbin/ifconfig/af_link.c b/sbin/ifconfig/af_link.c
index f651ddc51cb4..52295453b4f0 100644
--- a/sbin/ifconfig/af_link.c
+++ b/sbin/ifconfig/af_link.c
@@ -51,6 +51,7 @@ static const char rcsid[] =
 #include <net/ethernet.h>
 
 #include "ifconfig.h"
+#include "ifconfig_netlink.h"
 
 static struct ifreq link_ridreq;
 
@@ -90,6 +91,7 @@ print_pcp(int s)
 		printf("\tpcp %d\n", ifr.ifr_lan_pcp);
 }
 
+#ifdef WITHOUT_NETLINK
 static void
 link_status(int s __unused, const struct ifaddrs *ifa)
 {
@@ -143,6 +145,45 @@ pcp:
 	print_pcp(s);
 }
 
+#else
+static uint8_t
+convert_iftype(uint8_t iftype)
+{
+	switch (iftype) {
+	case IFT_IEEE8023ADLAG:
+		return (IFT_ETHER);
+	case IFT_INFINIBANDLAG:
+		return (IFT_INFINIBAND);
+	}
+	return (iftype);
+}
+
+static void
+link_status_nl(struct ifconfig_args *args __unused, struct io_handler *h,
+    if_link_t *link, if_addr_t *ifa __unused)
+{
+	if (link->ifla_address != NULL) {
+		struct sockaddr_dl sdl = {
+			.sdl_len = sizeof(struct sockaddr_dl),
+			.sdl_family = AF_LINK,
+			.sdl_type = convert_iftype(link->ifi_type),
+			.sdl_alen = NLA_DATA_LEN(link->ifla_address),
+		};
+		memcpy(LLADDR(&sdl), NLA_DATA(link->ifla_address), sdl.sdl_alen);
+		print_lladdr(&sdl);
+
+		if (link->iflaf_orig_hwaddr != NULL) {
+			struct nlattr *hwaddr = link->iflaf_orig_hwaddr;
+
+			if (memcmp(NLA_DATA(hwaddr), NLA_DATA(link->ifla_address), sdl.sdl_alen))
+				print_ether((struct ether_addr *)NLA_DATA(hwaddr), "hwaddr");
+		}
+	}
+	if (convert_iftype(link->ifi_type) == IFT_ETHER)
+		print_pcp(h->s);
+}
+#endif
+
 static void
 link_getaddr(const char *addr, int which)
 {
@@ -180,7 +221,11 @@ link_getaddr(const char *addr, int which)
 static struct afswtch af_link = {
 	.af_name	= "link",
 	.af_af		= AF_LINK,
+#ifdef WITHOUT_NETLINK
 	.af_status	= link_status,
+#else
+	.af_status_nl	= link_status_nl,
+#endif
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
@@ -188,7 +233,11 @@ static struct afswtch af_link = {
 static struct afswtch af_ether = {
 	.af_name	= "ether",
 	.af_af		= AF_LINK,
+#ifdef WITHOUT_NETLINK
 	.af_status	= link_status,
+#else
+	.af_status_nl	= link_status_nl,
+#endif
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
@@ -196,7 +245,11 @@ static struct afswtch af_ether = {
 static struct afswtch af_lladdr = {
 	.af_name	= "lladdr",
 	.af_af		= AF_LINK,
+#ifdef WITHOUT_NETLINK
 	.af_status	= link_status,
+#else
+	.af_status_nl	= link_status_nl,
+#endif
 	.af_getaddr	= link_getaddr,
 	.af_aifaddr	= SIOCSIFLLADDR,
 	.af_addreq	= &link_ridreq,
diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c
index 15a40f1c5601..7872c2b336a5 100644
--- a/sbin/ifconfig/ifconfig.c
+++ b/sbin/ifconfig/ifconfig.c
@@ -108,20 +108,14 @@ int	exit_code = 0;
 /* Formatter Strings */
 char	*f_inet, *f_inet6, *f_ether, *f_addr;
 
-static	bool group_member(const char *ifname, const char *match,
-		const char *nomatch);
-static	int ifconfig(int argc, char *const *argv, int iscreate,
-		const struct afswtch *afp);
+static void list_interfaces_ioctl(struct ifconfig_args *args);
 static	void status(struct ifconfig_args *args, const struct sockaddr_dl *sdl,
 		struct ifaddrs *ifa);
-static	void tunnel_status(int s);
 static _Noreturn void usage(void);
 
 static int getifflags(const char *ifname, int us, bool err_ok);
 
 static struct afswtch *af_getbyname(const char *name);
-static struct afswtch *af_getbyfamily(int af);
-static void af_other_status(int);
 
 void printifnamemaybe(void);
 
@@ -403,7 +397,15 @@ void printifnamemaybe()
 		printf("%s\n", name);
 }
 
-static void list_interfaces(struct ifconfig_args *args);
+static void
+list_interfaces(struct ifconfig_args *args)
+{
+#ifdef WITHOUT_NETLINK
+	list_interfaces_ioctl(args);
+#else
+	list_interfaces_nl(args);
+#endif
+}
 
 int
 main(int argc, char *argv[])
@@ -651,7 +653,7 @@ match_afp(const struct afswtch *afp, int sa_family, const struct sockaddr_dl *sd
 	return (afp->af_af == sa_family);
 }
 
-static bool
+bool
 match_if_flags(struct ifconfig_args *args, int if_flags)
 {
 	if ((if_flags & IFF_CANTCONFIG) != 0)
@@ -663,8 +665,9 @@ match_if_flags(struct ifconfig_args *args, int if_flags)
 	return (true);
 }
 
+#ifdef WITHOUT_NETLINK
 static void
-list_interfaces(struct ifconfig_args *args)
+list_interfaces_ioctl(struct ifconfig_args *args)
 {
 	struct ifa_queue q = TAILQ_HEAD_INITIALIZER(q);
 	struct ifaddrs *ifap, *sifap, *ifa;
@@ -743,13 +746,14 @@ list_interfaces(struct ifconfig_args *args)
 		printf("\n");
 	freeifaddrs(ifap);
 }
+#endif
 
 /*
  * Returns true if an interface should be listed because any its groups
  * matches shell pattern "match" and none of groups matches pattern "nomatch".
  * If any pattern is NULL, corresponding condition is skipped.
  */
-static bool
+bool
 group_member(const char *ifname, const char *match, const char *nomatch)
 {
 	static int		 sock = -1;
@@ -832,7 +836,7 @@ af_getbyname(const char *name)
 	return NULL;
 }
 
-static struct afswtch *
+struct afswtch *
 af_getbyfamily(int af)
 {
 	struct afswtch *afp;
@@ -843,7 +847,7 @@ af_getbyfamily(int af)
 	return NULL;
 }
 
-static void
+void
 af_other_status(int s)
 {
 	struct afswtch *afp;
@@ -933,7 +937,7 @@ static void setifdstaddr(const char *, int, int, const struct afswtch *);
 static const struct cmd setifdstaddr_cmd =
 	DEF_CMD("ifdstaddr", 0, setifdstaddr);
 
-static int
+int
 ifconfig(int argc, char *const *argv, int iscreate, const struct afswtch *uafp)
 {
 	const struct afswtch *afp, *nafp;
@@ -1514,7 +1518,7 @@ print_ifcap_nv(struct ifconfig_args *args, int s)
 		Perror("ioctl (SIOCGIFCAP)");
 }
 
-static void
+void
 print_ifcap(struct ifconfig_args *args, int s)
 {
 	if (ioctl(s, SIOCGIFCAP, (caddr_t)&ifr) != 0)
@@ -1533,7 +1537,7 @@ print_ifcap(struct ifconfig_args *args, int s)
 	}
 }
 
-static void
+void
 print_ifstatus(int s)
 {
 	struct ifstat ifs;
@@ -1543,13 +1547,14 @@ print_ifstatus(int s)
 		printf("%s", ifs.ascii);
 }
 
-static void
+void
 print_metric(int s)
 {
 	if (ioctl(s, SIOCGIFMETRIC, &ifr) != -1)
 		printf(" metric %d", ifr.ifr_metric);
 }
 
+#ifdef WITHOUT_NETLINK
 static void
 print_mtu(int s)
 {
@@ -1658,8 +1663,9 @@ status(struct ifconfig_args *args, const struct sockaddr_dl *sdl,
 	close(s);
 	return;
 }
+#endif
 
-static void
+void
 tunnel_status(int s)
 {
 	af_all_tunnel_status(s);
diff --git a/sbin/ifconfig/ifconfig.h b/sbin/ifconfig/ifconfig.h
index e65c26a031e6..70a2c92199b6 100644
--- a/sbin/ifconfig/ifconfig.h
+++ b/sbin/ifconfig/ifconfig.h
@@ -150,6 +150,20 @@ enum {
 	DSTADDR,
 };
 
+struct snl_state;
+struct snl_parsed_addr;
+struct snl_parsed_link;
+typedef struct snl_parsed_link if_link_t;
+typedef struct snl_parsed_addr if_addr_t;
+struct ifconfig_args;
+struct io_handler {
+	int			s;	/* socket to use for ioctls */
+	struct snl_state	*ss;	/* NETLINK_ROUTE snl(3) socket */
+};
+
+typedef	void af_status_nl_f(struct ifconfig_args *args, struct io_handler *h,
+    if_link_t *link, if_addr_t *ifa);
+
 struct afswtch {
 	const char	*af_name;	/* as given on cmd line, e.g. "inet" */
 	short		af_af;		/* AF_* */
@@ -162,7 +176,11 @@ struct afswtch {
 	 * is defined then it is invoked after all address status
 	 * is presented.
 	 */
+#ifndef WITHOUT_NETLINK
+	af_status_nl_f	*af_status_nl;
+#else
 	void		(*af_status)(int, const struct ifaddrs *);
+#endif
 	void		(*af_other_status)(int);
 					/* parse address method */
 	void		(*af_getaddr)(const char *, int);
@@ -238,6 +256,19 @@ 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);
+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);
+struct afswtch	*af_getbyfamily(int af);
+void	af_other_status(int s);
+void	print_ifstatus(int s);
+void	print_metric(int s);
+
+/* Netlink-related functions */
+void	list_interfaces_nl(struct ifconfig_args *args);
+
 /*
  * XXX expose this so modules that neeed to know of any pending
  * operations on ifmedia can avoid cmd line ordering confusion.
diff --git a/sbin/ifconfig/ifconfig_netlink.c b/sbin/ifconfig/ifconfig_netlink.c
new file mode 100644
index 000000000000..26a42b5866c5
--- /dev/null
+++ b/sbin/ifconfig/ifconfig_netlink.c
@@ -0,0 +1,427 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+
+#include <sys/bitcount.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include "ifconfig.h"
+#include "ifconfig_netlink.h"
+
+static const char	*IFFBITS[] = {
+	"UP",			/* 00:0x1 IFF_UP*/
+	"BROADCAST",		/* 01:0x2 IFF_BROADCAST*/
+	"DEBUG",		/* 02:0x4 IFF_DEBUG*/
+	"LOOPBACK",		/* 03:0x8 IFF_LOOPBACK*/
+	"POINTOPOINT",		/* 04:0x10 IFF_POINTOPOINT*/
+	"NEEDSEPOCH",		/* 05:0x20 IFF_NEEDSEPOCH*/
+	"RUNNING",		/* 06:0x40 IFF_DRV_RUNNING*/
+	"NOARP",		/* 07:0x80 IFF_NOARP*/
+	"PROMISC",		/* 08:0x100 IFF_PROMISC*/
+	"ALLMULTI",		/* 09:0x200 IFF_ALLMULTI*/
+	"DRV_OACTIVE",		/* 10:0x400 IFF_DRV_OACTIVE*/
+	"SIMPLEX",		/* 11:0x800 IFF_SIMPLEX*/
+	"LINK0",		/* 12:0x1000 IFF_LINK0*/
+	"LINK1",		/* 13:0x2000 IFF_LINK1*/
+	"LINK2",		/* 14:0x4000 IFF_LINK2*/
+	"MULTICAST",		/* 15:0x8000 IFF_MULTICAST*/
+	"CANTCONFIG",		/* 16:0x10000 IFF_CANTCONFIG*/
+	"PPROMISC",		/* 17:0x20000 IFF_PPROMISC*/
+	"MONITOR",		/* 18:0x40000 IFF_MONITOR*/
+	"STATICARP",		/* 19:0x80000 IFF_STATICARP*/
+	"STICKYARP",		/* 20:0x100000 IFF_STICKYARP*/
+	"DYING",		/* 21:0x200000 IFF_DYING*/
+	"RENAMING",		/* 22:0x400000 IFF_RENAMING*/
+	"NOGROUP",		/* 23:0x800000 IFF_NOGROUP*/
+	"LOWER_UP",		/* 24:0x1000000 IFF_NETLINK_1*/
+};
+
+static void
+print_bits(const char *btype, uint32_t *v, const int v_count,
+    const char **names, const int n_count)
+{
+	int num = 0;
+
+	for (int i = 0; i < v_count * 32; i++) {
+		bool is_set = v[i / 32] & (1 << (i % 32));
+		if (i == 31)
+			v++;
+		if (is_set) {
+			if (num++ == 0)
+				printf("<");
+			if (num != 1)
+				printf(",");
+			if (i < n_count)
+				printf("%s", names[i]);
+			else
+				printf("%s_%d", btype, i);
+		}
+	}
+	if (num > 0)
+		printf(">");
+}	
+
+static void
+nl_init_socket(struct snl_state *ss)
+{
+	if (snl_init(ss, NETLINK_ROUTE))
+		return;
+
+	if (modfind("netlink") == -1 && errno == ENOENT) {
+		/* Try to load */
+		if (kldload("netlink") == -1)
+			err(1, "netlink is not loaded and load attempt failed");
+		if (snl_init(ss, NETLINK_ROUTE))
+			return;
+	}
+
+	err(1, "unable to open netlink socket");
+}
+
+struct ifa {
+	struct ifa		*next;
+	uint32_t		count;
+	uint32_t		idx;
+	struct snl_parsed_addr	addr;
+};
+
+struct iface {
+	struct snl_parsed_link	link;
+	struct ifa		*ifa;
+	uint32_t		ifa_count;
+	uint32_t		idx;
+};
+
+struct ifmap {
+	uint32_t		size;
+	uint32_t		count;
+	struct iface		**ifaces;
+};
+
+/*
+ * Returns ifmap ifindex->snl_parsed_link.
+ * Memory is allocated using snl temporary buffers
+ */
+static struct ifmap *
+prepare_ifmap(struct snl_state *ss)
+{
+	struct snl_writer nw = {};
+
+	snl_init_writer(ss, &nw);
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+	snl_reserve_msg_object(&nw, struct ifinfomsg);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (NULL);
+
+	uint32_t nlmsg_seq = hdr->nlmsg_seq;
+	struct ifmap *ifmap = snl_allocz(ss, sizeof(*ifmap));
+	struct snl_errmsg_data e = {};
+
+	while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) {
+		struct iface *iface = snl_allocz(ss, sizeof(*iface));
+
+		if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser, &iface->link))
+			continue;
+		if (iface->link.ifi_index >= ifmap->size) {
+			size_t new_size = MAX(ifmap->size, 32);
+
+			while (new_size <= iface->link.ifi_index + 1)
+				new_size *= 2;
+
+			struct iface **ifaces= snl_allocz(ss, new_size * sizeof(void *));
+			memcpy(ifaces, ifmap->ifaces, ifmap->size * sizeof(void *));
+			ifmap->ifaces = ifaces;
+			ifmap->size = new_size;
+		}
+		ifmap->ifaces[iface->link.ifi_index] = iface;
+		ifmap->count++;
+		iface->idx = ifmap->count;
+	}
+	return (ifmap);
+}
+
+static void
+prepare_ifaddrs(struct snl_state *ss, struct ifmap *ifmap)
+{
+	struct snl_writer nw = {};
+
+	snl_init_writer(ss, &nw);
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETADDR);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+	snl_reserve_msg_object(&nw, struct ifaddrmsg);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return;
+
+	uint32_t nlmsg_seq = hdr->nlmsg_seq;
+	struct snl_errmsg_data e = {};
+	uint32_t count = 0;
+
+	while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) {
+		struct ifa *ifa = snl_allocz(ss, sizeof(*ifa));
+
+		if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &ifa->addr))
+			continue;
+
+		const uint32_t ifindex = ifa->addr.ifa_index;
+		if (ifindex >= ifmap->size || ifmap->ifaces[ifindex] == NULL)
+			continue;
+		struct iface *iface = ifmap->ifaces[ifindex];
+		ifa->next = iface->ifa;
+		ifa->count = ++count;
+		iface->ifa = ifa;
+		iface->ifa_count++;
+	}
+}
+
+static bool
+match_iface(struct ifconfig_args *args, struct iface *iface)
+{
+	if_link_t *link = &iface->link;
+
+	if (args->ifname != NULL && strcmp(args->ifname, link->ifla_ifname))
+		return (false);
+
+	if (!match_if_flags(args, link->ifi_flags))
+		return (false);
+
+	if (!group_member(link->ifla_ifname, args->matchgroup, args->nogroup))
+		return (false);
+
+	if (args->afp == NULL)
+		return (true);
+
+	if (!strcmp(args->afp->af_name, "ether")) {
+		if (link->ifla_address == NULL)
+			return (false);
+
+		struct sockaddr_dl sdl = {
+			.sdl_len = sizeof(struct sockaddr_dl),
+			.sdl_family = AF_LINK,
+			.sdl_type = link->ifi_type,
+			.sdl_alen = NLA_DATA_LEN(link->ifla_address),
+		};
+		return (match_ether(&sdl));
+	}
+	
+	for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) {
+		if (args->afp->af_af == ifa->addr.ifa_family)
+			return (true);
+	}
+
+	return (false);
+}
+
+/* Sort according to the kernel-provided order */
+static int
+cmp_iface(const void *_a, const void *_b)
+{
+	const struct iface *a = *((const void * const *)_a);
+	const struct iface *b = *((const void * const *)_b);
+
+	return ((a->idx > b->idx) * 2 - 1);
+}
+
+static int
+cmp_ifaddr(const void *_a, const void *_b)
+{
+	const struct ifa *a = *((const void * const *)_a);
+	const struct ifa *b = *((const void * const *)_b);
+
+	if (a->addr.ifa_family != b->addr.ifa_family)
+		return ((a->addr.ifa_family > b->addr.ifa_family) * 2 - 1);
+	return ((a->idx > b->idx) * 2 - 1);
+}
+
+static void
+sort_iface_ifaddrs(struct snl_state *ss, struct iface *iface)
+{
+	if (iface->ifa_count == 0)
+		return;
+
+	struct ifa **sorted_ifaddrs = snl_allocz(ss, iface->ifa_count * sizeof(void *));
+	struct ifa *ifa = iface->ifa;
+
+	for (int i = 0; i < iface->ifa_count; i++) {
+		struct ifa *ifa_next = ifa->next;
+
+		sorted_ifaddrs[i] = ifa;
+		ifa->next = NULL;
+		ifa = ifa_next;
+	}
+	qsort(sorted_ifaddrs, iface->ifa_count, sizeof(void *), cmp_ifaddr);
+	ifa = sorted_ifaddrs[0];
+	iface->ifa = ifa;
+	for (int i = 1; i < iface->ifa_count; i++) {
+		ifa->next = sorted_ifaddrs[i];
+		ifa = sorted_ifaddrs[i];
+	}
+}
+
+static void
+status_nl(struct ifconfig_args *args, struct io_handler *h, struct iface *iface)
+{
+	if_link_t *link = &iface->link;
+
+	printf("%s: ", link->ifla_ifname);
+
+	printf("flags=%x", link->ifi_flags);
+	print_bits("IFF", &link->ifi_flags, 1, IFFBITS, nitems(IFFBITS));
+
+	print_metric(h->s);
+	printf(" mtu %d\n", link->ifla_mtu);
+
+	if (link->ifla_ifalias != NULL)
+		printf("\tdescription: %s\n", link->ifla_ifalias);
+
+	/* TODO: convert to netlink */
+	strlcpy(ifr.ifr_name, link->ifla_ifname, sizeof(ifr.ifr_name));
+	print_ifcap(args, h->s);
+	tunnel_status(h->s);
+
+	if (args->allfamilies | (args->afp != NULL && args->afp->af_af == AF_LINK)) {
+		/* Start with link-level */
+		const struct afswtch *p = af_getbyfamily(AF_LINK);
+		if (p != NULL && link->ifla_address != NULL)
+			p->af_status_nl(args, h, link, NULL);
+	}
+
+	sort_iface_ifaddrs(h->ss, iface);
+
+	for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) {
+		if (args->allfamilies) {
+			const struct afswtch *p = af_getbyfamily(ifa->addr.ifa_family);
+
+			if (p != NULL)
+				p->af_status_nl(args, h, link, &ifa->addr);
+		} else if (args->afp->af_af == ifa->addr.ifa_family) {
+			const struct afswtch *p = args->afp;
+
+			p->af_status_nl(args, h, link, &ifa->addr);
+		}
+	}
+
+	/* TODO: convert to netlink */
+	if (args->allfamilies)
+		af_other_status(h->s);
+	else if (args->afp->af_other_status != NULL)
+		args->afp->af_other_status(h->s);
+
+	print_ifstatus(h->s);
+	if (args->verbose > 0)
+		sfp_status(h->s, &ifr, args->verbose);
+}
+
+static int
+get_local_socket(void)
+{
+	int s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+	
+	if (s < 0)
+		err(1, "socket(family %u,SOCK_DGRAM)", AF_LOCAL);
+	return (s);
+}
+
+static void
+set_global_ifname(if_link_t *link)
+{
+	int iflen = strlcpy(name, link->ifla_ifname, sizeof(name));
+	if (iflen >= sizeof(name))
+		errx(1, "%s: cloning name too long", link->ifla_ifname);
+	strlcpy(ifr.ifr_name, link->ifla_ifname, sizeof(ifr.ifr_name));
+}
+
+void
+list_interfaces_nl(struct ifconfig_args *args)
+{
+	struct snl_state ss = {};
+
+	nl_init_socket(&ss);
+
+	struct ifmap *ifmap = prepare_ifmap(&ss);
+	struct iface **sorted_ifaces = snl_allocz(&ss, ifmap->count * sizeof(void *));
+	for (int i = 0, num = 0; i < ifmap->size; i++) {
+		if (ifmap->ifaces[i] != NULL) {
+			sorted_ifaces[num++] = ifmap->ifaces[i];
+			if (num == ifmap->count)
+				break;
+		}
+	}
+	qsort(sorted_ifaces, ifmap->count, sizeof(void *), cmp_iface);
+	prepare_ifaddrs(&ss, ifmap);
+
+	struct io_handler h = {
+		.s = get_local_socket(),
+		.ss = &ss,
+	};
+
+	for (int i = 0, num = 0; i < ifmap->count; i++) {
+		struct iface *iface = sorted_ifaces[i];
+
+		if (!match_iface(args, iface))
+			continue;
+
+		set_global_ifname(&iface->link);
+
+		if (args->namesonly) {
+			if (num++ != 0)
+				printf(" ");
+			fputs(iface->link.ifla_ifname, stdout);
+		} else if (args->argc == 0)
+			status_nl(args, &h, iface);
+		else
+			ifconfig(args->argc, args->argv, 0, args->afp);
+	}
+	if (args->namesonly)
+		printf("\n");
+
+	close(h.s);
+	snl_free(&ss);
+}
+
diff --git a/sbin/ifconfig/ifconfig_netlink.h b/sbin/ifconfig/ifconfig_netlink.h
new file mode 100644
index 000000000000..1c762619a483
--- /dev/null
+++ b/sbin/ifconfig/ifconfig_netlink.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2023 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
*** 30 LINES SKIPPED ***