1 /* vi: set sw=4 ts=4: */
3 * iproute.c "ip route".
5 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
7 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
12 * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
13 * Kunihiro Ishiguro <kunihiro@zebra.org> 001102: rtnh_ifindex was not initialized
16 #include "ip_common.h" /* #include "libbb.h" is inside */
21 #define RTAX_RTTVAR RTAX_HOPS
25 typedef struct filter_t
{
31 struct rtnl_handle
*rth
;
32 int protocol
, protocolmask
;
47 #define filter (*(filter_t*)&bb_common_bufsiz1)
49 static int flush_update(void)
51 if (rtnl_send(filter
.rth
, filter
.flushb
, filter
.flushp
) < 0) {
52 bb_perror_msg("failed to send flush request");
59 static unsigned get_hz(void)
61 static unsigned hz_internal
;
67 fp
= fopen_for_read("/proc/net/psched");
71 if (fscanf(fp
, "%*08x%*08x%08x%08x", &nom
, &denom
) == 2)
77 hz_internal
= sysconf(_SC_CLK_TCK
);
81 static int print_route(const struct sockaddr_nl
*who UNUSED_PARAM
,
82 struct nlmsghdr
*n
, void *arg UNUSED_PARAM
)
84 struct rtmsg
*r
= NLMSG_DATA(n
);
85 int len
= n
->nlmsg_len
;
86 struct rtattr
* tb
[RTA_MAX
+1];
93 if (n
->nlmsg_type
!= RTM_NEWROUTE
&& n
->nlmsg_type
!= RTM_DELROUTE
) {
94 fprintf(stderr
, "Not a route: %08x %08x %08x\n",
95 n
->nlmsg_len
, n
->nlmsg_type
, n
->nlmsg_flags
);
98 if (filter
.flushb
&& n
->nlmsg_type
!= RTM_NEWROUTE
)
100 len
-= NLMSG_LENGTH(sizeof(*r
));
102 bb_error_msg_and_die("wrong nlmsg len %d", len
);
104 if (r
->rtm_family
== AF_INET6
)
106 else if (r
->rtm_family
== AF_INET
)
109 if (r
->rtm_family
== AF_INET6
) {
112 if (!(r
->rtm_flags
& RTM_F_CLONED
)) {
116 if (r
->rtm_flags
& RTM_F_CLONED
) {
119 if (filter
.tb
== RT_TABLE_LOCAL
) {
120 if (r
->rtm_type
!= RTN_LOCAL
) {
123 } else if (filter
.tb
== RT_TABLE_MAIN
) {
124 if (r
->rtm_type
== RTN_LOCAL
) {
133 if (filter
.tb
> 0 && filter
.tb
!= r
->rtm_table
) {
137 if (filter
.rdst
.family
&&
138 (r
->rtm_family
!= filter
.rdst
.family
|| filter
.rdst
.bitlen
> r
->rtm_dst_len
)) {
141 if (filter
.mdst
.family
&&
142 (r
->rtm_family
!= filter
.mdst
.family
||
143 (filter
.mdst
.bitlen
>= 0 && filter
.mdst
.bitlen
< r
->rtm_dst_len
))) {
146 if (filter
.rsrc
.family
&&
147 (r
->rtm_family
!= filter
.rsrc
.family
|| filter
.rsrc
.bitlen
> r
->rtm_src_len
)) {
150 if (filter
.msrc
.family
&&
151 (r
->rtm_family
!= filter
.msrc
.family
||
152 (filter
.msrc
.bitlen
>= 0 && filter
.msrc
.bitlen
< r
->rtm_src_len
))) {
156 memset(tb
, 0, sizeof(tb
));
157 parse_rtattr(tb
, RTA_MAX
, RTM_RTA(r
), len
);
159 if (filter
.rdst
.family
&& inet_addr_match(&dst
, &filter
.rdst
, filter
.rdst
.bitlen
))
161 if (filter
.mdst
.family
&& filter
.mdst
.bitlen
>= 0 &&
162 inet_addr_match(&dst
, &filter
.mdst
, r
->rtm_dst_len
))
165 if (filter
.rsrc
.family
&& inet_addr_match(&src
, &filter
.rsrc
, filter
.rsrc
.bitlen
))
167 if (filter
.msrc
.family
&& filter
.msrc
.bitlen
>= 0 &&
168 inet_addr_match(&src
, &filter
.msrc
, r
->rtm_src_len
))
172 r
->rtm_family
== AF_INET6
&&
173 r
->rtm_dst_len
== 0 &&
174 r
->rtm_type
== RTN_UNREACHABLE
&&
176 *(int*)RTA_DATA(tb
[RTA_PRIORITY
]) == -1)
181 if (NLMSG_ALIGN(filter
.flushp
) + n
->nlmsg_len
> filter
.flushe
) {
183 bb_error_msg_and_die("flush");
185 fn
= (struct nlmsghdr
*)(filter
.flushb
+ NLMSG_ALIGN(filter
.flushp
));
186 memcpy(fn
, n
, n
->nlmsg_len
);
187 fn
->nlmsg_type
= RTM_DELROUTE
;
188 fn
->nlmsg_flags
= NLM_F_REQUEST
;
189 fn
->nlmsg_seq
= ++filter
.rth
->seq
;
190 filter
.flushp
= (((char*)fn
) + n
->nlmsg_len
) - filter
.flushb
;
195 if (n
->nlmsg_type
== RTM_DELROUTE
) {
198 if (r
->rtm_type
!= RTN_UNICAST
&& !filter
.type
) {
199 printf("%s ", rtnl_rtntype_n2a(r
->rtm_type
, b1
, sizeof(b1
)));
203 if (r
->rtm_dst_len
!= host_len
) {
204 printf("%s/%u ", rt_addr_n2a(r
->rtm_family
,
205 RTA_DATA(tb
[RTA_DST
]),
210 printf("%s ", format_host(r
->rtm_family
,
211 RTA_PAYLOAD(tb
[RTA_DST
]),
212 RTA_DATA(tb
[RTA_DST
]),
216 } else if (r
->rtm_dst_len
) {
217 printf("0/%d ", r
->rtm_dst_len
);
222 if (r
->rtm_src_len
!= host_len
) {
223 printf("from %s/%u ", rt_addr_n2a(r
->rtm_family
,
224 RTA_DATA(tb
[RTA_SRC
]),
229 printf("from %s ", format_host(r
->rtm_family
,
230 RTA_PAYLOAD(tb
[RTA_SRC
]),
231 RTA_DATA(tb
[RTA_SRC
]),
235 } else if (r
->rtm_src_len
) {
236 printf("from 0/%u ", r
->rtm_src_len
);
238 if (tb
[RTA_GATEWAY
] && filter
.rvia
.bitlen
!= host_len
) {
239 printf("via %s ", format_host(r
->rtm_family
,
240 RTA_PAYLOAD(tb
[RTA_GATEWAY
]),
241 RTA_DATA(tb
[RTA_GATEWAY
]),
242 abuf
, sizeof(abuf
)));
244 if (tb
[RTA_OIF
] && filter
.oifmask
!= -1) {
245 printf("dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb
[RTA_OIF
])));
248 if (tb
[RTA_PREFSRC
] && filter
.rprefsrc
.bitlen
!= host_len
) {
249 /* Do not use format_host(). It is our local addr
250 and symbolic name will not be useful.
252 printf(" src %s ", rt_addr_n2a(r
->rtm_family
,
253 RTA_DATA(tb
[RTA_PREFSRC
]),
254 abuf
, sizeof(abuf
)));
256 if (tb
[RTA_PRIORITY
]) {
257 printf(" metric %d ", *(uint32_t*)RTA_DATA(tb
[RTA_PRIORITY
]));
259 if (r
->rtm_family
== AF_INET6
) {
260 struct rta_cacheinfo
*ci
= NULL
;
261 if (tb
[RTA_CACHEINFO
]) {
262 ci
= RTA_DATA(tb
[RTA_CACHEINFO
]);
264 if ((r
->rtm_flags
& RTM_F_CLONED
) || (ci
&& ci
->rta_expires
)) {
265 if (r
->rtm_flags
& RTM_F_CLONED
) {
266 printf("%c cache ", _SL_
);
268 if (ci
->rta_expires
) {
269 printf(" expires %dsec", ci
->rta_expires
/ get_hz());
271 if (ci
->rta_error
!= 0) {
272 printf(" error %d", ci
->rta_error
);
275 if (ci
->rta_error
!= 0)
276 printf(" error %d", ci
->rta_error
);
279 if (tb
[RTA_IIF
] && filter
.iifmask
!= -1) {
280 printf(" iif %s", ll_index_to_name(*(int*)RTA_DATA(tb
[RTA_IIF
])));
286 /* Return value becomes exitcode. It's okay to not return at all */
287 static int iproute_modify(int cmd
, unsigned flags
, char **argv
)
289 static const char keywords
[] ALIGN1
=
290 "src\0""via\0""mtu\0""lock\0""protocol\0"USE_FEATURE_IP_RULE("table\0")
291 "dev\0""oif\0""to\0""metric\0";
297 USE_FEATURE_IP_RULE(ARG_table
,)
309 struct rtnl_handle rth
;
316 struct rtattr
* mxrta
= (void*)mxbuf
;
322 memset(&req
, 0, sizeof(req
));
324 req
.n
.nlmsg_len
= NLMSG_LENGTH(sizeof(struct rtmsg
));
325 req
.n
.nlmsg_flags
= NLM_F_REQUEST
| flags
;
326 req
.n
.nlmsg_type
= cmd
;
327 req
.r
.rtm_family
= preferred_family
;
328 if (RT_TABLE_MAIN
) /* if it is zero, memset already did it */
329 req
.r
.rtm_table
= RT_TABLE_MAIN
;
330 if (RT_SCOPE_NOWHERE
)
331 req
.r
.rtm_scope
= RT_SCOPE_NOWHERE
;
333 if (cmd
!= RTM_DELROUTE
) {
334 req
.r
.rtm_protocol
= RTPROT_BOOT
;
335 req
.r
.rtm_scope
= RT_SCOPE_UNIVERSE
;
336 req
.r
.rtm_type
= RTN_UNICAST
;
339 mxrta
->rta_type
= RTA_METRICS
;
340 mxrta
->rta_len
= RTA_LENGTH(0);
343 arg
= index_in_substrings(keywords
, *argv
);
344 if (arg
== ARG_src
) {
347 get_addr(&addr
, *argv
, req
.r
.rtm_family
);
348 if (req
.r
.rtm_family
== AF_UNSPEC
)
349 req
.r
.rtm_family
= addr
.family
;
350 addattr_l(&req
.n
, sizeof(req
), RTA_PREFSRC
, &addr
.data
, addr
.bytelen
);
351 } else if (arg
== ARG_via
) {
355 get_addr(&addr
, *argv
, req
.r
.rtm_family
);
356 if (req
.r
.rtm_family
== AF_UNSPEC
) {
357 req
.r
.rtm_family
= addr
.family
;
359 addattr_l(&req
.n
, sizeof(req
), RTA_GATEWAY
, &addr
.data
, addr
.bytelen
);
360 } else if (arg
== ARG_mtu
) {
363 if (index_in_strings(keywords
, *argv
) == PARM_lock
) {
364 mxlock
|= (1 << RTAX_MTU
);
367 mtu
= get_unsigned(*argv
, "mtu");
368 rta_addattr32(mxrta
, sizeof(mxbuf
), RTAX_MTU
, mtu
);
369 } else if (arg
== ARG_protocol
) {
372 if (rtnl_rtprot_a2n(&prot
, *argv
))
373 invarg(*argv
, "protocol");
374 req
.r
.rtm_protocol
= prot
;
376 #if ENABLE_FEATURE_IP_RULE
377 } else if (arg
== ARG_table
) {
380 if (rtnl_rttable_a2n(&tid
, *argv
))
381 invarg(*argv
, "table");
382 req
.r
.rtm_table
= tid
;
384 } else if (arg
== ARG_dev
|| arg
== ARG_oif
) {
387 } else if (arg
== ARG_metric
) {
390 metric
= get_u32(*argv
, "metric");
391 addattr32(&req
.n
, sizeof(req
), RTA_PRIORITY
, metric
);
399 if ((**argv
< '0' || **argv
> '9')
400 && rtnl_rtntype_a2n(&type
, *argv
) == 0) {
402 req
.r
.rtm_type
= type
;
407 duparg2("to", *argv
);
409 get_prefix(&dst
, *argv
, req
.r
.rtm_family
);
410 if (req
.r
.rtm_family
== AF_UNSPEC
) {
411 req
.r
.rtm_family
= dst
.family
;
413 req
.r
.rtm_dst_len
= dst
.bitlen
;
416 addattr_l(&req
.n
, sizeof(req
), RTA_DST
, &dst
.data
, dst
.bytelen
);
430 idx
= xll_name_to_index(d
);
431 addattr32(&req
.n
, sizeof(req
), RTA_OIF
, idx
);
435 if (mxrta
->rta_len
> RTA_LENGTH(0)) {
437 rta_addattr32(mxrta
, sizeof(mxbuf
), RTAX_LOCK
, mxlock
);
439 addattr_l(&req
.n
, sizeof(req
), RTA_METRICS
, RTA_DATA(mxrta
), RTA_PAYLOAD(mxrta
));
442 if (req
.r
.rtm_type
== RTN_LOCAL
|| req
.r
.rtm_type
== RTN_NAT
)
443 req
.r
.rtm_scope
= RT_SCOPE_HOST
;
444 else if (req
.r
.rtm_type
== RTN_BROADCAST
||
445 req
.r
.rtm_type
== RTN_MULTICAST
||
446 req
.r
.rtm_type
== RTN_ANYCAST
)
447 req
.r
.rtm_scope
= RT_SCOPE_LINK
;
448 else if (req
.r
.rtm_type
== RTN_UNICAST
|| req
.r
.rtm_type
== RTN_UNSPEC
) {
449 if (cmd
== RTM_DELROUTE
)
450 req
.r
.rtm_scope
= RT_SCOPE_NOWHERE
;
451 else if (!(ok
& gw_ok
))
452 req
.r
.rtm_scope
= RT_SCOPE_LINK
;
455 if (req
.r
.rtm_family
== AF_UNSPEC
) {
456 req
.r
.rtm_family
= AF_INET
;
459 if (rtnl_talk(&rth
, &req
.n
, 0, 0, NULL
, NULL
, NULL
) < 0) {
466 static int rtnl_rtcache_request(struct rtnl_handle
*rth
, int family
)
472 struct sockaddr_nl nladdr
;
474 memset(&nladdr
, 0, sizeof(nladdr
));
475 memset(&req
, 0, sizeof(req
));
476 nladdr
.nl_family
= AF_NETLINK
;
478 req
.nlh
.nlmsg_len
= sizeof(req
);
480 req
.nlh
.nlmsg_type
= RTM_GETROUTE
;
481 if (NLM_F_ROOT
| NLM_F_REQUEST
)
482 req
.nlh
.nlmsg_flags
= NLM_F_ROOT
| NLM_F_REQUEST
;
483 /*req.nlh.nlmsg_pid = 0; - memset did it already */
484 req
.nlh
.nlmsg_seq
= rth
->dump
= ++rth
->seq
;
485 req
.rtm
.rtm_family
= family
;
487 req
.rtm
.rtm_flags
= RTM_F_CLONED
;
489 return xsendto(rth
->fd
, (void*)&req
, sizeof(req
), (struct sockaddr
*)&nladdr
, sizeof(nladdr
));
492 static void iproute_flush_cache(void)
494 static const char fn
[] ALIGN1
= "/proc/sys/net/ipv4/route/flush";
495 int flush_fd
= open_or_warn(fn
, O_WRONLY
);
501 if (write(flush_fd
, "-1", 2) < 2) {
502 bb_perror_msg("cannot flush routing cache");
508 static void iproute_reset_filter(void)
510 memset(&filter
, 0, sizeof(filter
));
511 filter
.mdst
.bitlen
= -1;
512 filter
.msrc
.bitlen
= -1;
515 /* Return value becomes exitcode. It's okay to not return at all */
516 static int iproute_list_or_flush(char **argv
, int flush
)
518 int do_ipv6
= preferred_family
;
519 struct rtnl_handle rth
;
522 static const char keywords
[] ALIGN1
=
523 /* "ip route list/flush" parameters: */
524 "protocol\0" "dev\0" "oif\0" "iif\0"
525 "via\0" "table\0" "cache\0"
527 /* and possible further keywords */
535 KW_proto
, KW_dev
, KW_oif
, KW_iif
,
536 KW_via
, KW_table
, KW_cache
,
547 iproute_reset_filter();
548 filter
.tb
= RT_TABLE_MAIN
;
551 bb_error_msg_and_die(bb_msg_requires_arg
, "\"ip route flush\"");
554 arg
= index_in_substrings(keywords
, *argv
);
555 if (arg
== KW_proto
) {
558 filter
.protocolmask
= -1;
559 if (rtnl_rtprot_a2n(&prot
, *argv
)) {
560 if (index_in_strings(keywords
, *argv
) != KW_all
)
561 invarg(*argv
, "protocol");
563 filter
.protocolmask
= 0;
565 filter
.protocol
= prot
;
566 } else if (arg
== KW_dev
|| arg
== KW_oif
) {
569 } else if (arg
== KW_iif
) {
572 } else if (arg
== KW_via
) {
574 get_prefix(&filter
.rvia
, *argv
, do_ipv6
);
575 } else if (arg
== KW_table
) { /* table all/cache/main */
577 parm
= index_in_substrings(keywords
, *argv
);
578 if (parm
== KW_cache
)
580 else if (parm
== KW_all
)
582 else if (parm
!= KW_main
) {
583 #if ENABLE_FEATURE_IP_RULE
585 if (rtnl_rttable_a2n(&tid
, *argv
))
586 invarg(*argv
, "table");
589 invarg(*argv
, "table");
592 } else if (arg
== KW_cache
) {
593 /* The command 'ip route flush cache' is used by OpenSWAN.
594 * Assuming it's a synonym for 'ip route flush table cache' */
596 } else if (arg
== KW_from
) {
598 parm
= index_in_substrings(keywords
, *argv
);
599 if (parm
== KW_root
) {
601 get_prefix(&filter
.rsrc
, *argv
, do_ipv6
);
602 } else if (parm
== KW_match
) {
604 get_prefix(&filter
.msrc
, *argv
, do_ipv6
);
606 if (parm
== KW_exact
)
608 get_prefix(&filter
.msrc
, *argv
, do_ipv6
);
609 filter
.rsrc
= filter
.msrc
;
611 } else { /* "to" is the default parameter */
614 arg
= index_in_substrings(keywords
, *argv
);
616 /* parm = arg; - would be more plausible, but we reuse 'arg' here */
617 if (arg
== KW_root
) {
619 get_prefix(&filter
.rdst
, *argv
, do_ipv6
);
620 } else if (arg
== KW_match
) {
622 get_prefix(&filter
.mdst
, *argv
, do_ipv6
);
623 } else { /* "to exact" is the default */
626 get_prefix(&filter
.mdst
, *argv
, do_ipv6
);
627 filter
.rdst
= filter
.mdst
;
633 if (do_ipv6
== AF_UNSPEC
&& filter
.tb
) {
644 idx
= xll_name_to_index(id
);
649 idx
= xll_name_to_index(od
);
656 char flushb
[4096-512];
658 if (filter
.tb
== -1) { /* "flush table cache" */
659 if (do_ipv6
!= AF_INET6
)
660 iproute_flush_cache();
661 if (do_ipv6
== AF_INET
)
665 filter
.flushb
= flushb
;
667 filter
.flushe
= sizeof(flushb
);
671 xrtnl_wilddump_request(&rth
, do_ipv6
, RTM_GETROUTE
);
673 xrtnl_dump_filter(&rth
, print_route
, NULL
);
674 if (filter
.flushed
== 0)
681 if (filter
.tb
!= -1) {
682 xrtnl_wilddump_request(&rth
, do_ipv6
, RTM_GETROUTE
);
683 } else if (rtnl_rtcache_request(&rth
, do_ipv6
) < 0) {
684 bb_perror_msg_and_die("cannot send dump request");
686 xrtnl_dump_filter(&rth
, print_route
, NULL
);
692 /* Return value becomes exitcode. It's okay to not return at all */
693 static int iproute_get(char **argv
)
695 struct rtnl_handle rth
;
705 static const char options
[] ALIGN1
=
706 "from\0""iif\0""oif\0""dev\0""notify\0""connected\0""to\0";
708 memset(&req
, 0, sizeof(req
));
710 iproute_reset_filter();
712 req
.n
.nlmsg_len
= NLMSG_LENGTH(sizeof(struct rtmsg
));
714 req
.n
.nlmsg_flags
= NLM_F_REQUEST
;
716 req
.n
.nlmsg_type
= RTM_GETROUTE
;
717 req
.r
.rtm_family
= preferred_family
;
718 /*req.r.rtm_table = 0; - memset did this already */
719 /*req.r.rtm_protocol = 0;*/
720 /*req.r.rtm_scope = 0;*/
721 /*req.r.rtm_type = 0;*/
722 /*req.r.rtm_src_len = 0;*/
723 /*req.r.rtm_dst_len = 0;*/
724 /*req.r.rtm_tos = 0;*/
727 switch (index_in_strings(options
, *argv
)) {
733 get_prefix(&addr
, *argv
, req
.r
.rtm_family
);
734 if (req
.r
.rtm_family
== AF_UNSPEC
) {
735 req
.r
.rtm_family
= addr
.family
;
738 addattr_l(&req
.n
, sizeof(req
), RTA_SRC
, &addr
.data
, addr
.bytelen
);
740 req
.r
.rtm_src_len
= addr
.bitlen
;
753 req
.r
.rtm_flags
|= RTM_F_NOTIFY
;
755 case 5: /* connected */
763 get_prefix(&addr
, *argv
, req
.r
.rtm_family
);
764 if (req
.r
.rtm_family
== AF_UNSPEC
) {
765 req
.r
.rtm_family
= addr
.family
;
768 addattr_l(&req
.n
, sizeof(req
), RTA_DST
, &addr
.data
, addr
.bytelen
);
770 req
.r
.rtm_dst_len
= addr
.bitlen
;
776 if (req
.r
.rtm_dst_len
== 0) {
777 bb_error_msg_and_die("need at least destination address");
788 idx
= xll_name_to_index(idev
);
789 addattr32(&req
.n
, sizeof(req
), RTA_IIF
, idx
);
792 idx
= xll_name_to_index(odev
);
793 addattr32(&req
.n
, sizeof(req
), RTA_OIF
, idx
);
797 if (req
.r
.rtm_family
== AF_UNSPEC
) {
798 req
.r
.rtm_family
= AF_INET
;
801 if (rtnl_talk(&rth
, &req
.n
, 0, 0, &req
.n
, NULL
, NULL
) < 0) {
805 if (connected
&& !from_ok
) {
806 struct rtmsg
*r
= NLMSG_DATA(&req
.n
);
807 int len
= req
.n
.nlmsg_len
;
808 struct rtattr
* tb
[RTA_MAX
+1];
810 print_route(NULL
, &req
.n
, NULL
);
812 if (req
.n
.nlmsg_type
!= RTM_NEWROUTE
) {
813 bb_error_msg_and_die("not a route?");
815 len
-= NLMSG_LENGTH(sizeof(*r
));
817 bb_error_msg_and_die("wrong len %d", len
);
820 memset(tb
, 0, sizeof(tb
));
821 parse_rtattr(tb
, RTA_MAX
, RTM_RTA(r
), len
);
823 if (tb
[RTA_PREFSRC
]) {
824 tb
[RTA_PREFSRC
]->rta_type
= RTA_SRC
;
825 r
->rtm_src_len
= 8*RTA_PAYLOAD(tb
[RTA_PREFSRC
]);
826 } else if (!tb
[RTA_SRC
]) {
827 bb_error_msg_and_die("failed to connect the route");
829 if (!odev
&& tb
[RTA_OIF
]) {
830 tb
[RTA_OIF
]->rta_type
= 0;
832 if (tb
[RTA_GATEWAY
]) {
833 tb
[RTA_GATEWAY
]->rta_type
= 0;
835 if (!idev
&& tb
[RTA_IIF
]) {
836 tb
[RTA_IIF
]->rta_type
= 0;
838 req
.n
.nlmsg_flags
= NLM_F_REQUEST
;
839 req
.n
.nlmsg_type
= RTM_GETROUTE
;
841 if (rtnl_talk(&rth
, &req
.n
, 0, 0, &req
.n
, NULL
, NULL
) < 0) {
845 print_route(NULL
, &req
.n
, NULL
);
849 /* Return value becomes exitcode. It's okay to not return at all */
850 int do_iproute(char **argv
)
852 static const char ip_route_commands
[] ALIGN1
=
853 /*0-3*/ "add\0""append\0""change\0""chg\0"
854 /*4-7*/ "delete\0""get\0""list\0""show\0"
855 /*8..*/ "prepend\0""replace\0""test\0""flush\0";
858 int cmd
= RTM_NEWROUTE
;
861 return iproute_list_or_flush(argv
, 0);
863 /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */
864 /* It probably means that it is using "first match" rule */
865 command_num
= index_in_substrings(ip_route_commands
, *argv
);
867 switch (command_num
) {
869 flags
= NLM_F_CREATE
|NLM_F_EXCL
;
872 flags
= NLM_F_CREATE
|NLM_F_APPEND
;
876 flags
= NLM_F_REPLACE
;
882 return iproute_get(argv
+1);
885 return iproute_list_or_flush(argv
+1, 0);
886 case 8: /* prepend */
887 flags
= NLM_F_CREATE
;
889 case 9: /* replace */
890 flags
= NLM_F_CREATE
|NLM_F_REPLACE
;
896 return iproute_list_or_flush(argv
+1, 1);
898 bb_error_msg_and_die("unknown command %s", *argv
);
901 return iproute_modify(cmd
, flags
, argv
+1);