Module Name:    src
Committed By:   ozaki-r
Date:           Wed Jun 28 08:17:50 UTC 2017

Modified Files:
        src/tests/net/arp: t_arp.sh
        src/tests/net/ndp: t_ndp.sh
        src/usr.sbin/arp: arp.c
        src/usr.sbin/ndp: ndp.c

Log Message:
Enable to remove multiple ARP/NDP entries for one destination

The kernel can have multiple ARP/NDP entries which have an indentical
destination on different interfaces. This is normal and can be
reproduce easily by ping -I or ping6 -S. We should be able to remove
such entries.

arp -d <ip> and ndp -d <ip> are changed to fetch all ARP/NDP entries
and remove matched entries. So we can remove multiple entries
described above. This fetch all and selective removal behavior is
the same as arp <ip> and ndp <ip>; they also do fetch all entries
and show only matched entries.

Related to PR 51179


To generate a diff of this commit:
cvs rdiff -u -r1.32 -r1.33 src/tests/net/arp/t_arp.sh
cvs rdiff -u -r1.28 -r1.29 src/tests/net/ndp/t_ndp.sh
cvs rdiff -u -r1.57 -r1.58 src/usr.sbin/arp/arp.c
cvs rdiff -u -r1.49 -r1.50 src/usr.sbin/ndp/ndp.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/tests/net/arp/t_arp.sh
diff -u src/tests/net/arp/t_arp.sh:1.32 src/tests/net/arp/t_arp.sh:1.33
--- src/tests/net/arp/t_arp.sh:1.32	Wed Jun 28 04:14:53 2017
+++ src/tests/net/arp/t_arp.sh	Wed Jun 28 08:17:50 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: t_arp.sh,v 1.32 2017/06/28 04:14:53 ozaki-r Exp $
+#	$NetBSD: t_arp.sh,v 1.33 2017/06/28 08:17:50 ozaki-r Exp $
 #
 # Copyright (c) 2015 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -654,7 +654,7 @@ arp_rtm_body()
 	str="$IP4DST link#2"
 	atf_check -s exit:0 -o match:"$str" cat $file
 
-	# Test arp -d and resulting routing messages (RTM_GET and RTM_DELETE)
+	# Test arp -d and resulting routing messages (RTM_DELETE)
 	rump.route -n monitor -c 2 > $file &
 	pid=$?
 	sleep 1
@@ -662,13 +662,7 @@ arp_rtm_body()
 	wait $pid
 	$DEBUG && cat $file
 
-	str="RTM_GET.+<UP,DONE,LLINFO>"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="<DST,GATEWAY>"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="$IP4DST $macaddr_dst"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="RTM_DELETE.+<DONE,LLINFO>"
+	str="RTM_DELETE.+<HOST,DONE,LLINFO,CLONED>"
 	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_DELETE $file
 	str="<DST,GATEWAY>"
 	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_DELETE $file
@@ -803,6 +797,85 @@ arp_purge_on_ifdown_cleanup()
 	cleanup
 }
 
+atf_test_case arp_stray_entries cleanup
+arp_stray_entries_head()
+{
+
+	atf_set "descr" "Tests if ARP entries are removed on route change"
+	atf_set "require.progs" "rump_server"
+}
+
+arp_stray_entries_body()
+{
+
+	rump_server_start $SOCKSRC
+	rump_server_start $SOCKDST
+
+	setup_dst_server
+	setup_src_server
+
+	rump_server_add_iface $SOCKSRC shmif1 bus1
+
+	export RUMP_SERVER=$SOCKSRC
+	atf_check -s exit:0 rump.ifconfig shmif1 inet $IP4SRC2/24
+	atf_check -s exit:0 rump.ifconfig -w 10
+
+	$DEBUG && rump.netstat -nr -f inet
+	atf_check -s exit:0 -o ignore rump.ping -n -w 1 -c 1 $IP4DST
+	$DEBUG && rump.arp -na
+	atf_check -s exit:0 -o match:'shmif0' rump.arp -n $IP4DST
+	atf_check -s exit:0 -o not-match:'shmif1' rump.arp -n $IP4DST
+
+	# Clean up
+	atf_check -s exit:0 -o ignore rump.arp -da
+	atf_check -s not-exit:0 -e match:'no entry' rump.arp -n $IP4DST
+
+	# ping from a different source address
+	atf_check -s exit:0 -o ignore \
+	    rump.ping -n -w 1 -c 1 -I $IP4SRC2 $IP4DST
+	$DEBUG && rump.arp -na
+	atf_check -s exit:0 -o match:'shmif0' rump.arp -n $IP4DST
+	# ARP reply goes back via shmif1, so a cache is created on shmif1
+	atf_check -s exit:0 -o match:'shmif1' rump.arp -n $IP4DST
+
+	# Clean up by arp -da
+	atf_check -s exit:0 -o ignore rump.arp -da
+	atf_check -s not-exit:0 -e match:'no entry' rump.arp -n $IP4DST
+
+	# ping from a different source address again
+	atf_check -s exit:0 -o ignore \
+	    rump.ping -n -w 1 -c 1 -I $IP4SRC2 $IP4DST
+	atf_check -s exit:0 -o match:'shmif0' rump.arp -n $IP4DST
+	# ARP reply doen't come
+	atf_check -s exit:0 -o not-match:'shmif1' rump.arp -n $IP4DST
+
+	# Cleanup caches on the destination
+	export RUMP_SERVER=$SOCKDST
+	atf_check -s exit:0 -o ignore rump.arp -da
+	export RUMP_SERVER=$SOCKSRC
+
+	# ping from a different source address again
+	atf_check -s exit:0 -o ignore \
+	    rump.ping -n -w 1 -c 1 -I $IP4SRC2 $IP4DST
+	atf_check -s exit:0 -o match:'shmif0' rump.arp -n $IP4DST
+	# ARP reply goes back via shmif1
+	atf_check -s exit:0 -o match:'shmif1' rump.arp -n $IP4DST
+
+	# Clean up by arp -d <ip>
+	atf_check -s exit:0 -o ignore rump.arp -d $IP4DST
+	# Both entries should be deleted
+	atf_check -s not-exit:0 -e match:'no entry' rump.arp -n $IP4DST
+
+	rump_server_destroy_ifaces
+}
+
+arp_stray_entries_cleanup()
+{
+
+	$DEBUG && dump
+	cleanup
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case arp_cache_expiration_5s
@@ -818,4 +891,5 @@ atf_init_test_cases()
 	atf_add_test_case arp_purge_on_route_change
 	atf_add_test_case arp_purge_on_route_delete
 	atf_add_test_case arp_purge_on_ifdown
+	atf_add_test_case arp_stray_entries
 }

Index: src/tests/net/ndp/t_ndp.sh
diff -u src/tests/net/ndp/t_ndp.sh:1.28 src/tests/net/ndp/t_ndp.sh:1.29
--- src/tests/net/ndp/t_ndp.sh:1.28	Wed Jun 28 04:14:53 2017
+++ src/tests/net/ndp/t_ndp.sh	Wed Jun 28 08:17:50 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: t_ndp.sh,v 1.28 2017/06/28 04:14:53 ozaki-r Exp $
+#	$NetBSD: t_ndp.sh,v 1.29 2017/06/28 08:17:50 ozaki-r Exp $
 #
 # Copyright (c) 2015 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -446,7 +446,7 @@ ndp_rtm_body()
 	str="$IP6DST link#2"
 	atf_check -s exit:0 -o match:"$str" cat $file
 
-	# Test ndp -d and resulting routing messages (RTM_GET and RTM_DELETE)
+	# Test ndp -d and resulting routing messages (RTM_DELETE)
 	rump.route -n monitor -c 2 > $file &
 	pid=$?
 	sleep 1
@@ -454,13 +454,7 @@ ndp_rtm_body()
 	wait $pid
 	$DEBUG && cat $file
 
-	str="RTM_GET.+<UP,DONE,LLINFO>"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="<DST,GATEWAY>"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="$IP6DST $macaddr_dst"
-	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_GET $file
-	str="RTM_DELETE.+<DONE,LLINFO>"
+	str="RTM_DELETE.+<HOST,DONE,LLINFO,CLONED>"
 	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_DELETE $file
 	str="<DST,GATEWAY>"
 	atf_check -s exit:0 -o match:"$str" grep -A 3 RTM_DELETE $file
@@ -600,6 +594,87 @@ ndp_purge_on_ifdown_cleanup()
 	cleanup
 }
 
+atf_test_case ndp_stray_entries cleanup
+ndp_stray_entries_head()
+{
+
+	atf_set "descr" "Tests if NDP entries are removed on route change"
+	atf_set "require.progs" "rump_server"
+}
+
+ndp_stray_entries_body()
+{
+
+	rump_server_start $SOCKSRC netinet6
+	rump_server_start $SOCKDST netinet6
+
+	setup_dst_server
+	setup_src_server
+
+	rump_server_add_iface $SOCKSRC shmif1 bus1
+
+	export RUMP_SERVER=$SOCKSRC
+	atf_check -s exit:0 rump.ifconfig shmif1 inet6 $IP6SRC2/64
+	atf_check -s exit:0 rump.ifconfig -w 10
+
+	$DEBUG && rump.netstat -nr -f inet6
+	atf_check -s exit:0 -o ignore rump.ping6 -n -X 1 -c 1 $IP6DST
+	$DEBUG && rump.ndp -na
+	atf_check -s exit:0 -o match:'shmif0' rump.ndp -n $IP6DST
+	atf_check -s exit:0 -o not-match:'shmif1' rump.ndp -n $IP6DST
+
+	# Clean up
+	atf_check -s exit:0 -o ignore rump.ndp -c
+	atf_check -s not-exit:0 -o ignore -e match:'no entry' rump.ndp -n $IP6DST
+
+	# ping from a different source address
+	atf_check -s exit:0 -o ignore \
+	    rump.ping6 -n -X 1 -c 1 -S $IP6SRC2 $IP6DST
+	$DEBUG && rump.ndp -na
+	atf_check -s exit:0 -o match:'shmif0' rump.ndp -n $IP6DST
+	# ARP reply goes back via shmif1, so a cache is created on shmif1
+	atf_check -s exit:0 -o match:'shmif1' rump.ndp -n $IP6DST
+
+	# Clean up by ndp -c
+	atf_check -s exit:0 -o ignore rump.ndp -c
+	atf_check -s not-exit:0 -o ignore -e match:'no entry' rump.ndp -n $IP6DST
+
+	# ping from a different source address again
+	atf_check -s exit:0 -o ignore \
+	    rump.ping6 -n -X 1 -c 1 -S $IP6SRC2 $IP6DST
+	atf_check -s exit:0 -o match:'shmif0' rump.ndp -n $IP6DST
+	# ARP reply doen't come
+	atf_check -s exit:0 -o not-match:'shmif1' rump.ndp -n $IP6DST
+
+	# Cleanup caches on the destination
+	export RUMP_SERVER=$SOCKDST
+	$DEBUG && rump.ndp -na
+	atf_check -s exit:0 -o ignore rump.ndp -c
+	$DEBUG && rump.ndp -na
+	export RUMP_SERVER=$SOCKSRC
+
+	# ping from a different source address again
+	atf_check -s exit:0 -o ignore \
+	    rump.ping6 -n -X 1 -c 1 -S $IP6SRC2 $IP6DST
+	atf_check -s exit:0 -o match:'shmif0' rump.ndp -n $IP6DST
+	# ARP reply goes back via shmif1
+	atf_check -s exit:0 -o match:'shmif1' rump.ndp -n $IP6DST
+
+	# Clean up by ndp -d <ip>
+	atf_check -s exit:0 -o ignore rump.ndp -d $IP6DST
+	# Both entries should be deleted
+	atf_check -s not-exit:0 -o ignore -e match:'no entry' rump.ndp -n $IP6DST
+
+	rump_server_destroy_ifaces
+}
+
+ndp_stray_entries_cleanup()
+{
+
+	$DEBUG && dump
+	cleanup
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case ndp_cache_expiration
@@ -611,4 +686,5 @@ atf_init_test_cases()
 	atf_add_test_case ndp_purge_on_route_change
 	atf_add_test_case ndp_purge_on_route_delete
 	atf_add_test_case ndp_purge_on_ifdown
+	atf_add_test_case ndp_stray_entries
 }

Index: src/usr.sbin/arp/arp.c
diff -u src/usr.sbin/arp/arp.c:1.57 src/usr.sbin/arp/arp.c:1.58
--- src/usr.sbin/arp/arp.c:1.57	Mon Jun 26 03:13:40 2017
+++ src/usr.sbin/arp/arp.c	Wed Jun 28 08:17:50 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: arp.c,v 1.57 2017/06/26 03:13:40 ozaki-r Exp $ */
+/*	$NetBSD: arp.c,v 1.58 2017/06/28 08:17:50 ozaki-r Exp $ */
 
 /*
  * Copyright (c) 1984, 1993
@@ -42,7 +42,7 @@ __COPYRIGHT("@(#) Copyright (c) 1984, 19
 #if 0
 static char sccsid[] = "@(#)arp.c	8.3 (Berkeley) 4/28/95";
 #else
-__RCSID("$NetBSD: arp.c,v 1.57 2017/06/26 03:13:40 ozaki-r Exp $");
+__RCSID("$NetBSD: arp.c,v 1.58 2017/06/28 08:17:50 ozaki-r Exp $");
 #endif
 #endif /* not lint */
 
@@ -79,9 +79,9 @@ __RCSID("$NetBSD: arp.c,v 1.57 2017/06/2
 #include "prog_ops.h"
 
 static int is_llinfo(const struct sockaddr_dl *, int);
-static int delete(const char *, const char *);
+static int delete_one(struct rt_msghdr *);
 static void dump(uint32_t);
-static void delete_all(void);
+static void delete(const char *, const char *);
 static void sdl_print(const struct sockaddr_dl *);
 static int getifname(u_int16_t, char *, size_t);
 static int atosdl(const char *s, struct sockaddr_dl *sdl);
@@ -90,8 +90,8 @@ static void get(const char *);
 static int getinetaddr(const char *, struct in_addr *);
 static int getsocket(void);
 static struct rt_msghdr *
-	rtmsg(const int, const int, const struct sockaddr_inarp *,
-	    const struct sockaddr_dl *);
+	rtmsg(const int, const int,  struct rt_msghdr *,
+	    const struct sockaddr_inarp *, const struct sockaddr_dl *);
 static int set(int, char **);
 static void usage(void) __dead;
 
@@ -157,11 +157,11 @@ main(int argc, char **argv)
 		break;
 	case 'd':
 		if (aflag && argc == 0)
-			delete_all();
+			delete(NULL, NULL);
 		else {
 			if (aflag || argc < 1 || argc > 2)
 				usage();
-			(void)delete(argv[0], argv[1]);
+			delete(argv[0], argv[1]);
 		}
 		break;
 	case 's':
@@ -310,7 +310,7 @@ set(int argc, char **argv)
 
 	}
 tryagain:
-	rtm = rtmsg(s, RTM_GET, &sin_m, &sdl_m);
+	rtm = rtmsg(s, RTM_GET, NULL, &sin_m, &sdl_m);
 	if (rtm == NULL) {
 		warn("%s", host);
 		return (1);
@@ -344,7 +344,7 @@ overwrite:
 	sin_m.sin_other = 0;
 	if (doing_proxy && export_only)
 		sin_m.sin_other = SIN_PROXY;
-	rtm = rtmsg(s, RTM_ADD, &sin_m, &sdl_m);
+	rtm = rtmsg(s, RTM_ADD, NULL, &sin_m, &sdl_m);
 	if (vflag)
 		(void)printf("%s (%s) added\n", host, eaddr);
 	return (rtm == NULL) ? 1 : 0;
@@ -390,50 +390,21 @@ is_llinfo(const struct sockaddr_dl *sdl,
  * Delete an arp entry
  */
 int
-delete(const char *host, const char *info)
+delete_one(struct rt_msghdr *rtm)
 {
 	struct sockaddr_inarp *sina;
 	struct sockaddr_dl *sdl;
-	struct rt_msghdr *rtm;
-	struct sockaddr_inarp sin_m = blank_sin; /* struct copy */
-	struct sockaddr_dl sdl_m = blank_sdl; /* struct copy */
 	int s;
 
 	s = getsocket();
-	if (info && strncmp(info, "pro", 3) == 0)
-		sin_m.sin_other = SIN_PROXY;
-	if (getinetaddr(host, &sin_m.sin_addr) == -1)
-		return (1);
-tryagain:
-	rtm = rtmsg(s, RTM_GET, &sin_m, &sdl_m);
-	if (rtm == NULL) {
-		warn("%s", host);
-		return (1);
-	}
 	sina = (struct sockaddr_inarp *)(void *)(rtm + 1);
 	sdl = (struct sockaddr_dl *)(void *)(RT_ROUNDUP(sina->sin_len) +
 	    (char *)(void *)sina);
-	if (sina->sin_addr.s_addr == sin_m.sin_addr.s_addr &&
-	    is_llinfo(sdl, rtm->rtm_flags))
-		goto delete;
-	if (sin_m.sin_other & SIN_PROXY) {
-		warnx("delete: can't locate %s", host);
-		return (1);
-	} else {
-		sin_m.sin_other = SIN_PROXY;
-		goto tryagain;
-	}
-delete:
-	if (sdl->sdl_family != AF_LINK) {
-		(void)warnx("cannot locate %s", host);
+	if (sdl->sdl_family != AF_LINK)
 		return (1);
-	}
-	rtm = rtmsg(s, RTM_DELETE, &sin_m, sdl);
+	rtm = rtmsg(s, RTM_DELETE, rtm, sina, sdl);
 	if (rtm == NULL)
 		return (1);
-	if (vflag)
-		(void)printf("%s (%s) deleted\n", host,
-		    inet_ntoa(sin_m.sin_addr));
 	return (0);
 }
 
@@ -519,36 +490,56 @@ dump(uint32_t addr)
  * Delete the entire arp table
  */
 void
-delete_all(void)
+delete(const char *host, const char *info)
 {
 	int mib[6];
 	size_t needed;
-	char addr[sizeof("000.000.000.000\0")];
 	char *lim, *buf, *next;
 	struct rt_msghdr *rtm;
 	struct sockaddr_inarp *sina;
+	struct sockaddr_inarp sin_m = blank_sin; /* struct copy */
+
+	if (host != NULL) {
+		int ret = getinetaddr(host, &sin_m.sin_addr);
+		if (ret == -1)
+			return;
+	}
+	if (info && strncmp(info, "pro", 3) == 0)
+		sin_m.sin_other = SIN_PROXY;
 
+retry:
 	mib[0] = CTL_NET;
 	mib[1] = PF_ROUTE;
 	mib[2] = 0;
 	mib[3] = AF_INET;
 	mib[4] = NET_RT_FLAGS;
-	mib[5] = 0;
+	mib[5] = RTF_LLDATA;
 	if (prog_sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
 		err(1, "route-sysctl-estimate");
 	if (needed == 0)
 		return;
 	if ((buf = malloc(needed)) == NULL)
 		err(1, "malloc");
-	if (prog_sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
+	if (prog_sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+		free(buf);
+		if (errno == ENOBUFS)
+			goto retry;
 		err(1, "actual retrieval of routing table");
+	}
 	lim = buf + needed;
+
 	for (next = buf; next < lim; next += rtm->rtm_msglen) {
+		int ret;
 		rtm = (struct rt_msghdr *)(void *)next;
 		sina = (struct sockaddr_inarp *)(void *)(rtm + 1);
-		(void)snprintf(addr, sizeof(addr), "%s",
-		    inet_ntoa(sina->sin_addr));
-		(void)delete(addr, NULL);
+		if (host != NULL &&
+		    sina->sin_addr.s_addr != sin_m.sin_addr.s_addr)
+			continue;
+		ret = delete_one(rtm);
+		if (vflag && ret == 0) {
+			(void)printf("%s (%s) deleted\n", host,
+			    inet_ntoa(sina->sin_addr));
+		}
 	}
 	free(buf);
 }
@@ -615,11 +606,11 @@ usage(void)
 }
 
 static struct rt_msghdr *
-rtmsg(const int s, const int cmd, const struct sockaddr_inarp *sin,
-    const struct sockaddr_dl *sdl)
+rtmsg(const int s, const int cmd, struct rt_msghdr *_rtm,
+    const struct sockaddr_inarp *sin, const struct sockaddr_dl *sdl)
 {
 	static int seq;
-	struct rt_msghdr *rtm;
+	struct rt_msghdr *rtm = _rtm;
 	char *cp;
 	int l;
 	static struct {
@@ -628,16 +619,16 @@ rtmsg(const int s, const int cmd, const 
 	} m_rtmsg;
 	pid_t pid;
 
-	rtm = &m_rtmsg.m_rtm;
-	cp = m_rtmsg.m_space;
 	errno = 0;
-
-	/* XXX depends on rtm is filled by RTM_GET */
-	if (cmd == RTM_DELETE) {
-		rtm->rtm_flags |= RTF_LLDATA;
+	if (rtm != NULL) {
+		memcpy(&m_rtmsg, rtm, rtm->rtm_msglen);
+		rtm = &m_rtmsg.m_rtm;
 		goto doit;
 	}
 	(void)memset(&m_rtmsg, 0, sizeof(m_rtmsg));
+	rtm = &m_rtmsg.m_rtm;
+	cp = m_rtmsg.m_space;
+
 	rtm->rtm_flags = flags;
 	rtm->rtm_version = RTM_VERSION;
 

Index: src/usr.sbin/ndp/ndp.c
diff -u src/usr.sbin/ndp/ndp.c:1.49 src/usr.sbin/ndp/ndp.c:1.50
--- src/usr.sbin/ndp/ndp.c:1.49	Mon Jun 26 03:13:40 2017
+++ src/usr.sbin/ndp/ndp.c	Wed Jun 28 08:17:50 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: ndp.c,v 1.49 2017/06/26 03:13:40 ozaki-r Exp $	*/
+/*	$NetBSD: ndp.c,v 1.50 2017/06/28 08:17:50 ozaki-r Exp $	*/
 /*	$KAME: ndp.c,v 1.121 2005/07/13 11:30:13 keiichi Exp $	*/
 
 /*
@@ -122,13 +122,14 @@ static char ifix_buf[IFNAMSIZ];		/* if_i
 static void getsocket(void);
 static int set(int, char **);
 static void get(char *);
-static int delete(char *);
-static void dump(struct in6_addr *, int);
+static int delete(struct rt_msghdr *, char *);
+static void delete_one(char *);
+static void do_foreach(struct in6_addr *, char *, int);
 static struct in6_nbrinfo *getnbrinfo(struct in6_addr *, unsigned int, int);
 static char *ether_str(struct sockaddr_dl *);
 static int ndp_ether_aton(char *, u_char *);
 __dead static void usage(void);
-static int rtmsg(int);
+static int rtmsg(int, struct rt_msghdr *);
 static void ifinfo(char *, int, char **);
 static void rtrlist(void);
 static void plist(void);
@@ -144,6 +145,9 @@ static const char *sec2str(time_t);
 static char *ether_str(struct sockaddr_dl *);
 static void ts_print(const struct timeval *);
 
+#define NDP_F_CLEAR	1
+#define NDP_F_DELETE	2
+
 #ifdef ICMPV6CTL_ND6_DRLIST
 static const char *rtpref_str[] = {
 	"medium",		/* 00 */
@@ -223,14 +227,14 @@ main(int argc, char **argv)
 			usage();
 			/*NOTREACHED*/
 		}
-		dump(0, mode == 'c');
+		do_foreach(0, NULL, mode == 'c' ? NDP_F_CLEAR : 0);
 		break;
 	case 'd':
 		if (argc != 0) {
 			usage();
 			/*NOTREACHED*/
 		}
-		(void)delete(arg);
+		delete_one(arg);
 		break;
 	case 'I':
 #ifdef SIOCSDEFIFACE_IN6	/* XXX: check SIOCGDEFIFACE_IN6 as well? */
@@ -386,7 +390,7 @@ set(int argc, char **argv)
 			flags |= RTF_ANNOUNCE;
 		argv++;
 	}
-	if (rtmsg(RTM_GET) < 0) {
+	if (rtmsg(RTM_GET, NULL) < 0) {
 		errx(1, "RTM_GET(%s) failed", host);
 		/* NOTREACHED */
 	}
@@ -415,7 +419,7 @@ overwrite:
 	}
 	sdl_m.sdl_type = sdl->sdl_type;
 	sdl_m.sdl_index = sdl->sdl_index;
-	return (rtmsg(RTM_ADD));
+	return (rtmsg(RTM_ADD, NULL));
 }
 
 /*
@@ -437,7 +441,7 @@ get(char *host)
 		return;
 	}
 	makeaddr(mysin, res->ai_addr);
-	dump(&mysin->sin6_addr, 0);
+	do_foreach(&mysin->sin6_addr, host, 0);
 	if (found_entry == 0) {
 		(void)getnameinfo((struct sockaddr *)(void *)mysin,
 		    (socklen_t)mysin->sin6_len,
@@ -447,52 +451,44 @@ get(char *host)
 	}
 }
 
-/*
- * Delete a neighbor cache entry
- */
-static int
-delete(char *host)
+static void
+delete_one(char *host)
 {
 	struct sockaddr_in6 *mysin = &sin_m;
-	register struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
-	struct sockaddr_dl *sdl;
 	struct addrinfo hints, *res;
 	int gai_error;
 
-	getsocket();
 	sin_m = blank_sin;
-
-	bzero(&hints, sizeof(hints));
+	(void)memset(&hints, 0, sizeof(hints));
 	hints.ai_family = AF_INET6;
 	gai_error = getaddrinfo(host, NULL, &hints, &res);
 	if (gai_error) {
 		warnx("%s: %s", host, gai_strerror(gai_error));
-		return 1;
+		return;
 	}
 	makeaddr(mysin, res->ai_addr);
-	if (rtmsg(RTM_GET) < 0)
-		errx(1, "RTM_GET(%s) failed", host);
+	do_foreach(&mysin->sin6_addr, host, NDP_F_DELETE);
+}
+
+/*
+ * Delete a neighbor cache entry
+ */
+static int
+delete(struct rt_msghdr *rtm, char *host)
+{
+	struct sockaddr_in6 *mysin = &sin_m;
+	struct sockaddr_dl *sdl;
+
+	getsocket();
 	mysin = (struct sockaddr_in6 *)(void *)(rtm + 1);
 	sdl = (struct sockaddr_dl *)(void *)(RT_ROUNDUP(mysin->sin6_len) +
 	    (char *)(void *)mysin);
-	if (IN6_ARE_ADDR_EQUAL(&mysin->sin6_addr, &sin_m.sin6_addr)) {
-		if (sdl->sdl_family == AF_LINK &&
-		    !(rtm->rtm_flags & RTF_GATEWAY)) {
-			goto delete;
-		}
-		/*
-		 * IPv4 arp command retries with sin_other = SIN_PROXY here.
-		 */
-		warnx("delete: cannot delete non-NDP entry");
-		return 1;
-	}
 
-delete:
 	if (sdl->sdl_family != AF_LINK) {
 		(void)printf("cannot locate %s\n", host);
 		return (1);
 	}
-	if (rtmsg(RTM_DELETE) == 0) {
+	if (rtmsg(RTM_DELETE, rtm) == 0) {
 		struct sockaddr_in6 s6 = *mysin; /* XXX: for safety */
 
 		mysin->sin6_scope_id = 0;
@@ -512,10 +508,13 @@ delete:
 #define W_IF	6
 
 /*
- * Dump the entire neighbor cache
+ * Iterate on neighbor caches and do
+ * - dump all caches,
+ * - clear all caches (NDP_F_CLEAR) or
+ * - remove matched caches (NDP_F_DELETE)
  */
 static void
-dump(struct in6_addr *addr, int cflag)
+do_foreach(struct in6_addr *addr, char *host, int _flags)
 {
 	int mib[6];
 	size_t needed;
@@ -530,6 +529,8 @@ dump(struct in6_addr *addr, int cflag)
 	int ifwidth;
 	char flgbuf[8], *fl;
 	const char *ifname;
+	int cflag = _flags == NDP_F_CLEAR;
+	int dflag = _flags == NDP_F_DELETE;
 
 	/* Print header */
 	if (!tflag && !cflag)
@@ -543,14 +544,18 @@ again:;
 	mib[2] = 0;
 	mib[3] = AF_INET6;
 	mib[4] = NET_RT_FLAGS;
-	mib[5] = 0;
+	mib[5] = RTF_LLDATA;
 	if (prog_sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
 		err(1, "sysctl(PF_ROUTE estimate)");
 	if (needed > 0) {
 		if ((buf = malloc(needed)) == NULL)
 			err(1, "malloc");
-		if (prog_sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
+		if (prog_sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+			free(buf);
+			if (errno == ENOBUFS)
+				goto again;
 			err(1, "sysctl(PF_ROUTE, NET_RT_FLAGS)");
+		}
 		lim = buf + needed;
 	} else
 		buf = lim = NULL;
@@ -587,6 +592,10 @@ again:;
 			found_entry = 1;
 		} else if (IN6_IS_ADDR_MULTICAST(&mysin->sin6_addr))
 			continue;
+		if (dflag) {
+			(void)delete(rtm, host_buf);
+			continue;
+		}
 		if (IN6_IS_ADDR_LINKLOCAL(&mysin->sin6_addr) ||
 		    IN6_IS_ADDR_MC_LINKLOCAL(&mysin->sin6_addr)) {
 			uint16_t scopeid = mysin->sin6_scope_id;
@@ -600,8 +609,13 @@ again:;
 		    host_buf, sizeof(host_buf), NULL, 0,
 		    (nflag ? NI_NUMERICHOST : 0));
 		if (cflag) {
+			/* Restore scopeid */
+			if (IN6_IS_ADDR_LINKLOCAL(&mysin->sin6_addr) ||
+			    IN6_IS_ADDR_MC_LINKLOCAL(&mysin->sin6_addr))
+				inet6_putscopeid(mysin, INET6_IS_ADDR_LINKLOCAL|
+				    INET6_IS_ADDR_MC_LINKLOCAL);
 			if ((rtm->rtm_flags & RTF_STATIC) == 0)
-				(void)delete(host_buf);
+				(void)delete(rtm, host_buf);
 			continue;
 		}
 		(void)gettimeofday(&tim, 0);
@@ -776,19 +790,21 @@ usage(void)
 }
 
 static int
-rtmsg(int cmd)
+rtmsg(int cmd, struct rt_msghdr *_rtm)
 {
 	static int seq;
-	register struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
+	register struct rt_msghdr *rtm = _rtm;
 	register char *cp = m_rtmsg.m_space;
 	register int l;
 
 	errno = 0;
-	if (cmd == RTM_DELETE) {
-		rtm->rtm_flags |= RTF_LLDATA;
+	if (rtm != NULL) {
+		memcpy(&m_rtmsg, rtm, rtm->rtm_msglen);
+		rtm = &m_rtmsg.m_rtm;
 		goto doit;
 	}
 	(void)memset(&m_rtmsg, 0, sizeof(m_rtmsg));
+	rtm = &m_rtmsg.m_rtm;
 	rtm->rtm_flags = flags;
 	rtm->rtm_version = RTM_VERSION;
 

Reply via email to