[PATCH 1/1] networking: add 'ip neigh' command

Curt Brune curt at cumulusnetworks.com
Fri Oct 9 15:22:29 UTC 2015


This patch ports the 'ip neigh' command, originally written by Alexey
Kuznetsov, <kuznet at ms2.inr.ac.ru>, to busybox.

The base of the port is the version of iproute that shipped with
Debian Squeeze, taken from:

  http://http.debian.net/debian/pool/main/i/iproute/iproute_20100519.orig.tar.gz

This patch has actively been used by the Open Network Install
Environment (ONIE) project for over 3 years without incident.

Signed-off-by: Curt Brune <curt at cumulusnetworks.com>
---
 networking/Config.src             |  13 ++
 networking/ip.c                   |  19 +-
 networking/libiproute/Kbuild.src  |   8 +
 networking/libiproute/ip_common.h |   2 +-
 networking/libiproute/ipneigh.c   | 393 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 433 insertions(+), 2 deletions(-)
 create mode 100644 networking/libiproute/ipneigh.c

diff --git a/networking/Config.src b/networking/Config.src
index 43ccbf3..8c7417f 100644
--- a/networking/Config.src
+++ b/networking/Config.src
@@ -552,10 +552,17 @@ config FEATURE_IP_RULE
 	default y
 	depends on IP
 	help
 	  Add support for rule commands to "ip".
 
+config FEATURE_IP_NEIGH
+	bool "ip neighbor"
+	default y
+	depends on IP
+	help
+	  Add support for neighbor commands to "ip".
+
 config FEATURE_IP_SHORT_FORMS
 	bool "Support short forms of ip commands"
 	default y
 	depends on IP
 	help
@@ -563,10 +570,11 @@ config FEATURE_IP_SHORT_FORMS
 	  ip addr   -> ipaddr
 	  ip link   -> iplink
 	  ip route  -> iproute
 	  ip tunnel -> iptunnel
 	  ip rule   -> iprule
+	  ip neigh  -> ipneigh
 
 	  Say N unless you desparately need the short form of the ip
 	  object commands.
 
 config FEATURE_IP_RARE_PROTOCOLS
@@ -602,10 +610,15 @@ config IPTUNNEL
 config IPRULE
 	bool
 	default y
 	depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE
 
+config IPNEIGH
+	bool
+	default y
+	depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_NEIGH
+
 config IPCALC
 	bool "ipcalc"
 	default y
 	help
 	  ipcalc takes an IP address and netmask and calculates the
diff --git a/networking/ip.c b/networking/ip.c
index d35345c..ddfe74e 100644
--- a/networking/ip.c
+++ b/networking/ip.c
@@ -14,19 +14,21 @@
 //usage:       "[OPTIONS] {"
 //usage:	IF_FEATURE_IP_ADDRESS("address | ")
 //usage:	IF_FEATURE_IP_ROUTE("route | ")
 //usage:	IF_FEATURE_IP_LINK("link | ")
 //usage:	IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage:	IF_FEATURE_IP_NEIGH("neigh | ")
 //usage:	IF_FEATURE_IP_RULE("rule")
 //usage:       "} {COMMAND}"
 //usage:#define ip_full_usage "\n\n"
 //usage:       "ip [OPTIONS] OBJECT {COMMAND}\n"
 //usage:       "where OBJECT := {"
 //usage:	IF_FEATURE_IP_ADDRESS("address | ")
 //usage:	IF_FEATURE_IP_ROUTE("route | ")
 //usage:	IF_FEATURE_IP_LINK("link | ")
 //usage:	IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage:	IF_FEATURE_IP_NEIGH("neigh | ")
 //usage:	IF_FEATURE_IP_RULE("rule")
 //usage:       "}\n"
 //usage:       "OPTIONS := { -f[amily] { inet | inet6 | link } | -o[neline] }"
 //usage:
 //usage:#define ipaddr_trivial_usage
@@ -78,21 +80,27 @@
 //usage:#define iptunnel_full_usage "\n\n"
 //usage:       "iptunnel { add | change | del | show } [NAME]\n"
 //usage:       "	[mode { ipip | gre | sit }] [remote ADDR] [local ADDR]\n"
 //usage:       "	[[i|o]seq] [[i|o]key KEY] [[i|o]csum]\n"
 //usage:       "	[ttl TTL] [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]"
+//usage:
+//usage:#define ipneigh_trivial_usage
+//usage:       "{ show | flush} [ to PREFIX ] [ dev DEV ] [ nud STATE ]"
+//usage:#define ipneigh_full_usage "\n\n"
+//usage:       "ipneigh { show | flush} [ to PREFIX ] [ dev DEV ] [ nud STATE ]"
 
 #include "libbb.h"
 
 #include "libiproute/utils.h"
 #include "libiproute/ip_common.h"
 
 #if ENABLE_FEATURE_IP_ADDRESS \
  || ENABLE_FEATURE_IP_ROUTE \
  || ENABLE_FEATURE_IP_LINK \
  || ENABLE_FEATURE_IP_TUNNEL \
- || ENABLE_FEATURE_IP_RULE
+ || ENABLE_FEATURE_IP_RULE \
+ || ENABLE_FEATURE_IP_NEIGH
 
 static int FAST_FUNC ip_print_help(char **argv UNUSED_PARAM)
 {
 	bb_show_usage();
 }
@@ -138,10 +146,17 @@ int iptunnel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int iptunnel_main(int argc UNUSED_PARAM, char **argv)
 {
 	return ip_do(do_iptunnel, argv);
 }
 #endif
+#if ENABLE_FEATURE_IP_NEIGH
+int ipneigh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipneigh_main(int argc UNUSED_PARAM, char **argv)
+{
+	return ip_do(do_ipneigh, argv);
+}
+#endif
 
 
 int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int ip_main(int argc UNUSED_PARAM, char **argv)
 {
@@ -151,20 +166,22 @@ int ip_main(int argc UNUSED_PARAM, char **argv)
 		IF_FEATURE_IP_ROUTE("r\0")
 		IF_FEATURE_IP_LINK("link\0")
 		IF_FEATURE_IP_TUNNEL("tunnel\0")
 		IF_FEATURE_IP_TUNNEL("tunl\0")
 		IF_FEATURE_IP_RULE("rule\0")
+		IF_FEATURE_IP_NEIGH("neigh\0")
 		;
 	static const ip_func_ptr_t ip_func_ptrs[] = {
 		ip_print_help,
 		IF_FEATURE_IP_ADDRESS(do_ipaddr,)
 		IF_FEATURE_IP_ROUTE(do_iproute,)
 		IF_FEATURE_IP_ROUTE(do_iproute,)
 		IF_FEATURE_IP_LINK(do_iplink,)
 		IF_FEATURE_IP_TUNNEL(do_iptunnel,)
 		IF_FEATURE_IP_TUNNEL(do_iptunnel,)
 		IF_FEATURE_IP_RULE(do_iprule,)
+		IF_FEATURE_IP_NEIGH(do_ipneigh,)
 	};
 	ip_func_ptr_t ip_func;
 	int key;
 
 	argv = ip_parse_common_args(argv + 1);
diff --git a/networking/libiproute/Kbuild.src b/networking/libiproute/Kbuild.src
index 7c78f3c..c20e2fe 100644
--- a/networking/libiproute/Kbuild.src
+++ b/networking/libiproute/Kbuild.src
@@ -62,5 +62,13 @@ lib-$(CONFIG_FEATURE_IP_TUNNEL) += \
 lib-$(CONFIG_FEATURE_IP_RULE) += \
 	ip_parse_common_args.o \
 	iprule.o \
 	rt_names.o \
 	utils.o
+
+lib-$(CONFIG_FEATURE_IP_NEIGH) += \
+	ip_parse_common_args.o \
+	ipneigh.o \
+	libnetlink.o \
+	ll_map.o \
+	rt_names.o \
+	utils.o
diff --git a/networking/libiproute/ip_common.h b/networking/libiproute/ip_common.h
index 30c7e59..40171be 100644
--- a/networking/libiproute/ip_common.h
+++ b/networking/libiproute/ip_common.h
@@ -22,11 +22,11 @@ int FAST_FUNC ipaddr_list_or_flush(char **argv, int flush);
 //void FAST_FUNC ipneigh_reset_filter(void);
 
 int FAST_FUNC do_ipaddr(char **argv);
 int FAST_FUNC do_iproute(char **argv);
 int FAST_FUNC do_iprule(char **argv);
-//int FAST_FUNC do_ipneigh(char **argv);
+int FAST_FUNC do_ipneigh(char **argv);
 int FAST_FUNC do_iptunnel(char **argv);
 int FAST_FUNC do_iplink(char **argv);
 //int FAST_FUNC do_ipmonitor(char **argv);
 //int FAST_FUNC do_multiaddr(char **argv);
 //int FAST_FUNC do_multiroute(char **argv);
diff --git a/networking/libiproute/ipneigh.c b/networking/libiproute/ipneigh.c
new file mode 100644
index 0000000..68b129d
--- /dev/null
+++ b/networking/libiproute/ipneigh.c
@@ -0,0 +1,393 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet at ms2.inr.ac.ru>
+ *
+ * Ported to Busybox by:  Curt Brune <curt at cumulusnetworks.com>
+ *
+ */
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+#include <linux/neighbour.h>
+#include <net/if_arp.h>
+
+static int xshow_stats = 3;
+
+static inline __u32 rta_getattr_u32(const struct rtattr *rta)
+{
+	return *(__u32 *)RTA_DATA(rta);
+}
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+
+struct filter_t {
+	int family;
+	int index;
+	int state;
+	int unused_only;
+	inet_prefix pfx;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	struct rtnl_handle *rth;
+} FIX_ALIASING;
+typedef struct filter_t filter_t;
+
+#define G_filter (*(filter_t*)&bb_common_bufsiz1)
+
+static int flush_update(void)
+{
+	if (rtnl_send(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) {
+		bb_perror_msg("can't send flush request");
+		return -1;
+	}
+	G_filter.flushp = 0;
+	return 0;
+}
+
+static unsigned get_user_hz(void)
+{
+	static unsigned hz_internal;
+	FILE *fp;
+
+	if (hz_internal)
+		return hz_internal;
+
+	fp = fopen_for_read("/proc/net/psched");
+	if (fp) {
+		unsigned nom, denom;
+
+		if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
+			if (nom == 1000000)
+				hz_internal = denom;
+		fclose(fp);
+	}
+	if (!hz_internal)
+		hz_internal = sysconf(_SC_CLK_TCK);
+	return hz_internal;
+}
+
+static int nud_state_a2n(unsigned *state, char *arg)
+{
+	static const char keywords[] ALIGN1 =
+		/* "ip neigh show/flush" parameters: */
+		"permanent\0" "reachable\0"   "noarp\0"  "none\0"
+		"stale\0"     "incomplete\0"  "delay\0"  "probe\0"
+		"failed\0"
+		;
+	enum {
+		KW_permanent, KW_reachable, KW_noarp, KW_none,
+		KW_stale, KW_incomplete, KW_delay, KW_probe,
+		KW_failed,
+	};
+	int id = index_in_substrings(keywords, arg);
+
+	if (id == KW_permanent)
+		*state = NUD_PERMANENT;
+	else if (id == KW_reachable)
+		*state = NUD_REACHABLE;
+	else if (id == KW_noarp)
+		*state = NUD_NOARP;
+	else if (id == KW_none)
+		*state = NUD_NONE;
+	else if (id == KW_stale)
+		*state = NUD_STALE;
+	else if (id == KW_incomplete)
+		*state = NUD_INCOMPLETE;
+	else if (id == KW_delay)
+		*state = NUD_DELAY;
+	else if (id == KW_probe)
+		*state = NUD_PROBE;
+	else if (id == KW_failed)
+		*state = NUD_FAILED;
+	else {
+		return -1;
+	}
+	return 0;
+}
+
+#ifndef NDA_RTA
+#define NDA_RTA(r) \
+	((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#endif
+
+
+static int FAST_FUNC print_neigh(const struct sockaddr_nl *who UNUSED_PARAM,
+				 struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+	struct ndmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[NDA_MAX+1];
+	char abuf[256];
+
+	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+		bb_error_msg_and_die("Not RTM_NEWNEIGH: %08x %08x %08x\n",
+				     n->nlmsg_len, n->nlmsg_type,
+				     n->nlmsg_flags);
+	}
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		bb_error_msg_and_die("BUG: wrong nlmsg len %d\n", len);
+	}
+
+	if (G_filter.flushb && n->nlmsg_type != RTM_NEWNEIGH)
+		return 0;
+
+	if (G_filter.family && G_filter.family != r->ndm_family)
+		return 0;
+	if (G_filter.index && G_filter.index != r->ndm_ifindex)
+		return 0;
+	if (!(G_filter.state&r->ndm_state) &&
+	    !(r->ndm_flags & NTF_PROXY) &&
+	    (r->ndm_state || !(G_filter.state&0x100)) &&
+	    (r->ndm_family != AF_DECnet))
+		return 0;
+
+	parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (tb[NDA_DST]) {
+		if (G_filter.pfx.family) {
+			inet_prefix dst;
+			memset(&dst, 0, sizeof(dst));
+			dst.family = r->ndm_family;
+			memcpy(&dst.data, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST]));
+			if (inet_addr_match(&dst, &G_filter.pfx, G_filter.pfx.bitlen))
+				return 0;
+		}
+	}
+	if (G_filter.unused_only && tb[NDA_CACHEINFO]) {
+		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+		if (ci->ndm_refcnt)
+			return 0;
+	}
+
+	if (G_filter.flushb) {
+		struct nlmsghdr *fn;
+		if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
+		memcpy(fn, n, n->nlmsg_len);
+		fn->nlmsg_type = RTM_DELNEIGH;
+		fn->nlmsg_flags = NLM_F_REQUEST;
+		fn->nlmsg_seq = ++(G_filter.rth->seq);
+		G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb;
+		G_filter.flushed++;
+		if (xshow_stats < 2)
+			return 0;
+	}
+
+	if (tb[NDA_DST]) {
+		printf("%s ",
+		       format_host(r->ndm_family,
+				   RTA_PAYLOAD(tb[NDA_DST]),
+				   RTA_DATA(tb[NDA_DST]),
+				   abuf, sizeof(abuf)));
+	}
+	if (!G_filter.index && r->ndm_ifindex)
+		printf("dev %s ", ll_index_to_name(r->ndm_ifindex));
+	if (tb[NDA_LLADDR]) {
+		SPRINT_BUF(b1);
+		printf("lladdr %s", ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+						RTA_PAYLOAD(tb[NDA_LLADDR]),
+						ARPHRD_ETHER,
+						b1, sizeof(b1)));
+	}
+	if (r->ndm_flags & NTF_ROUTER) {
+		printf(" router");
+	}
+	if (r->ndm_flags & NTF_PROXY) {
+		printf(" proxy");
+	}
+	if (tb[NDA_CACHEINFO] && xshow_stats) {
+		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+		int hz = get_user_hz();
+
+		if (ci->ndm_refcnt)
+			printf(" ref %d", ci->ndm_refcnt);
+		printf(" used %d/%d/%d", ci->ndm_used/hz,
+		       ci->ndm_confirmed/hz, ci->ndm_updated/hz);
+	}
+
+	if (tb[NDA_PROBES] && xshow_stats) {
+		__u32 p = rta_getattr_u32(tb[NDA_PROBES]);
+		printf(" probes %u", p);
+	}
+
+	if (r->ndm_state) {
+		int nud = r->ndm_state;
+		printf(" ");
+
+#define PRINT_FLAG(f) if (nud & NUD_##f) { \
+	nud &= ~NUD_##f; printf( #f "%s", nud ? "," : ""); }
+		PRINT_FLAG(INCOMPLETE);
+		PRINT_FLAG(REACHABLE);
+		PRINT_FLAG(STALE);
+		PRINT_FLAG(DELAY);
+		PRINT_FLAG(PROBE);
+		PRINT_FLAG(FAILED);
+		PRINT_FLAG(NOARP);
+		PRINT_FLAG(PERMANENT);
+#undef PRINT_FLAG
+	}
+	printf("\n");
+
+	return 0;
+}
+
+static void ipneigh_reset_filter(void)
+{
+	memset(&G_filter, 0, sizeof(G_filter));
+	G_filter.state = ~0;
+}
+
+#define MAX_ROUNDS	10
+/* Return value becomes exitcode. It's okay to not return at all */
+static int FAST_FUNC ipneigh_list_or_flush(char **argv, int flush)
+{
+	static const char keywords[] ALIGN1 =
+		/* "ip neigh show/flush" parameters: */
+		"to\0" "dev\0"   "nud\0";
+	enum {
+		KW_to, KW_dev, KW_nud,
+	};
+	struct rtnl_handle rth;
+	struct ndmsg ndm = { 0 };
+	char *filter_dev = NULL;
+	int state_given = 0;
+	int arg;
+
+	ipneigh_reset_filter();
+
+	if (flush && !*argv)
+		bb_error_msg_and_die(bb_msg_requires_arg, "\"ip neigh flush\"");
+
+	if (!G_filter.family)
+		G_filter.family = preferred_family;
+
+	G_filter.state = (flush) ?
+		~(NUD_PERMANENT|NUD_NOARP) : 0xFF & ~NUD_NOARP;
+
+	while (*argv) {
+		arg = index_in_substrings(keywords, *argv);
+		if (arg == KW_dev) {
+			NEXT_ARG();
+			filter_dev = *argv;
+		} else if (arg == KW_nud) {
+			unsigned state;
+			NEXT_ARG();
+			if (!state_given) {
+				state_given = 1;
+				G_filter.state = 0;
+			}
+			if (nud_state_a2n(&state, *argv)) {
+				if (strcmp(*argv, "all") != 0) {
+					bb_error_msg_and_die(bb_msg_invalid_arg,
+							     *argv, "nud state is bad");
+				}
+				state = ~0;
+				if (flush)
+					state &= ~NUD_NOARP;
+			}
+			if (state == 0)
+				state = 0x100;
+			G_filter.state |= state;
+		} else {
+			if (arg == KW_to) {
+				NEXT_ARG();
+			}
+			get_prefix(&G_filter.pfx, *argv, G_filter.family);
+			if (G_filter.family == AF_UNSPEC)
+				G_filter.family = G_filter.pfx.family;
+		}
+		argv++;
+	}
+
+	xrtnl_open(&rth);
+	ll_init_map(&rth);
+
+	if (filter_dev)  {
+		if ((G_filter.index = xll_name_to_index(filter_dev)) == 0) {
+			bb_error_msg_and_die(bb_msg_invalid_arg,
+					     filter_dev, "Cannot find device");
+		}
+	}
+
+	if (flush) {
+		int round = 0;
+		char flushb[4096-512];
+		G_filter.flushb = flushb;
+		G_filter.flushp = 0;
+		G_filter.flushe = sizeof(flushb);
+		G_filter.state &= ~NUD_FAILED;
+		G_filter.rth = &rth;
+
+		while (round < MAX_ROUNDS) {
+			if (xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETNEIGH) < 0) {
+				bb_perror_msg_and_die("Cannot send dump request");
+			}
+			G_filter.flushed = 0;
+			if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) {
+				bb_perror_msg_and_die("Flush terminated");
+			}
+			if (G_filter.flushed == 0) {
+				if (round == 0)
+					printf("Nothing to flush.\n");
+				else
+					printf("*** Flush is complete after %d round%s ***\n", round, round>1?"s":"");
+				return 0;
+			}
+			round++;
+			if (flush_update() < 0)
+				exit(1);
+			printf("\n*** Round %d, deleting %d entries ***\n", round, G_filter.flushed);
+		}
+		bb_error_msg_and_die("*** Flush not complete bailing out after %d rounds\n", MAX_ROUNDS);
+	}
+
+	ndm.ndm_family = G_filter.family;
+
+	if (rtnl_dump_request(&rth, RTM_GETNEIGH, &ndm, sizeof(struct ndmsg)) < 0) {
+		bb_perror_msg_and_die("Cannot send dump request");
+	}
+
+	if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) {
+		bb_error_msg_and_die("Dump terminated\n");
+	}
+
+	return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int FAST_FUNC do_ipneigh(char **argv)
+{
+	static const char ip_neigh_commands[] ALIGN1 =
+		/*0-1*/	"show\0"  "flush\0";
+	int command_num;
+
+	if (!*argv)
+		return ipneigh_list_or_flush(argv, 0);
+
+	/* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */
+	/* It probably means that it is using "first match" rule */
+	command_num = index_in_substrings(ip_neigh_commands, *argv);
+
+	switch (command_num) {
+		case 0: /* show */
+			return ipneigh_list_or_flush(argv + 1, 0);
+		case 1: /* flush */
+			return ipneigh_list_or_flush(argv + 1, 1);
+		default:
+			bb_error_msg_and_die("unknown command %s", *argv);
+	}
+
+	return 1;
+}
-- 
1.9.1



More information about the busybox mailing list