diff options
Diffstat (limited to 'networking/libiproute/ipneigh.c')
-rw-r--r-- | networking/libiproute/ipneigh.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/networking/libiproute/ipneigh.c b/networking/libiproute/ipneigh.c new file mode 100644 index 000000000..179505c2d --- /dev/null +++ b/networking/libiproute/ipneigh.c | |||
@@ -0,0 +1,353 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
4 | * | ||
5 | * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> | ||
6 | * | ||
7 | * Ported to Busybox by: Curt Brune <curt@cumulusnetworks.com> | ||
8 | */ | ||
9 | |||
10 | #include "ip_common.h" /* #include "libbb.h" is inside */ | ||
11 | #include "rt_names.h" | ||
12 | #include "utils.h" | ||
13 | #include <linux/neighbour.h> | ||
14 | #include <net/if_arp.h> | ||
15 | |||
16 | //static int xshow_stats = 3; | ||
17 | enum { xshow_stats = 3 }; | ||
18 | |||
19 | static inline uint32_t rta_getattr_u32(const struct rtattr *rta) | ||
20 | { | ||
21 | return *(uint32_t *)RTA_DATA(rta); | ||
22 | } | ||
23 | |||
24 | #ifndef RTAX_RTTVAR | ||
25 | #define RTAX_RTTVAR RTAX_HOPS | ||
26 | #endif | ||
27 | |||
28 | |||
29 | struct filter_t { | ||
30 | int family; | ||
31 | int index; | ||
32 | int state; | ||
33 | int unused_only; | ||
34 | inet_prefix pfx; | ||
35 | int flushed; | ||
36 | char *flushb; | ||
37 | int flushp; | ||
38 | int flushe; | ||
39 | struct rtnl_handle *rth; | ||
40 | } FIX_ALIASING; | ||
41 | typedef struct filter_t filter_t; | ||
42 | |||
43 | #define G_filter (*(filter_t*)&bb_common_bufsiz1) | ||
44 | |||
45 | static int flush_update(void) | ||
46 | { | ||
47 | if (rtnl_send(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) { | ||
48 | bb_perror_msg("can't send flush request"); | ||
49 | return -1; | ||
50 | } | ||
51 | G_filter.flushp = 0; | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static unsigned nud_state_a2n(char *arg) | ||
56 | { | ||
57 | static const char keywords[] ALIGN1 = | ||
58 | /* "ip neigh show/flush" parameters: */ | ||
59 | "permanent\0" "reachable\0" "noarp\0" "none\0" | ||
60 | "stale\0" "incomplete\0" "delay\0" "probe\0" | ||
61 | "failed\0" | ||
62 | ; | ||
63 | static uint8_t nuds[] = { | ||
64 | NUD_PERMANENT,NUD_REACHABLE, NUD_NOARP,NUD_NONE, | ||
65 | NUD_STALE, NUD_INCOMPLETE,NUD_DELAY,NUD_PROBE, | ||
66 | NUD_FAILED | ||
67 | }; | ||
68 | int id; | ||
69 | |||
70 | BUILD_BUG_ON( | ||
71 | (NUD_PERMANENT|NUD_REACHABLE| NUD_NOARP|NUD_NONE| | ||
72 | NUD_STALE| NUD_INCOMPLETE|NUD_DELAY|NUD_PROBE| | ||
73 | NUD_FAILED) > 0xff | ||
74 | ); | ||
75 | |||
76 | id = index_in_substrings(keywords, arg); | ||
77 | if (id < 0) | ||
78 | bb_error_msg_and_die(bb_msg_invalid_arg_to, arg, "nud state"); | ||
79 | return nuds[id]; | ||
80 | } | ||
81 | |||
82 | #ifndef NDA_RTA | ||
83 | #define NDA_RTA(r) \ | ||
84 | ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) | ||
85 | #endif | ||
86 | |||
87 | |||
88 | static int FAST_FUNC print_neigh(const struct sockaddr_nl *who UNUSED_PARAM, | ||
89 | struct nlmsghdr *n, void *arg UNUSED_PARAM) | ||
90 | { | ||
91 | struct ndmsg *r = NLMSG_DATA(n); | ||
92 | int len = n->nlmsg_len; | ||
93 | struct rtattr *tb[NDA_MAX+1]; | ||
94 | |||
95 | if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) { | ||
96 | bb_error_msg_and_die("not RTM_NEWNEIGH: %08x %08x %08x", | ||
97 | n->nlmsg_len, n->nlmsg_type, | ||
98 | n->nlmsg_flags); | ||
99 | } | ||
100 | len -= NLMSG_LENGTH(sizeof(*r)); | ||
101 | if (len < 0) { | ||
102 | bb_error_msg_and_die("BUG: wrong nlmsg len %d", len); | ||
103 | } | ||
104 | |||
105 | if (G_filter.flushb && n->nlmsg_type != RTM_NEWNEIGH) | ||
106 | return 0; | ||
107 | |||
108 | if (G_filter.family && G_filter.family != r->ndm_family) | ||
109 | return 0; | ||
110 | if (G_filter.index && G_filter.index != r->ndm_ifindex) | ||
111 | return 0; | ||
112 | if (!(G_filter.state&r->ndm_state) && | ||
113 | !(r->ndm_flags & NTF_PROXY) && | ||
114 | (r->ndm_state || !(G_filter.state & 0x100)) && | ||
115 | (r->ndm_family != AF_DECnet)) | ||
116 | return 0; | ||
117 | |||
118 | parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); | ||
119 | |||
120 | if (tb[NDA_DST]) { | ||
121 | if (G_filter.pfx.family) { | ||
122 | inet_prefix dst; | ||
123 | memset(&dst, 0, sizeof(dst)); | ||
124 | dst.family = r->ndm_family; | ||
125 | memcpy(&dst.data, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST])); | ||
126 | if (inet_addr_match(&dst, &G_filter.pfx, G_filter.pfx.bitlen)) | ||
127 | return 0; | ||
128 | } | ||
129 | } | ||
130 | if (G_filter.unused_only && tb[NDA_CACHEINFO]) { | ||
131 | struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]); | ||
132 | if (ci->ndm_refcnt) | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | if (G_filter.flushb) { | ||
137 | struct nlmsghdr *fn; | ||
138 | if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) { | ||
139 | if (flush_update()) | ||
140 | return -1; | ||
141 | } | ||
142 | fn = (struct nlmsghdr*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp)); | ||
143 | memcpy(fn, n, n->nlmsg_len); | ||
144 | fn->nlmsg_type = RTM_DELNEIGH; | ||
145 | fn->nlmsg_flags = NLM_F_REQUEST; | ||
146 | fn->nlmsg_seq = ++(G_filter.rth->seq); | ||
147 | G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb; | ||
148 | G_filter.flushed++; | ||
149 | if (xshow_stats < 2) | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | if (tb[NDA_DST]) { | ||
154 | printf("%s ", | ||
155 | format_host(r->ndm_family, | ||
156 | RTA_PAYLOAD(tb[NDA_DST]), | ||
157 | RTA_DATA(tb[NDA_DST])) | ||
158 | ); | ||
159 | } | ||
160 | if (!G_filter.index && r->ndm_ifindex) | ||
161 | printf("dev %s ", ll_index_to_name(r->ndm_ifindex)); | ||
162 | if (tb[NDA_LLADDR]) { | ||
163 | SPRINT_BUF(b1); | ||
164 | printf("lladdr %s", ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]), | ||
165 | RTA_PAYLOAD(tb[NDA_LLADDR]), | ||
166 | ARPHRD_ETHER, | ||
167 | b1, sizeof(b1))); | ||
168 | } | ||
169 | if (r->ndm_flags & NTF_ROUTER) { | ||
170 | printf(" router"); | ||
171 | } | ||
172 | if (r->ndm_flags & NTF_PROXY) { | ||
173 | printf(" proxy"); | ||
174 | } | ||
175 | if (tb[NDA_CACHEINFO] && xshow_stats) { | ||
176 | struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]); | ||
177 | int hz = get_hz(); | ||
178 | |||
179 | if (ci->ndm_refcnt) | ||
180 | printf(" ref %d", ci->ndm_refcnt); | ||
181 | printf(" used %d/%d/%d", ci->ndm_used/hz, | ||
182 | ci->ndm_confirmed/hz, ci->ndm_updated/hz); | ||
183 | } | ||
184 | |||
185 | if (tb[NDA_PROBES] && xshow_stats) { | ||
186 | uint32_t p = rta_getattr_u32(tb[NDA_PROBES]); | ||
187 | printf(" probes %u", p); | ||
188 | } | ||
189 | |||
190 | /*if (r->ndm_state)*/ { | ||
191 | int nud = r->ndm_state; | ||
192 | char c = ' '; | ||
193 | #define PRINT_FLAG(f) \ | ||
194 | if (nud & NUD_##f) { \ | ||
195 | printf("%c"#f, c); \ | ||
196 | c = ','; \ | ||
197 | } | ||
198 | PRINT_FLAG(INCOMPLETE); | ||
199 | PRINT_FLAG(REACHABLE); | ||
200 | PRINT_FLAG(STALE); | ||
201 | PRINT_FLAG(DELAY); | ||
202 | PRINT_FLAG(PROBE); | ||
203 | PRINT_FLAG(FAILED); | ||
204 | PRINT_FLAG(NOARP); | ||
205 | PRINT_FLAG(PERMANENT); | ||
206 | #undef PRINT_FLAG | ||
207 | } | ||
208 | bb_putchar('\n'); | ||
209 | |||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static void ipneigh_reset_filter(void) | ||
214 | { | ||
215 | memset(&G_filter, 0, sizeof(G_filter)); | ||
216 | G_filter.state = ~0; | ||
217 | } | ||
218 | |||
219 | #define MAX_ROUNDS 10 | ||
220 | /* Return value becomes exitcode. It's okay to not return at all */ | ||
221 | static int FAST_FUNC ipneigh_list_or_flush(char **argv, int flush) | ||
222 | { | ||
223 | static const char keywords[] ALIGN1 = | ||
224 | /* "ip neigh show/flush" parameters: */ | ||
225 | "to\0" "dev\0" "nud\0"; | ||
226 | enum { | ||
227 | KW_to, KW_dev, KW_nud, | ||
228 | }; | ||
229 | struct rtnl_handle rth; | ||
230 | struct ndmsg ndm = { 0 }; | ||
231 | char *filter_dev = NULL; | ||
232 | int state_given = 0; | ||
233 | int arg; | ||
234 | |||
235 | ipneigh_reset_filter(); | ||
236 | |||
237 | if (flush && !*argv) | ||
238 | bb_error_msg_and_die(bb_msg_requires_arg, "\"ip neigh flush\""); | ||
239 | |||
240 | if (!G_filter.family) | ||
241 | G_filter.family = preferred_family; | ||
242 | |||
243 | G_filter.state = (flush) ? | ||
244 | ~(NUD_PERMANENT|NUD_NOARP) : 0xFF & ~NUD_NOARP; | ||
245 | |||
246 | while (*argv) { | ||
247 | arg = index_in_substrings(keywords, *argv); | ||
248 | if (arg == KW_dev) { | ||
249 | NEXT_ARG(); | ||
250 | filter_dev = *argv; | ||
251 | } else if (arg == KW_nud) { | ||
252 | unsigned state; | ||
253 | NEXT_ARG(); | ||
254 | if (!state_given) { | ||
255 | state_given = 1; | ||
256 | G_filter.state = 0; | ||
257 | } | ||
258 | if (strcmp(*argv, "all") == 0) { | ||
259 | state = ~0; | ||
260 | if (flush) | ||
261 | state &= ~NUD_NOARP; | ||
262 | } else { | ||
263 | state = nud_state_a2n(*argv); | ||
264 | } | ||
265 | if (state == 0) | ||
266 | state = 0x100; | ||
267 | G_filter.state |= state; | ||
268 | } else { | ||
269 | if (arg == KW_to) { | ||
270 | NEXT_ARG(); | ||
271 | } | ||
272 | get_prefix(&G_filter.pfx, *argv, G_filter.family); | ||
273 | if (G_filter.family == AF_UNSPEC) | ||
274 | G_filter.family = G_filter.pfx.family; | ||
275 | } | ||
276 | argv++; | ||
277 | } | ||
278 | |||
279 | xrtnl_open(&rth); | ||
280 | ll_init_map(&rth); | ||
281 | |||
282 | if (filter_dev) { | ||
283 | G_filter.index = xll_name_to_index(filter_dev); | ||
284 | if (G_filter.index == 0) { | ||
285 | bb_error_msg_and_die("can't find device '%s'", filter_dev); | ||
286 | } | ||
287 | } | ||
288 | |||
289 | if (flush) { | ||
290 | int round = 0; | ||
291 | char flushb[4096-512]; | ||
292 | G_filter.flushb = flushb; | ||
293 | G_filter.flushp = 0; | ||
294 | G_filter.flushe = sizeof(flushb); | ||
295 | G_filter.state &= ~NUD_FAILED; | ||
296 | G_filter.rth = &rth; | ||
297 | |||
298 | while (round < MAX_ROUNDS) { | ||
299 | if (xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETNEIGH) < 0) { | ||
300 | bb_perror_msg_and_die("can't send dump request"); | ||
301 | } | ||
302 | G_filter.flushed = 0; | ||
303 | if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) { | ||
304 | bb_perror_msg_and_die("flush terminated"); | ||
305 | } | ||
306 | if (G_filter.flushed == 0) { | ||
307 | if (round == 0) | ||
308 | puts("Nothing to flush"); | ||
309 | else | ||
310 | printf("*** Flush is complete after %d round(s) ***\n", round); | ||
311 | return 0; | ||
312 | } | ||
313 | round++; | ||
314 | if (flush_update() < 0) | ||
315 | xfunc_die(); | ||
316 | printf("\n*** Round %d, deleting %d entries ***\n", round, G_filter.flushed); | ||
317 | } | ||
318 | bb_error_msg_and_die("*** Flush not complete bailing out after %d rounds", MAX_ROUNDS); | ||
319 | } | ||
320 | |||
321 | ndm.ndm_family = G_filter.family; | ||
322 | |||
323 | if (rtnl_dump_request(&rth, RTM_GETNEIGH, &ndm, sizeof(struct ndmsg)) < 0) { | ||
324 | bb_perror_msg_and_die("can't send dump request"); | ||
325 | } | ||
326 | |||
327 | if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) { | ||
328 | bb_error_msg_and_die("dump terminated"); | ||
329 | } | ||
330 | |||
331 | return 0; | ||
332 | } | ||
333 | |||
334 | /* Return value becomes exitcode. It's okay to not return at all */ | ||
335 | int FAST_FUNC do_ipneigh(char **argv) | ||
336 | { | ||
337 | static const char ip_neigh_commands[] ALIGN1 = | ||
338 | /*0-1*/ "show\0" "flush\0"; | ||
339 | int command_num; | ||
340 | |||
341 | if (!*argv) | ||
342 | return ipneigh_list_or_flush(argv, 0); | ||
343 | |||
344 | command_num = index_in_substrings(ip_neigh_commands, *argv); | ||
345 | switch (command_num) { | ||
346 | case 0: /* show */ | ||
347 | return ipneigh_list_or_flush(argv + 1, 0); | ||
348 | case 1: /* flush */ | ||
349 | return ipneigh_list_or_flush(argv + 1, 1); | ||
350 | } | ||
351 | invarg_1_to_2(*argv, applet_name); | ||
352 | return 1; | ||
353 | } | ||