git: 91fbe0819bb9 - main - ndp: convert ndp(8) to netlink.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Tue, 25 Apr 2023 12:32:36 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=91fbe0819bb9c6e1a5e5b854075deb51742eb41f

commit 91fbe0819bb9c6e1a5e5b854075deb51742eb41f
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-04-25 12:30:39 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-04-25 12:30:39 +0000

    ndp: convert ndp(8) to netlink.
    
    The change is intended to be fully transparent to the users.
    Similarly to route(8) and netstat(8), ndp can be build without
      netlink by defining WITHOUT_NETLINK in make.conf.
    
     Differential Revision:  https://reviews.freebsd.org/D39720
---
 usr.sbin/ndp/Makefile      |   7 +
 usr.sbin/ndp/ndp.c         | 185 ++++++++++------
 usr.sbin/ndp/ndp.h         |  27 +++
 usr.sbin/ndp/ndp_netlink.c | 511 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 661 insertions(+), 69 deletions(-)

diff --git a/usr.sbin/ndp/Makefile b/usr.sbin/ndp/Makefile
index 84eb586083b3..1722f4a5a2ef 100644
--- a/usr.sbin/ndp/Makefile
+++ b/usr.sbin/ndp/Makefile
@@ -31,6 +31,13 @@ CFLAGS+=	-DEXPERIMENTAL
 CFLAGS+=	-DDRAFT_IETF_6MAN_IPV6ONLY_FLAG
 .endif
 
+.if ${MK_NETLINK_SUPPORT} != "no"
+SRCS+=	ndp_netlink.c
+.else
+CFLAGS+=-DWITHOUT_NETLINK
+.endif
+
+
 WARNS?=	3
 
 .include <bsd.prog.mk>
diff --git a/usr.sbin/ndp/ndp.c b/usr.sbin/ndp/ndp.c
index eaf652507e09..23e186466c22 100644
--- a/usr.sbin/ndp/ndp.c
+++ b/usr.sbin/ndp/ndp.c
@@ -102,6 +102,7 @@
 #include <netdb.h>
 #include <errno.h>
 #include <nlist.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
 #include <paths.h>
@@ -112,6 +113,8 @@
 #include <libxo/xo.h>
 #include "gmt2local.h"
 
+#include "ndp.h"
+
 #define	NEXTADDR(w, s)					\
 	if (rtm->rtm_addrs & (w)) {			\
 		bcopy((char *)&s, cp, sizeof(s));	\
@@ -119,8 +122,6 @@
 	}
 
 static pid_t pid;
-static int nflag;
-static int tflag;
 static int32_t thiszone;	/* time difference with gmt */
 static int s = -1;
 static int repeat = 0;
@@ -129,16 +130,13 @@ static char host_buf[NI_MAXHOST];	/* getnameinfo() */
 static char ifix_buf[IFNAMSIZ];		/* if_indextoname() */
 
 static int file(char *);
-static void getsocket(void);
 static int set(int, char **);
 static void get(char *);
 static int delete(char *);
-static void dump(struct sockaddr_in6 *, int);
+static int dump(struct sockaddr_in6 *, int);
 static struct in6_nbrinfo *getnbrinfo(struct in6_addr *, int, int);
-static char *ether_str(struct sockaddr_dl *);
 static int ndp_ether_aton(char *, u_char *);
 static void usage(void);
-static int rtmsg(int);
 static void ifinfo(char *, int, char **);
 static void rtrlist(void);
 static void plist(void);
@@ -149,8 +147,11 @@ static void harmonize_rtr(void);
 static void getdefif(void);
 static void setdefif(char *);
 #endif
-static char *sec2str(time_t);
-static void ts_print(const struct timeval *);
+
+#ifdef WITHOUT_NETLINK
+static void getsocket(void);
+static int rtmsg(int);
+#endif
 
 static const char *rtpref_str[] = {
 	"medium",		/* 00 */
@@ -159,8 +160,27 @@ static const char *rtpref_str[] = {
 	"low"			/* 11 */
 };
 
+struct ndp_opts opts = {};
+
 #define NDP_XO_VERSION	"1"
 
+bool
+valid_type(int if_type)
+{
+	switch (if_type) {
+	case IFT_ETHER:
+	case IFT_FDDI:
+	case IFT_ISO88023:
+	case IFT_ISO88024:
+	case IFT_ISO88025:
+	case IFT_L2VLAN:
+	case IFT_BRIDGE:
+		return (true);
+		break;
+	}
+	return (false);
+}
+
 int
 main(int argc, char **argv)
 {
@@ -206,10 +226,10 @@ main(int argc, char **argv)
 			arg = optarg;
 			break;
 		case 'n':
-			nflag = 1;
+			opts.nflag = true;
 			break;
 		case 't':
-			tflag = 1;
+			opts.tflag = true;
 			break;
 		case 'A':
 			if (mode) {
@@ -385,12 +405,12 @@ static struct sockaddr_dl blank_sdl = {
 	.sdl_family = AF_LINK
 };
 static struct sockaddr_dl sdl_m;
-static time_t expire_time;
-static int flags, found_entry;
+#ifdef WITHOUT_NETLINK
 static struct {
 	struct	rt_msghdr m_rtm;
 	char	m_space[512];
 } m_rtmsg;
+#endif
 
 /*
  * Set an individual neighbor cache entry
@@ -398,44 +418,44 @@ static struct {
 static int
 set(int argc, char **argv)
 {
-	register struct sockaddr_in6 *sin = &sin_m;
-	register struct sockaddr_dl *sdl;
-	register struct rt_msghdr *rtm = &(m_rtmsg.m_rtm);
-	struct addrinfo hints, *res;
+	struct sockaddr_in6 *sin = &sin_m;
 	int gai_error;
 	u_char *ea;
 	char *host = argv[0], *eaddr = argv[1];
 
-	getsocket();
 	argc -= 2;
 	argv += 2;
 	sdl_m = blank_sdl;
 	sin_m = blank_sin;
 
-	bzero(&hints, sizeof(hints));
-	hints.ai_family = AF_INET6;
-	gai_error = getaddrinfo(host, NULL, &hints, &res);
+	gai_error = getaddr(host, sin);
 	if (gai_error) {
 		xo_warnx("%s: %s", host, gai_strerror(gai_error));
 		return 1;
 	}
-	sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
-	sin->sin6_scope_id =
-	    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+
 	ea = (u_char *)LLADDR(&sdl_m);
 	if (ndp_ether_aton(eaddr, ea) == 0)
 		sdl_m.sdl_alen = 6;
-	flags = expire_time = 0;
 	while (argc-- > 0) {
 		if (strncmp(argv[0], "temp", 4) == 0) {
 			struct timeval now;
 
 			gettimeofday(&now, 0);
-			expire_time = now.tv_sec + 20 * 60;
+			opts.expire_time = now.tv_sec + 20 * 60;
 		} else if (strncmp(argv[0], "proxy", 5) == 0)
-			flags |= RTF_ANNOUNCE;
+			opts.flags |= RTF_ANNOUNCE;
 		argv++;
 	}
+
+#ifndef WITHOUT_NETLINK
+	return (set_nl(0, sin, &sdl_m, host));
+#else
+	struct rt_msghdr *rtm = &(m_rtmsg.m_rtm);
+	struct sockaddr_dl *sdl;
+
+	getsocket();
+
 	if (rtmsg(RTM_GET) < 0) {
 		xo_errx(1, "RTM_GET(%s) failed", host);
 		/* NOTREACHED */
@@ -445,12 +465,8 @@ set(int argc, char **argv)
 	if (IN6_ARE_ADDR_EQUAL(&sin->sin6_addr, &sin_m.sin6_addr)) {
 		if (sdl->sdl_family == AF_LINK &&
 		    !(rtm->rtm_flags & RTF_GATEWAY)) {
-			switch (sdl->sdl_type) {
-			case IFT_ETHER: case IFT_FDDI: case IFT_ISO88023:
-			case IFT_ISO88024: case IFT_ISO88025:
-			case IFT_L2VLAN: case IFT_BRIDGE:
+			if (valid_type(sdl->sdl_type))
 				goto overwrite;
-			}
 		}
 		xo_warnx("cannot configure a new entry");
 		return 1;
@@ -464,6 +480,24 @@ overwrite:
 	sdl_m.sdl_type = sdl->sdl_type;
 	sdl_m.sdl_index = sdl->sdl_index;
 	return (rtmsg(RTM_ADD));
+#endif
+}
+
+int
+getaddr(char *host, struct sockaddr_in6 *sin6)
+{
+	struct addrinfo hints = { .ai_family = AF_INET6 };
+	struct addrinfo *res;
+
+	int gai_error = getaddrinfo(host, NULL, &hints, &res);
+	if (gai_error != 0)
+		return (gai_error);
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_len = sizeof(*sin6);
+	sin6->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+	sin6->sin6_scope_id =
+	    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+	return (0);
 }
 
 /*
@@ -473,55 +507,45 @@ static void
 get(char *host)
 {
 	struct sockaddr_in6 *sin = &sin_m;
-	struct addrinfo hints, *res;
 	int gai_error;
 
 	sin_m = blank_sin;
-	bzero(&hints, sizeof(hints));
-	hints.ai_family = AF_INET6;
-	gai_error = getaddrinfo(host, NULL, &hints, &res);
+
+	gai_error = getaddr(host, sin);
 	if (gai_error) {
 		xo_warnx("%s: %s", host, gai_strerror(gai_error));
 		return;
 	}
-	sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
-	sin->sin6_scope_id =
-	    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
-	dump(sin, 0);
-	if (found_entry == 0) {
+	if (dump(sin, 0) == 0) {
 		getnameinfo((struct sockaddr *)sin, sin->sin6_len, host_buf,
 		    sizeof(host_buf), NULL ,0,
-		    (nflag ? NI_NUMERICHOST : 0));
+		    (opts.nflag ? NI_NUMERICHOST : 0));
 		xo_errx(1, "%s (%s) -- no entry", host, host_buf);
 	}
 }
 
+#ifdef WITHOUT_NETLINK
 /*
  * Delete a neighbor cache entry
  */
 static int
-delete(char *host)
+delete_rtsock(char *host)
 {
 	struct sockaddr_in6 *sin = &sin_m;
 	register struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
 	register char *cp = m_rtmsg.m_space;
 	struct sockaddr_dl *sdl;
-	struct addrinfo hints, *res;
 	int gai_error;
 
 	getsocket();
 	sin_m = blank_sin;
 
-	bzero(&hints, sizeof(hints));
-	hints.ai_family = AF_INET6;
-	gai_error = getaddrinfo(host, NULL, &hints, &res);
+	gai_error = getaddr(host, sin);
 	if (gai_error) {
 		xo_warnx("%s: %s", host, gai_strerror(gai_error));
 		return 1;
 	}
-	sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
-	sin->sin6_scope_id =
-	    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+
 	if (rtmsg(RTM_GET) < 0) {
 		xo_errx(1, "RTM_GET(%s) failed", host);
 		/* NOTREACHED */
@@ -552,7 +576,7 @@ delete:
 		getnameinfo((struct sockaddr *)sin,
 		    sin->sin6_len, host_buf,
 		    sizeof(host_buf), NULL, 0,
-		    (nflag ? NI_NUMERICHOST : 0));
+		    (opts.nflag ? NI_NUMERICHOST : 0));
 		xo_open_instance("neighbor-cache");
 
 		char *ifname = if_indextoname(sdl->sdl_index, ifix_buf);
@@ -571,15 +595,11 @@ delete:
 	return 0;
 }
 
-#define W_ADDR	36
-#define W_LL	17
-#define W_IF	6
-
 /*
  * Dump the entire neighbor cache
  */
-static void
-dump(struct sockaddr_in6 *addr, int cflag)
+static int
+dump_rtsock(struct sockaddr_in6 *addr, int cflag)
 {
 	int mib[6];
 	size_t needed;
@@ -596,7 +616,7 @@ dump(struct sockaddr_in6 *addr, int cflag)
 	char *ifname;
 
 	/* Print header */
-	if (!tflag && !cflag) {
+	if (!opts.tflag && !cflag) {
 		char xobuf[200];
 		snprintf(xobuf, sizeof(xobuf),
 		    "{T:/%%-%d.%ds} {T:/%%-%d.%ds} {T:/%%%d.%ds} {T:/%%-9.9s} {T:%%1s} {T:%%5s}\n",
@@ -626,6 +646,7 @@ again:;
 	} else
 		buf = lim = NULL;
 
+	int count = 0;
 	for (next = buf; next && next < lim; next += rtm->rtm_msglen) {
 		int isrouter = 0, prbs = 0;
 
@@ -658,9 +679,9 @@ again:;
 			    &sin->sin6_addr) == 0 ||
 			    addr->sin6_scope_id != sin->sin6_scope_id)
 				continue;
-			found_entry = 1;
 		} else if (IN6_IS_ADDR_MULTICAST(&sin->sin6_addr))
 			continue;
+		count++;
 		if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) ||
 		    IN6_IS_ADDR_MC_LINKLOCAL(&sin->sin6_addr)) {
 			/* XXX: should scope id be filled in the kernel? */
@@ -668,7 +689,7 @@ again:;
 				sin->sin6_scope_id = sdl->sdl_index;
 		}
 		getnameinfo((struct sockaddr *)sin, sin->sin6_len, host_buf,
-		    sizeof(host_buf), NULL, 0, (nflag ? NI_NUMERICHOST : 0));
+		    sizeof(host_buf), NULL, 0, (opts.nflag ? NI_NUMERICHOST : 0));
 		if (cflag) {
 #ifdef RTF_WASCLONED
 			if (rtm->rtm_flags & RTF_WASCLONED)
@@ -684,7 +705,7 @@ again:;
 			continue;
 		}
 		gettimeofday(&now, 0);
-		if (tflag)
+		if (opts.tflag)
 			ts_print(&now);
 
 		addrwidth = strlen(host_buf);
@@ -795,6 +816,30 @@ again:;
 	}
 
 	xo_close_list("neighbor-cache");
+
+	return (count);
+}
+#endif
+
+
+static int
+delete(char *host)
+{
+#ifndef WITHOUT_NETLINK
+	return (delete_nl(0, host));
+#else
+	return (delete_rtsock(host));
+#endif
+}
+
+static int
+dump(struct sockaddr_in6 *addr, int cflag)
+{
+#ifndef WITHOUT_NETLINK
+	return (print_entries_nl(0, addr, cflag));
+#else
+	return (dump_rtsock(addr, cflag));
+#endif
 }
 
 static struct in6_nbrinfo *
@@ -820,7 +865,7 @@ getnbrinfo(struct in6_addr *addr, int ifindex, int warning)
 	return(&nbi);
 }
 
-static char *
+char *
 ether_str(struct sockaddr_dl *sdl)
 {
 	static char hbuf[NI_MAXHOST];
@@ -869,6 +914,7 @@ usage(void)
 	exit(1);
 }
 
+#ifdef WITHOUT_NETLINK
 static int
 rtmsg(int cmd)
 {
@@ -882,7 +928,7 @@ rtmsg(int cmd)
 	if (cmd == RTM_DELETE)
 		goto doit;
 	bzero((char *)&m_rtmsg, sizeof(m_rtmsg));
-	rtm->rtm_flags = flags;
+	rtm->rtm_flags = opts.flags;
 	rtm->rtm_version = RTM_VERSION;
 
 	switch (cmd) {
@@ -890,8 +936,8 @@ rtmsg(int cmd)
 		xo_errx(1, "internal wrong cmd");
 	case RTM_ADD:
 		rtm->rtm_addrs |= RTA_GATEWAY;
-		if (expire_time) {
-			rtm->rtm_rmx.rmx_expire = expire_time;
+		if (opts.expire_time) {
+			rtm->rtm_rmx.rmx_expire = opts.expire_time;
 			rtm->rtm_inits = RTV_EXPIRE;
 		}
 		rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA);
@@ -922,6 +968,7 @@ doit:
 		xo_warn("read from routing socket");
 	return (0);
 }
+#endif
 
 static void
 ifinfo(char *ifname, int argc, char **argv)
@@ -1129,9 +1176,9 @@ rtrlist(void)
 
 		if (getnameinfo((struct sockaddr *)&p->rtaddr,
 		    p->rtaddr.sin6_len, host_buf, sizeof(host_buf), NULL, 0,
-		    (nflag ? NI_NUMERICHOST : 0)) != 0)
+		    (opts.nflag ? NI_NUMERICHOST : 0)) != 0)
 			strlcpy(host_buf, "?", sizeof(host_buf));
-		if (nflag)
+		if (opts.nflag)
 			paddr = host_buf;
 		else {
 			inet_ntop(AF_INET6, &p->rtaddr.sin6_addr, abuf, sizeof(abuf));
@@ -1187,7 +1234,7 @@ plist(void)
 	size_t l;
 	struct timeval now;
 	const int niflags = NI_NUMERICHOST;
-	int ninflags = nflag ? NI_NUMERICHOST : 0;
+	int ninflags = opts.nflag ? NI_NUMERICHOST : 0;
 	char namebuf[NI_MAXHOST];
 
 	if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &l, NULL, 0) < 0) {
@@ -1430,7 +1477,7 @@ getdefif(void)
 }
 #endif /* SIOCSDEFIFACE_IN6 */
 
-static char *
+char *
 sec2str(time_t total)
 {
 	static char result[256];
@@ -1475,7 +1522,7 @@ sec2str(time_t total)
  * Print the timestamp
  * from tcpdump/util.c
  */
-static void
+void
 ts_print(const struct timeval *tvp)
 {
 	int sec;
diff --git a/usr.sbin/ndp/ndp.h b/usr.sbin/ndp/ndp.h
new file mode 100644
index 000000000000..5b2558982e86
--- /dev/null
+++ b/usr.sbin/ndp/ndp.h
@@ -0,0 +1,27 @@
+#ifndef _USR_SBIN_NDP_NDP_H_
+#define _USR_SBIN_NDP_NDP_H_
+
+#define W_ADDR	36
+#define W_LL	17
+#define W_IF	6
+
+struct ndp_opts {
+	bool nflag;
+	bool tflag;
+	int flags;
+	time_t expire_time;
+};
+
+extern struct ndp_opts opts;
+
+bool valid_type(int if_type);
+void ts_print(const struct timeval *tvp);
+char *ether_str(struct sockaddr_dl *sdl);
+char *sec2str(time_t total);
+int getaddr(char *host, struct sockaddr_in6 *sin6);
+int print_entries_nl(uint32_t ifindex, struct sockaddr_in6 *addr, bool cflag);
+int delete_nl(uint32_t ifindex, char *host);
+int set_nl(uint32_t ifindex, struct sockaddr_in6 *dst, struct sockaddr_dl *sdl,
+    char *host);
+
+#endif
diff --git a/usr.sbin/ndp/ndp_netlink.c b/usr.sbin/ndp/ndp_netlink.c
new file mode 100644
index 000000000000..927cbf9ddcb9
--- /dev/null
+++ b/usr.sbin/ndp/ndp_netlink.c
@@ -0,0 +1,511 @@
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/linker.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <netinet/icmp6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <errno.h>
+#include <nlist.h>
+#include <stdio.h>
+#include <string.h>
+#include <paths.h>
+#include <err.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+#include "gmt2local.h"
+
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_route.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_route.h>
+#include <netlink/netlink_snl_route_compat.h>
+#include <netlink/netlink_snl_route_parsers.h>
+
+#include <libxo/xo.h>
+#include "ndp.h"
+
+#define RTF_ANNOUNCE	RTF_PROTO2
+
+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");
+}
+
+static bool
+get_link_info(struct snl_state *ss, uint32_t ifindex,
+    struct snl_parsed_link_simple *link)
+{
+	struct snl_writer nw;
+
+	snl_init_writer(ss, &nw);
+
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK);
+	struct ifinfomsg *ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg);
+	if (ifmsg != NULL)
+		ifmsg->ifi_index = ifindex;
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (false);
+
+	hdr = snl_read_reply(ss, hdr->nlmsg_seq);
+
+	if (hdr == NULL || hdr->nlmsg_type != RTM_NEWLINK)
+		return (false);
+
+	if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link))
+		return (false);
+
+	return (true);
+}
+
+
+
+static bool
+has_l2(struct snl_state *ss, uint32_t ifindex)
+{
+	struct snl_parsed_link_simple link = {};
+
+	if (!get_link_info(ss, ifindex, &link))
+		return (false);
+
+	return (valid_type(link.ifi_type) != 0);
+}
+
+static uint32_t
+get_myfib()
+{
+	uint32_t fibnum = 0;
+	size_t len = sizeof(fibnum);
+
+	sysctlbyname("net.my_fibnum", (void *)&fibnum, &len, NULL, 0);
+
+	return (fibnum);
+}
+
+static void
+ip6_writemask(struct in6_addr *addr6, uint8_t mask)
+{
+	uint32_t *cp;
+
+	for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32)
+		*cp++ = 0xFFFFFFFF;
+	if (mask > 0)
+		*cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
+}
+#define s6_addr32 __u6_addr.__u6_addr32
+#define IN6_MASK_ADDR(a, m)	do { \
+	(a)->s6_addr32[0] &= (m)->s6_addr32[0]; \
+	(a)->s6_addr32[1] &= (m)->s6_addr32[1]; \
+	(a)->s6_addr32[2] &= (m)->s6_addr32[2]; \
+	(a)->s6_addr32[3] &= (m)->s6_addr32[3]; \
+} while (0)
+
+static int
+guess_ifindex(struct snl_state *ss, uint32_t fibnum, const struct sockaddr_in6 *dst)
+{
+	struct snl_writer nw;
+
+	if (IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr))
+		return (dst->sin6_scope_id);
+	else if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr))
+		return (0);
+
+
+	snl_init_writer(ss, &nw);
+
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETROUTE);
+	struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg);
+	rtm->rtm_family = AF_INET6;
+
+	snl_add_msg_attr_ip(&nw, RTA_DST, (struct sockaddr *)dst);
+	snl_add_msg_attr_u32(&nw, RTA_TABLE, fibnum);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (0);
+
+	hdr = snl_read_reply(ss, hdr->nlmsg_seq);
+
+	if (hdr->nlmsg_type != NL_RTM_NEWROUTE) {
+		/* No route found, unable to guess ifindex */
+		return (0);
+	}
+
+	struct snl_parsed_route r = {};
+	if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r))
+		return (0);
+
+	if (r.rta_multipath || (r.rta_rtflags & RTF_GATEWAY))
+		return (0);
+
+	/* Check if the interface is of supported type */
+	if (has_l2(ss, r.rta_oif))
+		return (r.rta_oif);
+
+	/* Check the case when we matched the loopback route for P2P */
+	snl_init_writer(ss, &nw);
+	hdr = snl_create_msg_request(&nw, RTM_GETNEXTHOP);
+	snl_reserve_msg_object(&nw, struct nhmsg);
+
+	int off = snl_add_msg_attr_nested(&nw, NHA_FREEBSD);
+	snl_add_msg_attr_u32(&nw, NHAF_KID, r.rta_knh_id);
+	snl_add_msg_attr_u8(&nw, NHAF_FAMILY, AF_INET);
+	snl_add_msg_attr_u32(&nw, NHAF_TABLE, fibnum);
+	snl_end_attr_nested(&nw, off);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr))
+		return (0);
+
+	hdr = snl_read_reply(ss, hdr->nlmsg_seq);
+
+	if (hdr->nlmsg_type != NL_RTM_NEWNEXTHOP) {
+		/* No nexthop found, unable to guess ifindex */
+		return (0);
+	}
+
+	struct snl_parsed_nhop nh = {};
+	if (!snl_parse_nlmsg(ss, hdr, &snl_nhmsg_parser, &nh))
+		return (0);
+
+	return (nh.nhaf_aif);
+}
+
+static uint32_t
+fix_ifindex(struct snl_state *ss, uint32_t ifindex, const struct sockaddr_in6 *sa)
+{
+	if (ifindex == 0)
+		ifindex = guess_ifindex(ss, get_myfib(), sa);
+	return (ifindex);
+}
+
+static void
+print_entry(struct snl_parsed_neigh *neigh, struct snl_parsed_link_simple *link)
+{
+	struct timeval now;
+	char host_buf[NI_MAXHOST];
+	int addrwidth;
+	int llwidth;
+	int ifwidth;
+	char *ifname;
+
+	getnameinfo(neigh->nda_dst, sizeof(struct sockaddr_in6), host_buf,
+	    sizeof(host_buf), NULL, 0, (opts.nflag ? NI_NUMERICHOST : 0));
+
+	gettimeofday(&now, 0);
+	if (opts.tflag)
+		ts_print(&now);
+
+	struct sockaddr_dl sdl = {
+		.sdl_family = AF_LINK,
+		.sdl_type = link->ifi_type,
+		.sdl_len = sizeof(struct sockaddr_dl),
+		.sdl_alen = NLA_DATA_LEN(neigh->nda_lladdr),
+	};
+	memcpy(sdl.sdl_data, NLA_DATA(neigh->nda_lladdr), sdl.sdl_alen);
+
+	addrwidth = strlen(host_buf);
+	if (addrwidth < W_ADDR)
+		addrwidth = W_ADDR;
+	llwidth = strlen(ether_str(&sdl));
+	if (W_ADDR + W_LL - addrwidth > llwidth)
+		llwidth = W_ADDR + W_LL - addrwidth;
+	ifname = link->ifla_ifname;
+	ifwidth = strlen(ifname);
+	if (W_ADDR + W_LL + W_IF - addrwidth - llwidth > ifwidth)
+		ifwidth = W_ADDR + W_LL + W_IF - addrwidth - llwidth;
+
+	xo_open_instance("neighbor-cache");
+	/* Compose format string for libxo, as it doesn't support *.* */
+	char xobuf[200];
+	snprintf(xobuf, sizeof(xobuf),
+	    "{:address/%%-%d.%ds/%%s} {:mac-address/%%-%d.%ds/%%s} {:interface/%%%d.%ds/%%s}",
+	    addrwidth, addrwidth, llwidth, llwidth, ifwidth, ifwidth);
+	xo_emit(xobuf, host_buf, ether_str(&sdl), ifname);
+
+	/* Print neighbor discovery specific information */
+	uint32_t expire = neigh->ndaf_next_ts;
+	int expire_in = expire - now.tv_sec;
+	if (expire > now.tv_sec)
+		xo_emit("{d:/ %-9.9s}{e:expires_sec/%d}", sec2str(expire_in), expire_in);
+	else if (expire == 0)
+		xo_emit("{d:/ %-9.9s}{en:permanent/true}", "permanent");
+	else
+		xo_emit("{d:/ %-9.9s}{e:expires_sec/%d}", "expired", expire_in);
+
+	const char *lle_state = "";
+	switch (neigh->ndm_state) {
+	case NUD_INCOMPLETE:
+		lle_state = "I";
+		break;
+	case NUD_REACHABLE:
+		lle_state = "R";
+		break;
+	case NUD_STALE:
+		lle_state = "S";
+		break;
+	case NUD_DELAY:
+		lle_state = "D";
+		break;
+	case NUD_PROBE:
+		lle_state = "P";
+		break;
+	case NUD_FAILED:
+		lle_state = "F";
+		break;
+	default:
+		lle_state = "N";
+		break;
+	}
+	xo_emit(" {:neighbor-state/%s}", lle_state);
+
+	bool isrouter = neigh->ndm_flags & NTF_ROUTER;
+
+	/*
+	 * other flags. R: router, P: proxy, W: ??
+	 */
+	char flgbuf[8];
+	snprintf(flgbuf, sizeof(flgbuf), "%s%s",
+	    isrouter ? "R" : "",
+	    (neigh->ndm_flags & NTF_PROXY) ? "p" : "");
+	xo_emit(" {:nd-flags/%s}", flgbuf);
+
+	if (neigh->nda_probes != 0)
+		xo_emit("{u:/ %d}", neigh->nda_probes);
+
+	xo_emit("\n");
+	xo_close_instance("neighbor-cache");
+}
+
+int
+print_entries_nl(uint32_t ifindex, struct sockaddr_in6 *addr, bool cflag)
+{
+	struct snl_state ss_req = {}, ss_cmd = {};
+	struct snl_parsed_link_simple link = {};
+	struct snl_writer nw;
+
+	nl_init_socket(&ss_req);
+	snl_init_writer(&ss_req, &nw);
+
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETNEIGH);
+	struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg);
+	if (ndmsg != NULL) {
+		ndmsg->ndm_family = AF_INET6;
+		ndmsg->ndm_ifindex = ifindex;
+	}
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(&ss_req, hdr)) {
+		snl_free(&ss_req);
+		return (0);
+	}
+
+	uint32_t nlmsg_seq = hdr->nlmsg_seq;
+	struct snl_errmsg_data e = {};
+	int count = 0;
+	nl_init_socket(&ss_cmd);
+
+	/* Print header */
+	if (!opts.tflag && !cflag) {
+		char xobuf[200];
+		snprintf(xobuf, sizeof(xobuf),
+		    "{T:/%%-%d.%ds} {T:/%%-%d.%ds} {T:/%%%d.%ds} {T:/%%-9.9s} {T:%%1s} {T:%%5s}\n",
+		    W_ADDR, W_ADDR, W_LL, W_LL, W_IF, W_IF);
+		xo_emit(xobuf, "Neighbor", "Linklayer Address", "Netif", "Expire", "S", "Flags");
+	}
+	xo_open_list("neighbor-cache");
+
+	while ((hdr = snl_read_reply_multi(&ss_req, nlmsg_seq, &e)) != NULL) {
+		struct snl_parsed_neigh neigh = {};
+
+		if (!snl_parse_nlmsg(&ss_req, hdr, &snl_rtm_neigh_parser, &neigh))
+			continue;
+
+		if (neigh.nda_ifindex != link.ifi_index) {
+			snl_clear_lb(&ss_cmd);
+			memset(&link, 0, sizeof(link));
+			if (!get_link_info(&ss_cmd, neigh.nda_ifindex, &link))
+				continue;
+		}
+
+		/* TODO: embed LL in the parser */
+		struct sockaddr_in6 *dst = (struct sockaddr_in6 *)neigh.nda_dst;
+		if (IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr))
+			dst->sin6_scope_id = neigh.nda_ifindex;
+
+		if (addr != NULL) {
+			if (IN6_ARE_ADDR_EQUAL(&addr->sin6_addr,
+			    &dst->sin6_addr) == 0 ||
+			    addr->sin6_scope_id != dst->sin6_scope_id)
+				continue;
+		}
+
+		print_entry(&neigh, &link);
+		if (cflag) {
+			char dst_str[INET6_ADDRSTRLEN];
+
+			inet_ntop(AF_INET6, &dst->sin6_addr, dst_str, sizeof(dst_str));
+			delete_nl(neigh.nda_ifindex, dst_str);
+		}
+		count++;
+		snl_clear_lb(&ss_req);
+	}
+	xo_close_list("neighbor-cache");
+
+	snl_free(&ss_req);
+	snl_free(&ss_cmd);
+
+	return (count);
+}
+
+int
+delete_nl(uint32_t ifindex, char *host)
+{
+	struct snl_state ss = {};
+	struct snl_writer nw;
+	struct sockaddr_in6 dst;
+
+	int gai_error = getaddr(host, &dst);
+	if (gai_error) {
+		xo_warnx("%s: %s", host, gai_strerror(gai_error));
+		return 1;
+	}
+
+	nl_init_socket(&ss);
+
+	ifindex = fix_ifindex(&ss, ifindex, &dst);
+	if (ifindex == 0) {
+		xo_warnx("delete: cannot locate %s", host);
+		snl_free(&ss);
+		return (0);
+	}
+
+	snl_init_writer(&ss, &nw);
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_DELNEIGH);
+	struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg);
+	if (ndmsg != NULL) {
+		ndmsg->ndm_family = AF_INET6;
+		ndmsg->ndm_ifindex = ifindex;
+	}
+	snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)&dst);
+
+	if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) {
+		snl_free(&ss);
+		return (1);
+	}
+
+	struct snl_errmsg_data e = {};
+	snl_read_reply_code(&ss, hdr->nlmsg_seq, &e);
+	if (e.error != 0) {
+		if (e.error_str != NULL)
+			xo_warnx("delete %s: %s (%s)", host, strerror(e.error), e.error_str);
+		else
+			xo_warnx("delete %s: %s", host, strerror(e.error));
+	} else {
+		char host_buf[NI_MAXHOST];
+		char ifix_buf[IFNAMSIZ];
+
+		getnameinfo((struct sockaddr *)&dst,
*** 73 LINES SKIPPED ***