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