diff --git a/networking/Config.src b/networking/Config.src index 43ccbf385..8c7417f86 100644 --- a/networking/Config.src +++ b/networking/Config.src @@ -554,6 +554,13 @@ config FEATURE_IP_RULE 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 @@ -565,6 +572,7 @@ config FEATURE_IP_SHORT_FORMS 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. @@ -604,6 +612,11 @@ config IPRULE 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 diff --git a/networking/ip.c b/networking/ip.c index d35345c36..ddfe74e9c 100644 --- a/networking/ip.c +++ b/networking/ip.c @@ -16,6 +16,7 @@ //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" @@ -25,6 +26,7 @@ //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] }" @@ -80,6 +82,11 @@ //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" @@ -90,7 +97,8 @@ || 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) { @@ -140,6 +148,13 @@ 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; @@ -153,6 +168,7 @@ int ip_main(int argc UNUSED_PARAM, char **argv) 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, @@ -163,6 +179,7 @@ int ip_main(int argc UNUSED_PARAM, char **argv) 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; diff --git a/networking/libiproute/Kbuild.src b/networking/libiproute/Kbuild.src index 7c78f3c6a..c20e2fee8 100644 --- a/networking/libiproute/Kbuild.src +++ b/networking/libiproute/Kbuild.src @@ -64,3 +64,11 @@ lib-$(CONFIG_FEATURE_IP_RULE) += \ 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 30c7e595b..40171bed9 100644 --- a/networking/libiproute/ip_common.h +++ b/networking/libiproute/ip_common.h @@ -24,7 +24,7 @@ int FAST_FUNC ipaddr_list_or_flush(char **argv, int flush); 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); diff --git a/networking/libiproute/ipneigh.c b/networking/libiproute/ipneigh.c new file mode 100644 index 000000000..03a15d845 --- /dev/null +++ b/networking/libiproute/ipneigh.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + * + * Authors: Alexey Kuznetsov, + * + * Ported to Busybox by: Curt Brune + */ + +#include "ip_common.h" /* #include "libbb.h" is inside */ +#include "rt_names.h" +#include "utils.h" +#include +#include + +//static int xshow_stats = 3; +enum { xshow_stats = 3 }; + +static inline uint32_t rta_getattr_u32(const struct rtattr *rta) +{ + return *(uint32_t *)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 nud_state_a2n(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" + ; + static uint8_t nuds[] = { + NUD_PERMANENT,NUD_REACHABLE, NUD_NOARP,NUD_NONE, + NUD_STALE, NUD_INCOMPLETE,NUD_DELAY,NUD_PROBE, + NUD_FAILED + }; + int id; + + BUILD_BUG_ON( + (NUD_PERMANENT|NUD_REACHABLE| NUD_NOARP|NUD_NONE| + NUD_STALE| NUD_INCOMPLETE|NUD_DELAY|NUD_PROBE| + NUD_FAILED) > 0xff + ); + + id = index_in_substrings(keywords, arg); + if (id < 0) + bb_error_msg_and_die(bb_msg_invalid_arg, arg, "nud state"); + return nuds[id]; +} + +#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->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", 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_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) { + uint32_t p = rta_getattr_u32(tb[NDA_PROBES]); + printf(" probes %u", p); + } + + /*if (r->ndm_state)*/ { + int nud = r->ndm_state; + char c = ' '; +#define PRINT_FLAG(f) \ + if (nud & NUD_##f) { \ + printf("%c"#f, c); \ + c = ','; \ + } + 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 + } + bb_putchar('\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 (strcmp(*argv, "all") == 0) { + state = ~0; + if (flush) + state &= ~NUD_NOARP; + } else { + state = nud_state_a2n(*argv); + } + 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("can't 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) + puts("Nothing to flush"); + else + printf("*** Flush is complete after %d round(s) ***\n", round); + return 0; + } + round++; + if (flush_update() < 0) + xfunc_die(); + 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", 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("can't send dump request"); + } + + if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + 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); + + 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); + } + invarg(*argv, applet_name); + return 1; +} diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c index 6ecd5f719..f7209b126 100644 --- a/networking/libiproute/iproute.c +++ b/networking/libiproute/iproute.c @@ -55,28 +55,6 @@ static int flush_update(void) return 0; } -static unsigned get_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 = bb_clk_tck(); - return hz_internal; -} - static int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM, struct nlmsghdr *n, void *arg UNUSED_PARAM) { @@ -217,7 +195,7 @@ static int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM, if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) { if (flush_update()) - bb_error_msg_and_die("flush"); + xfunc_die(); } fn = (void*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp)); memcpy(fn, n, n->nlmsg_len); @@ -954,7 +932,7 @@ int FAST_FUNC do_iproute(char **argv) case 11: /* flush */ return iproute_list_or_flush(argv+1, 1); default: - bb_error_msg_and_die("unknown command %s", *argv); + invarg(*argv, applet_name); } return iproute_modify(cmd, flags, argv+1); diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c index d0fe30605..37b5311f0 100644 --- a/networking/libiproute/utils.c +++ b/networking/libiproute/utils.c @@ -13,6 +13,28 @@ #include "utils.h" #include "inet_common.h" +unsigned get_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 = bb_clk_tck(); + return hz_internal; +} + unsigned get_unsigned(char *arg, const char *errmsg) { unsigned long res; diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h index 5fb4a862c..cd15b706b 100644 --- a/networking/libiproute/utils.h +++ b/networking/libiproute/utils.h @@ -85,6 +85,8 @@ int dnet_pton(int af, const char *src, void *addr); const char *ipx_ntop(int af, const void *addr, char *str, size_t len); int ipx_pton(int af, const char *src, void *addr); +unsigned get_hz(void); + POP_SAVED_FUNCTION_VISIBILITY #endif