blob: 0f90a1905c7299726b4400aebcab3c746215cb0d
1 | /* vi: set sw=4 ts=4: */ |
2 | /* route |
3 | * |
4 | * Similar to the standard Unix route, but with only the necessary |
5 | * parts for AF_INET and AF_INET6 |
6 | * |
7 | * Bjorn Wesen, Axis Communications AB |
8 | * |
9 | * Author of the original route: |
10 | * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> |
11 | * (derived from FvK's 'route.c 1.70 01/04/94') |
12 | * |
13 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
14 | * |
15 | * |
16 | * displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru> |
17 | * adjustments by Larry Doolittle <LRDoolittle@lbl.gov> |
18 | * |
19 | * IPV6 support added by Bart Visscher <magick@linux-fan.com> |
20 | */ |
21 | |
22 | /* 2004/03/09 Manuel Novoa III <mjn3@codepoet.org> |
23 | * |
24 | * Rewritten to fix several bugs, add additional error checking, and |
25 | * remove ridiculous amounts of bloat. |
26 | */ |
27 | //config:config ROUTE |
28 | //config: bool "route" |
29 | //config: default y |
30 | //config: select PLATFORM_LINUX |
31 | //config: help |
32 | //config: Route displays or manipulates the kernel's IP routing tables. |
33 | |
34 | //applet:IF_ROUTE(APPLET(route, BB_DIR_SBIN, BB_SUID_DROP)) |
35 | |
36 | //kbuild:lib-$(CONFIG_ROUTE) += route.o |
37 | |
38 | //usage:#define route_trivial_usage |
39 | //usage: "[{add|del|delete}]" |
40 | //usage:#define route_full_usage "\n\n" |
41 | //usage: "Edit kernel routing tables\n" |
42 | //usage: "\n -n Don't resolve names" |
43 | //usage: "\n -e Display other/more information" |
44 | //usage: "\n -A inet" IF_FEATURE_IPV6("{6}") " Select address family" |
45 | |
46 | #include <sys/socket.h> |
47 | #include <net/route.h> |
48 | #include <net/if.h> |
49 | |
50 | #include "libbb.h" |
51 | #include "inet_common.h" |
52 | |
53 | #if ENABLE_FEATURE_IPV6 |
54 | #include <linux/ipv6_route.h> |
55 | #endif |
56 | |
57 | #ifndef RTF_UP |
58 | /* Keep this in sync with /usr/src/linux/include/linux/route.h */ |
59 | #define RTF_UP 0x0001 /* route usable */ |
60 | #define RTF_GATEWAY 0x0002 /* destination is a gateway */ |
61 | #define RTF_HOST 0x0004 /* host entry (net otherwise) */ |
62 | #define RTF_REINSTATE 0x0008 /* reinstate route after tmout */ |
63 | #define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */ |
64 | #define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */ |
65 | #define RTF_MTU 0x0040 /* specific MTU for this route */ |
66 | #ifndef RTF_MSS |
67 | #define RTF_MSS RTF_MTU /* Compatibility :-( */ |
68 | #endif |
69 | #define RTF_WINDOW 0x0080 /* per route window clamping */ |
70 | #define RTF_IRTT 0x0100 /* Initial round trip time */ |
71 | #define RTF_REJECT 0x0200 /* Reject route */ |
72 | #define RTF_NONEXTHOP 0x00200000 /* route with no nexthop */ |
73 | #endif |
74 | |
75 | #ifndef RTF_CACHE |
76 | #define RTF_DEFAULT 0x00010000 /* default - learned via ND */ |
77 | #define RTF_ADDRCONF 0x00040000 /* addrconf route - RA */ |
78 | #define RTF_CACHE 0x01000000 /* cache entry */ |
79 | #endif |
80 | |
81 | #if defined(SIOCADDRTOLD) || defined(RTF_IRTT) /* route */ |
82 | #define HAVE_NEW_ADDRT 1 |
83 | #endif |
84 | |
85 | #if HAVE_NEW_ADDRT |
86 | #define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr) |
87 | #define full_mask(x) (x) |
88 | #else |
89 | #define mask_in_addr(x) ((x).rt_genmask) |
90 | #define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr) |
91 | #endif |
92 | |
93 | /* The RTACTION entries must agree with tbl_verb[] below! */ |
94 | #define RTACTION_ADD 1 |
95 | #define RTACTION_DEL 2 |
96 | |
97 | /* For the various tbl_*[] arrays, the 1st byte is the offset to |
98 | * the next entry and the 2nd byte is return value. */ |
99 | |
100 | #define NET_FLAG 1 |
101 | #define HOST_FLAG 2 |
102 | |
103 | /* We remap '-' to '#' to avoid problems with getopt. */ |
104 | static const char tbl_hash_net_host[] ALIGN1 = |
105 | "\007\001#net\0" |
106 | /* "\010\002#host\0" */ |
107 | "\007\002#host" /* Since last, we can save a byte. */ |
108 | ; |
109 | |
110 | #define KW_TAKES_ARG 020 |
111 | #define KW_SETS_FLAG 040 |
112 | |
113 | #define KW_IPVx_METRIC 020 |
114 | #define KW_IPVx_NETMASK 021 |
115 | #define KW_IPVx_GATEWAY 022 |
116 | #define KW_IPVx_MSS 023 |
117 | #define KW_IPVx_WINDOW 024 |
118 | #define KW_IPVx_IRTT 025 |
119 | #define KW_IPVx_DEVICE 026 |
120 | |
121 | #define KW_IPVx_FLAG_ONLY 040 |
122 | #define KW_IPVx_REJECT 040 |
123 | #define KW_IPVx_MOD 041 |
124 | #define KW_IPVx_DYN 042 |
125 | #define KW_IPVx_REINSTATE 043 |
126 | |
127 | static const char tbl_ipvx[] ALIGN1 = |
128 | /* 020 is the "takes an arg" bit */ |
129 | #if HAVE_NEW_ADDRT |
130 | "\011\020metric\0" |
131 | #endif |
132 | "\012\021netmask\0" |
133 | "\005\022gw\0" |
134 | "\012\022gateway\0" |
135 | "\006\023mss\0" |
136 | "\011\024window\0" |
137 | #ifdef RTF_IRTT |
138 | "\007\025irtt\0" |
139 | #endif |
140 | "\006\026dev\0" |
141 | "\011\026device\0" |
142 | /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */ |
143 | #ifdef RTF_REJECT |
144 | "\011\040reject\0" |
145 | #endif |
146 | "\006\041mod\0" |
147 | "\006\042dyn\0" |
148 | /* "\014\043reinstate\0" */ |
149 | "\013\043reinstate" /* Since last, we can save a byte. */ |
150 | ; |
151 | |
152 | static const uint16_t flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */ |
153 | #ifdef RTF_REJECT |
154 | RTF_REJECT, |
155 | #endif |
156 | RTF_MODIFIED, |
157 | RTF_DYNAMIC, |
158 | RTF_REINSTATE |
159 | }; |
160 | |
161 | static int kw_lookup(const char *kwtbl, char ***pargs) |
162 | { |
163 | if (**pargs) { |
164 | do { |
165 | if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */ |
166 | *pargs += 1; |
167 | if (kwtbl[1] & KW_TAKES_ARG) { |
168 | if (!**pargs) { /* No more args! */ |
169 | bb_show_usage(); |
170 | } |
171 | *pargs += 1; /* Calling routine will use args[-1]. */ |
172 | } |
173 | return kwtbl[1]; |
174 | } |
175 | kwtbl += *kwtbl; |
176 | } while (*kwtbl); |
177 | } |
178 | return 0; |
179 | } |
180 | |
181 | /* Add or delete a route, depending on action. */ |
182 | |
183 | static NOINLINE void INET_setroute(int action, char **args) |
184 | { |
185 | /* char buffer instead of bona-fide struct avoids aliasing warning */ |
186 | char rt_buf[sizeof(struct rtentry)]; |
187 | struct rtentry *const rt = (void *)rt_buf; |
188 | |
189 | const char *netmask = NULL; |
190 | int skfd, isnet, xflag; |
191 | |
192 | /* Grab the -net or -host options. Remember they were transformed. */ |
193 | xflag = kw_lookup(tbl_hash_net_host, &args); |
194 | |
195 | /* If we did grab -net or -host, make sure we still have an arg left. */ |
196 | if (*args == NULL) { |
197 | bb_show_usage(); |
198 | } |
199 | |
200 | /* Clean out the RTREQ structure. */ |
201 | memset(rt, 0, sizeof(*rt)); |
202 | |
203 | { |
204 | const char *target = *args++; |
205 | char *prefix; |
206 | |
207 | /* recognize x.x.x.x/mask format. */ |
208 | prefix = strchr(target, '/'); |
209 | if (prefix) { |
210 | int prefix_len; |
211 | |
212 | prefix_len = xatoul_range(prefix+1, 0, 32); |
213 | mask_in_addr(*rt) = htonl( ~(0xffffffffUL >> prefix_len)); |
214 | *prefix = '\0'; |
215 | #if HAVE_NEW_ADDRT |
216 | rt->rt_genmask.sa_family = AF_INET; |
217 | #endif |
218 | } else { |
219 | /* Default netmask. */ |
220 | netmask = "default"; |
221 | } |
222 | /* Prefer hostname lookup is -host flag (xflag==1) was given. */ |
223 | isnet = INET_resolve(target, (struct sockaddr_in *) &rt->rt_dst, |
224 | (xflag & HOST_FLAG)); |
225 | if (isnet < 0) { |
226 | bb_error_msg_and_die("resolving %s", target); |
227 | } |
228 | if (prefix) { |
229 | /* do not destroy prefix for process args */ |
230 | *prefix = '/'; |
231 | } |
232 | } |
233 | |
234 | if (xflag) { /* Reinit isnet if -net or -host was specified. */ |
235 | isnet = (xflag & NET_FLAG); |
236 | } |
237 | |
238 | /* Fill in the other fields. */ |
239 | rt->rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST)); |
240 | |
241 | while (*args) { |
242 | int k = kw_lookup(tbl_ipvx, &args); |
243 | const char *args_m1 = args[-1]; |
244 | |
245 | if (k & KW_IPVx_FLAG_ONLY) { |
246 | rt->rt_flags |= flags_ipvx[k & 3]; |
247 | continue; |
248 | } |
249 | |
250 | #if HAVE_NEW_ADDRT |
251 | if (k == KW_IPVx_METRIC) { |
252 | rt->rt_metric = xatoul(args_m1) + 1; |
253 | continue; |
254 | } |
255 | #endif |
256 | |
257 | if (k == KW_IPVx_NETMASK) { |
258 | struct sockaddr mask; |
259 | |
260 | if (mask_in_addr(*rt)) { |
261 | bb_show_usage(); |
262 | } |
263 | |
264 | netmask = args_m1; |
265 | isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0); |
266 | if (isnet < 0) { |
267 | bb_error_msg_and_die("resolving %s", netmask); |
268 | } |
269 | rt->rt_genmask = full_mask(mask); |
270 | continue; |
271 | } |
272 | |
273 | if (k == KW_IPVx_GATEWAY) { |
274 | if (rt->rt_flags & RTF_GATEWAY) { |
275 | bb_show_usage(); |
276 | } |
277 | |
278 | isnet = INET_resolve(args_m1, |
279 | (struct sockaddr_in *) &rt->rt_gateway, 1); |
280 | rt->rt_flags |= RTF_GATEWAY; |
281 | |
282 | if (isnet) { |
283 | if (isnet < 0) { |
284 | bb_error_msg_and_die("resolving %s", args_m1); |
285 | } |
286 | bb_error_msg_and_die("gateway %s is a NETWORK", args_m1); |
287 | } |
288 | continue; |
289 | } |
290 | |
291 | if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */ |
292 | rt->rt_flags |= RTF_MSS; |
293 | rt->rt_mss = xatoul_range(args_m1, 64, 32768); |
294 | continue; |
295 | } |
296 | |
297 | if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */ |
298 | rt->rt_flags |= RTF_WINDOW; |
299 | rt->rt_window = xatoul_range(args_m1, 128, INT_MAX); |
300 | continue; |
301 | } |
302 | |
303 | #ifdef RTF_IRTT |
304 | if (k == KW_IPVx_IRTT) { |
305 | rt->rt_flags |= RTF_IRTT; |
306 | rt->rt_irtt = xatoul(args_m1); |
307 | rt->rt_irtt *= (bb_clk_tck() / 100); /* FIXME */ |
308 | #if 0 /* FIXME: do we need to check anything of this? */ |
309 | if (rt->rt_irtt < 1 || rt->rt_irtt > (120 * HZ)) { |
310 | bb_error_msg_and_die("bad irtt"); |
311 | } |
312 | #endif |
313 | continue; |
314 | } |
315 | #endif |
316 | |
317 | /* Device is special in that it can be the last arg specified |
318 | * and doesn't require the dev/device keyword in that case. */ |
319 | if (!rt->rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { |
320 | /* Don't use args_m1 here since args may have changed! */ |
321 | rt->rt_dev = args[-1]; |
322 | continue; |
323 | } |
324 | |
325 | /* Nothing matched. */ |
326 | bb_show_usage(); |
327 | } |
328 | |
329 | #ifdef RTF_REJECT |
330 | if ((rt->rt_flags & RTF_REJECT) && !rt->rt_dev) { |
331 | rt->rt_dev = (char*)"lo"; |
332 | } |
333 | #endif |
334 | |
335 | /* sanity checks.. */ |
336 | if (mask_in_addr(*rt)) { |
337 | uint32_t mask = mask_in_addr(*rt); |
338 | |
339 | mask = ~ntohl(mask); |
340 | if ((rt->rt_flags & RTF_HOST) && mask != 0xffffffff) { |
341 | bb_error_msg_and_die("netmask %.8x and host route conflict", |
342 | (unsigned int) mask); |
343 | } |
344 | if (mask & (mask + 1)) { |
345 | bb_error_msg_and_die("bogus netmask %s", netmask); |
346 | } |
347 | mask = ((struct sockaddr_in *) &rt->rt_dst)->sin_addr.s_addr; |
348 | if (mask & ~(uint32_t)mask_in_addr(*rt)) { |
349 | bb_error_msg_and_die("netmask and route address conflict"); |
350 | } |
351 | } |
352 | |
353 | /* Fill out netmask if still unset */ |
354 | if ((action == RTACTION_ADD) && (rt->rt_flags & RTF_HOST)) { |
355 | mask_in_addr(*rt) = 0xffffffff; |
356 | } |
357 | |
358 | /* Create a socket to the INET kernel. */ |
359 | skfd = xsocket(AF_INET, SOCK_DGRAM, 0); |
360 | |
361 | if (action == RTACTION_ADD) |
362 | xioctl(skfd, SIOCADDRT, rt); |
363 | else |
364 | xioctl(skfd, SIOCDELRT, rt); |
365 | |
366 | if (ENABLE_FEATURE_CLEAN_UP) close(skfd); |
367 | } |
368 | |
369 | #if ENABLE_FEATURE_IPV6 |
370 | |
371 | static NOINLINE void INET6_setroute(int action, char **args) |
372 | { |
373 | struct sockaddr_in6 sa6; |
374 | struct in6_rtmsg rt; |
375 | int prefix_len, skfd; |
376 | const char *devname; |
377 | |
378 | /* We know args isn't NULL from the check in route_main. */ |
379 | const char *target = *args++; |
380 | |
381 | if (strcmp(target, "default") == 0) { |
382 | prefix_len = 0; |
383 | memset(&sa6, 0, sizeof(sa6)); |
384 | } else { |
385 | char *cp; |
386 | cp = strchr(target, '/'); /* Yes... const to non is ok. */ |
387 | if (cp) { |
388 | *cp = '\0'; |
389 | prefix_len = xatoul_range(cp + 1, 0, 128); |
390 | } else { |
391 | prefix_len = 128; |
392 | } |
393 | if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { |
394 | bb_error_msg_and_die("resolving %s", target); |
395 | } |
396 | } |
397 | |
398 | /* Clean out the RTREQ structure. */ |
399 | memset(&rt, 0, sizeof(rt)); |
400 | |
401 | memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr)); |
402 | |
403 | /* Fill in the other fields. */ |
404 | rt.rtmsg_dst_len = prefix_len; |
405 | rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP); |
406 | rt.rtmsg_metric = 1; |
407 | |
408 | devname = NULL; |
409 | |
410 | while (*args) { |
411 | int k = kw_lookup(tbl_ipvx, &args); |
412 | const char *args_m1 = args[-1]; |
413 | |
414 | if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) { |
415 | rt.rtmsg_flags |= flags_ipvx[k & 3]; |
416 | continue; |
417 | } |
418 | |
419 | if (k == KW_IPVx_METRIC) { |
420 | rt.rtmsg_metric = xatoul(args_m1); |
421 | continue; |
422 | } |
423 | |
424 | if (k == KW_IPVx_GATEWAY) { |
425 | if (rt.rtmsg_flags & RTF_GATEWAY) { |
426 | bb_show_usage(); |
427 | } |
428 | |
429 | if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) { |
430 | bb_error_msg_and_die("resolving %s", args_m1); |
431 | } |
432 | memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr, |
433 | sizeof(struct in6_addr)); |
434 | rt.rtmsg_flags |= RTF_GATEWAY; |
435 | continue; |
436 | } |
437 | |
438 | /* Device is special in that it can be the last arg specified |
439 | * and doesn't require the dev/device keyword in that case. */ |
440 | if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { |
441 | /* Don't use args_m1 here since args may have changed! */ |
442 | devname = args[-1]; |
443 | continue; |
444 | } |
445 | |
446 | /* Nothing matched. */ |
447 | bb_show_usage(); |
448 | } |
449 | |
450 | /* Create a socket to the INET6 kernel. */ |
451 | skfd = xsocket(AF_INET6, SOCK_DGRAM, 0); |
452 | |
453 | rt.rtmsg_ifindex = 0; |
454 | |
455 | if (devname) { |
456 | struct ifreq ifr; |
457 | memset(&ifr, 0, sizeof(ifr)); |
458 | strncpy_IFNAMSIZ(ifr.ifr_name, devname); |
459 | xioctl(skfd, SIOCGIFINDEX, &ifr); |
460 | rt.rtmsg_ifindex = ifr.ifr_ifindex; |
461 | } |
462 | |
463 | /* Tell the kernel to accept this route. */ |
464 | if (action == RTACTION_ADD) |
465 | xioctl(skfd, SIOCADDRT, &rt); |
466 | else |
467 | xioctl(skfd, SIOCDELRT, &rt); |
468 | |
469 | if (ENABLE_FEATURE_CLEAN_UP) close(skfd); |
470 | } |
471 | #endif |
472 | |
473 | static const |
474 | IF_NOT_FEATURE_IPV6(uint16_t) |
475 | IF_FEATURE_IPV6(unsigned) |
476 | flagvals[] = { /* Must agree with flagchars[]. */ |
477 | RTF_UP, |
478 | RTF_GATEWAY, |
479 | RTF_HOST, |
480 | RTF_REINSTATE, |
481 | RTF_DYNAMIC, |
482 | RTF_MODIFIED, |
483 | #if ENABLE_FEATURE_IPV6 |
484 | RTF_DEFAULT, |
485 | RTF_ADDRCONF, |
486 | RTF_CACHE, |
487 | RTF_REJECT, |
488 | RTF_NONEXTHOP, /* this one doesn't fit into 16 bits */ |
489 | #endif |
490 | }; |
491 | /* Must agree with flagvals[]. */ |
492 | static const char flagchars[] ALIGN1 = |
493 | "UGHRDM" |
494 | #if ENABLE_FEATURE_IPV6 |
495 | "DAC!n" |
496 | #endif |
497 | ; |
498 | #define IPV4_MASK (RTF_UP|RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED) |
499 | #define IPV6_MASK (RTF_UP|RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE|RTF_REJECT|RTF_NONEXTHOP) |
500 | |
501 | static void set_flags(char *flagstr, int flags) |
502 | { |
503 | int i; |
504 | |
505 | for (i = 0; (*flagstr = flagchars[i]) != 0; i++) { |
506 | if (flags & flagvals[i]) { |
507 | ++flagstr; |
508 | } |
509 | } |
510 | } |
511 | |
512 | /* also used in netstat */ |
513 | void FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt) |
514 | { |
515 | char devname[64], flags[16], *sdest, *sgw; |
516 | unsigned long d, g, m; |
517 | int r; |
518 | int flgs, ref, use, metric, mtu, win, ir; |
519 | struct sockaddr_in s_addr; |
520 | struct in_addr mask; |
521 | |
522 | FILE *fp = xfopen_for_read("/proc/net/route"); |
523 | |
524 | printf("Kernel IP routing table\n" |
525 | "Destination Gateway Genmask Flags %s Iface\n", |
526 | netstatfmt ? " MSS Window irtt" : "Metric Ref Use"); |
527 | |
528 | /* Skip the first line. */ |
529 | r = fscanf(fp, "%*[^\n]\n"); |
530 | if (r < 0) { |
531 | /* Empty line, read error, or EOF. Yes, if routing table |
532 | * is completely empty, /proc/net/route has no header. |
533 | */ |
534 | goto ERROR; |
535 | } |
536 | while (1) { |
537 | r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", |
538 | devname, &d, &g, &flgs, &ref, &use, &metric, &m, |
539 | &mtu, &win, &ir); |
540 | if (r != 11) { |
541 | ERROR: |
542 | if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ |
543 | break; |
544 | } |
545 | bb_perror_msg_and_die(bb_msg_read_error); |
546 | } |
547 | |
548 | if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */ |
549 | continue; |
550 | } |
551 | |
552 | set_flags(flags, (flgs & IPV4_MASK)); |
553 | #ifdef RTF_REJECT |
554 | if (flgs & RTF_REJECT) { |
555 | flags[0] = '!'; |
556 | } |
557 | #endif |
558 | |
559 | memset(&s_addr, 0, sizeof(struct sockaddr_in)); |
560 | s_addr.sin_family = AF_INET; |
561 | s_addr.sin_addr.s_addr = d; |
562 | sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */ |
563 | s_addr.sin_addr.s_addr = g; |
564 | sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */ |
565 | mask.s_addr = m; |
566 | /* "%15.15s" truncates hostnames, do we really want that? */ |
567 | printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags); |
568 | free(sdest); |
569 | free(sgw); |
570 | if (netstatfmt) { |
571 | printf("%5d %-5d %6d %s\n", mtu, win, ir, devname); |
572 | } else { |
573 | printf("%-6d %-2d %7d %s\n", metric, ref, use, devname); |
574 | } |
575 | } |
576 | fclose(fp); |
577 | } |
578 | |
579 | #if ENABLE_FEATURE_IPV6 |
580 | |
581 | static void INET6_displayroutes(void) |
582 | { |
583 | char addr6[128], *naddr6; |
584 | /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses. |
585 | * We read the non-delimited strings into the tail of the buffer |
586 | * using fscanf and then modify the buffer by shifting forward |
587 | * while inserting ':'s and the nul terminator for the first string. |
588 | * Hence the strings are at addr6x and addr6x+40. This generates |
589 | * _much_ less code than the previous (upstream) approach. */ |
590 | char addr6x[80]; |
591 | char iface[16], flags[16]; |
592 | int iflags, metric, refcnt, use, prefix_len, slen; |
593 | struct sockaddr_in6 snaddr6; |
594 | |
595 | FILE *fp = xfopen_for_read("/proc/net/ipv6_route"); |
596 | |
597 | printf("Kernel IPv6 routing table\n%-44s%-40s" |
598 | "Flags Metric Ref Use Iface\n", |
599 | "Destination", "Next Hop"); |
600 | |
601 | while (1) { |
602 | int r; |
603 | r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n", |
604 | addr6x+14, &prefix_len, &slen, addr6x+40+7, |
605 | &metric, &refcnt, &use, &iflags, iface); |
606 | if (r != 9) { |
607 | if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ |
608 | break; |
609 | } |
610 | ERROR: |
611 | bb_perror_msg_and_die(bb_msg_read_error); |
612 | } |
613 | |
614 | /* Do the addr6x shift-and-insert changes to ':'-delimit addresses. |
615 | * For now, always do this to validate the proc route format, even |
616 | * if the interface is down. */ |
617 | { |
618 | int i = 0; |
619 | char *p = addr6x+14; |
620 | |
621 | do { |
622 | if (!*p) { |
623 | if (i == 40) { /* nul terminator for 1st address? */ |
624 | addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */ |
625 | ++p; /* Skip and continue. */ |
626 | continue; |
627 | } |
628 | goto ERROR; |
629 | } |
630 | addr6x[i++] = *p++; |
631 | if (!((i+1) % 5)) { |
632 | addr6x[i++] = ':'; |
633 | } |
634 | } while (i < 40+28+7); |
635 | } |
636 | |
637 | set_flags(flags, (iflags & IPV6_MASK)); |
638 | |
639 | r = 0; |
640 | while (1) { |
641 | inet_pton(AF_INET6, addr6x + r, |
642 | (struct sockaddr *) &snaddr6.sin6_addr); |
643 | snaddr6.sin6_family = AF_INET6; |
644 | naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6, |
645 | 0x0fff /* Apparently, upstream never resolves. */ |
646 | ); |
647 | |
648 | if (!r) { /* 1st pass */ |
649 | snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len); |
650 | r += 40; |
651 | free(naddr6); |
652 | } else { /* 2nd pass */ |
653 | /* Print the info. */ |
654 | printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n", |
655 | addr6, naddr6, flags, metric, refcnt, use, iface); |
656 | free(naddr6); |
657 | break; |
658 | } |
659 | } |
660 | } |
661 | fclose(fp); |
662 | } |
663 | |
664 | #endif |
665 | |
666 | #define ROUTE_OPT_A 0x01 |
667 | #define ROUTE_OPT_n 0x02 |
668 | #define ROUTE_OPT_e 0x04 |
669 | #define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */ |
670 | |
671 | /* 1st byte is offset to next entry offset. 2nd byte is return value. */ |
672 | /* 2nd byte matches RTACTION_* code */ |
673 | static const char tbl_verb[] ALIGN1 = |
674 | "\006\001add\0" |
675 | "\006\002del\0" |
676 | /* "\011\002delete\0" */ |
677 | "\010\002delete" /* Since it's last, we can save a byte. */ |
678 | ; |
679 | |
680 | int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
681 | int route_main(int argc UNUSED_PARAM, char **argv) |
682 | { |
683 | unsigned opt; |
684 | int what; |
685 | char *family; |
686 | char **p; |
687 | |
688 | /* First, remap '-net' and '-host' to avoid getopt problems. */ |
689 | p = argv; |
690 | while (*++p) { |
691 | if (strcmp(*p, "-net") == 0 || strcmp(*p, "-host") == 0) { |
692 | p[0][0] = '#'; |
693 | } |
694 | } |
695 | |
696 | opt = getopt32(argv, "A:ne", &family); |
697 | |
698 | if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) { |
699 | #if ENABLE_FEATURE_IPV6 |
700 | if (strcmp(family, "inet6") == 0) { |
701 | opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */ |
702 | } else |
703 | #endif |
704 | bb_show_usage(); |
705 | } |
706 | |
707 | argv += optind; |
708 | |
709 | /* No more args means display the routing table. */ |
710 | if (!*argv) { |
711 | int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0; |
712 | #if ENABLE_FEATURE_IPV6 |
713 | if (opt & ROUTE_OPT_INET6) |
714 | INET6_displayroutes(); |
715 | else |
716 | #endif |
717 | bb_displayroutes(noresolve, opt & ROUTE_OPT_e); |
718 | |
719 | fflush_stdout_and_exit(EXIT_SUCCESS); |
720 | } |
721 | |
722 | /* Check verb. At the moment, must be add, del, or delete. */ |
723 | what = kw_lookup(tbl_verb, &argv); |
724 | if (!what || !*argv) { /* Unknown verb or no more args. */ |
725 | bb_show_usage(); |
726 | } |
727 | |
728 | #if ENABLE_FEATURE_IPV6 |
729 | if (opt & ROUTE_OPT_INET6) |
730 | INET6_setroute(what, argv); |
731 | else |
732 | #endif |
733 | INET_setroute(what, argv); |
734 | |
735 | return EXIT_SUCCESS; |
736 | } |
737 |