mirror of
https://github.com/sheumann/hush.git
synced 2024-11-14 20:09:05 +00:00
b42107f215
Linux kernel, starting from 2.6.19 allows ip table ids to have 32-bit values. In order to preserve compatibility, the old 8-bit field: rtm_table is still in use when table id is lower than 256. Add support for the 32-bit table id (RTA_TABLE attribute) in: - ip route print - ip route modify - ip rule print - ip rule modify Add printing of table ids to ip route. Changes are compatible with the mainline iproute2 utilities. These changes are required for compatibility with ConnMan, which by default uses table ids greater than 255. function old new delta print_route 1588 1637 +49 do_iproute 2187 2222 +35 do_iprule 955 987 +32 print_rule 617 630 +13 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 129/0) Total: 129 bytes Signed-off-by: Lukasz Nowak <lnowak@tycoint.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
967 lines
22 KiB
C
967 lines
22 KiB
C
/* vi: set sw=4 ts=4: */
|
|
/*
|
|
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
|
|
*
|
|
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
|
|
*
|
|
* Changes:
|
|
*
|
|
* Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
|
|
* Kunihiro Ishiguro <kunihiro@zebra.org> 001102: rtnh_ifindex was not initialized
|
|
*/
|
|
|
|
#include "ip_common.h" /* #include "libbb.h" is inside */
|
|
#include "common_bufsiz.h"
|
|
#include "rt_names.h"
|
|
#include "utils.h"
|
|
|
|
#ifndef RTAX_RTTVAR
|
|
#define RTAX_RTTVAR RTAX_HOPS
|
|
#endif
|
|
|
|
|
|
struct filter_t {
|
|
int tb;
|
|
smallint flushed;
|
|
char *flushb;
|
|
int flushp;
|
|
int flushe;
|
|
struct rtnl_handle *rth;
|
|
//int protocol, protocolmask; - write-only fields?!
|
|
//int scope, scopemask; - unused
|
|
//int type; - read-only
|
|
//int typemask; - unused
|
|
//int tos, tosmask; - unused
|
|
int iif;
|
|
int oif;
|
|
//int realm, realmmask; - unused
|
|
//inet_prefix rprefsrc; - read-only
|
|
inet_prefix rvia;
|
|
inet_prefix rdst;
|
|
inet_prefix mdst;
|
|
inet_prefix rsrc;
|
|
inet_prefix msrc;
|
|
} FIX_ALIASING;
|
|
typedef struct filter_t filter_t;
|
|
|
|
#define G_filter (*(filter_t*)bb_common_bufsiz1)
|
|
#define INIT_G() do { setup_common_bufsiz(); } while (0)
|
|
|
|
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 int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM,
|
|
struct nlmsghdr *n, void *arg UNUSED_PARAM)
|
|
{
|
|
struct rtmsg *r = NLMSG_DATA(n);
|
|
int len = n->nlmsg_len;
|
|
struct rtattr *tb[RTA_MAX+1];
|
|
inet_prefix dst;
|
|
inet_prefix src;
|
|
int host_len = -1;
|
|
uint32_t tid;
|
|
|
|
if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) {
|
|
fprintf(stderr, "Not a route: %08x %08x %08x\n",
|
|
n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
|
|
return 0;
|
|
}
|
|
if (G_filter.flushb && n->nlmsg_type != RTM_NEWROUTE)
|
|
return 0;
|
|
len -= NLMSG_LENGTH(sizeof(*r));
|
|
if (len < 0)
|
|
bb_error_msg_and_die("wrong nlmsg len %d", len);
|
|
|
|
memset(tb, 0, sizeof(tb));
|
|
parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
|
|
|
|
if (tb[RTA_TABLE])
|
|
tid = *(uint32_t *)RTA_DATA(tb[RTA_TABLE]);
|
|
else
|
|
tid = r->rtm_table;
|
|
|
|
if (r->rtm_family == AF_INET6)
|
|
host_len = 128;
|
|
else if (r->rtm_family == AF_INET)
|
|
host_len = 32;
|
|
|
|
if (r->rtm_family == AF_INET6) {
|
|
if (G_filter.tb) {
|
|
if (G_filter.tb < 0) {
|
|
if (!(r->rtm_flags & RTM_F_CLONED)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (r->rtm_flags & RTM_F_CLONED) {
|
|
return 0;
|
|
}
|
|
if (G_filter.tb == RT_TABLE_LOCAL) {
|
|
if (r->rtm_type != RTN_LOCAL) {
|
|
return 0;
|
|
}
|
|
} else if (G_filter.tb == RT_TABLE_MAIN) {
|
|
if (r->rtm_type == RTN_LOCAL) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (G_filter.tb > 0 && G_filter.tb != tid) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (G_filter.rdst.family
|
|
&& (r->rtm_family != G_filter.rdst.family || G_filter.rdst.bitlen > r->rtm_dst_len)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.mdst.family
|
|
&& (r->rtm_family != G_filter.mdst.family
|
|
|| (G_filter.mdst.bitlen >= 0 && G_filter.mdst.bitlen < r->rtm_dst_len)
|
|
)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.rsrc.family
|
|
&& (r->rtm_family != G_filter.rsrc.family || G_filter.rsrc.bitlen > r->rtm_src_len)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.msrc.family
|
|
&& (r->rtm_family != G_filter.msrc.family
|
|
|| (G_filter.msrc.bitlen >= 0 && G_filter.msrc.bitlen < r->rtm_src_len)
|
|
)
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
memset(&src, 0, sizeof(src));
|
|
memset(&dst, 0, sizeof(dst));
|
|
|
|
if (tb[RTA_SRC]) {
|
|
src.bitlen = r->rtm_src_len;
|
|
src.bytelen = (r->rtm_family == AF_INET6 ? 16 : 4);
|
|
memcpy(src.data, RTA_DATA(tb[RTA_SRC]), src.bytelen);
|
|
}
|
|
if (tb[RTA_DST]) {
|
|
dst.bitlen = r->rtm_dst_len;
|
|
dst.bytelen = (r->rtm_family == AF_INET6 ? 16 : 4);
|
|
memcpy(dst.data, RTA_DATA(tb[RTA_DST]), dst.bytelen);
|
|
}
|
|
|
|
if (G_filter.rdst.family
|
|
&& inet_addr_match(&dst, &G_filter.rdst, G_filter.rdst.bitlen)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.mdst.family
|
|
&& G_filter.mdst.bitlen >= 0
|
|
&& inet_addr_match(&dst, &G_filter.mdst, r->rtm_dst_len)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.rsrc.family
|
|
&& inet_addr_match(&src, &G_filter.rsrc, G_filter.rsrc.bitlen)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.msrc.family && G_filter.msrc.bitlen >= 0
|
|
&& inet_addr_match(&src, &G_filter.msrc, r->rtm_src_len)
|
|
) {
|
|
return 0;
|
|
}
|
|
if (G_filter.oif != 0) {
|
|
if (!tb[RTA_OIF])
|
|
return 0;
|
|
if (G_filter.oif != *(int*)RTA_DATA(tb[RTA_OIF]))
|
|
return 0;
|
|
}
|
|
|
|
if (G_filter.flushb) {
|
|
struct nlmsghdr *fn;
|
|
|
|
/* We are creating route flush commands */
|
|
|
|
if (r->rtm_family == AF_INET6
|
|
&& r->rtm_dst_len == 0
|
|
&& r->rtm_type == RTN_UNREACHABLE
|
|
&& tb[RTA_PRIORITY]
|
|
&& *(int*)RTA_DATA(tb[RTA_PRIORITY]) == -1
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
|
|
if (flush_update())
|
|
xfunc_die();
|
|
}
|
|
fn = (void*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
|
|
memcpy(fn, n, n->nlmsg_len);
|
|
fn->nlmsg_type = RTM_DELROUTE;
|
|
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 = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* We are printing routes */
|
|
|
|
if (n->nlmsg_type == RTM_DELROUTE) {
|
|
printf("Deleted ");
|
|
}
|
|
if (r->rtm_type != RTN_UNICAST /* && !G_filter.type - always 0 */) {
|
|
printf("%s ", rtnl_rtntype_n2a(r->rtm_type));
|
|
}
|
|
|
|
if (tb[RTA_DST]) {
|
|
if (r->rtm_dst_len != host_len) {
|
|
printf("%s/%u ",
|
|
rt_addr_n2a(r->rtm_family, RTA_DATA(tb[RTA_DST])),
|
|
r->rtm_dst_len
|
|
);
|
|
} else {
|
|
printf("%s ", format_host(r->rtm_family,
|
|
RTA_PAYLOAD(tb[RTA_DST]),
|
|
RTA_DATA(tb[RTA_DST]))
|
|
);
|
|
}
|
|
} else if (r->rtm_dst_len) {
|
|
printf("0/%d ", r->rtm_dst_len);
|
|
} else {
|
|
printf("default ");
|
|
}
|
|
if (tb[RTA_SRC]) {
|
|
if (r->rtm_src_len != host_len) {
|
|
printf("from %s/%u ",
|
|
rt_addr_n2a(r->rtm_family, RTA_DATA(tb[RTA_SRC])),
|
|
r->rtm_src_len
|
|
);
|
|
} else {
|
|
printf("from %s ", format_host(r->rtm_family,
|
|
RTA_PAYLOAD(tb[RTA_SRC]),
|
|
RTA_DATA(tb[RTA_SRC]))
|
|
);
|
|
}
|
|
} else if (r->rtm_src_len) {
|
|
printf("from 0/%u ", r->rtm_src_len);
|
|
}
|
|
if (tb[RTA_GATEWAY] && G_filter.rvia.bitlen != host_len) {
|
|
printf("via %s ", format_host(r->rtm_family,
|
|
RTA_PAYLOAD(tb[RTA_GATEWAY]),
|
|
RTA_DATA(tb[RTA_GATEWAY]))
|
|
);
|
|
}
|
|
if (tb[RTA_OIF]) {
|
|
printf("dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF])));
|
|
}
|
|
#if ENABLE_FEATURE_IP_RULE
|
|
if (tid && tid != RT_TABLE_MAIN && !G_filter.tb)
|
|
printf("table %s ", rtnl_rttable_n2a(tid));
|
|
#endif
|
|
|
|
/* Todo: parse & show "proto kernel", "scope link" here */
|
|
|
|
if (tb[RTA_PREFSRC] && /*G_filter.rprefsrc.bitlen - always 0*/ 0 != host_len) {
|
|
/* Do not use format_host(). It is our local addr
|
|
and symbolic name will not be useful.
|
|
*/
|
|
printf(" src %s ", rt_addr_n2a(r->rtm_family,
|
|
RTA_DATA(tb[RTA_PREFSRC])));
|
|
}
|
|
if (tb[RTA_PRIORITY]) {
|
|
printf(" metric %d ", *(uint32_t*)RTA_DATA(tb[RTA_PRIORITY]));
|
|
}
|
|
if (r->rtm_flags & RTNH_F_DEAD) {
|
|
printf("dead ");
|
|
}
|
|
if (r->rtm_flags & RTNH_F_ONLINK) {
|
|
printf("onlink ");
|
|
}
|
|
if (r->rtm_flags & RTNH_F_PERVASIVE) {
|
|
printf("pervasive ");
|
|
}
|
|
if (r->rtm_flags & RTM_F_NOTIFY) {
|
|
printf("notify ");
|
|
}
|
|
|
|
if (r->rtm_family == AF_INET6) {
|
|
struct rta_cacheinfo *ci = NULL;
|
|
if (tb[RTA_CACHEINFO]) {
|
|
ci = RTA_DATA(tb[RTA_CACHEINFO]);
|
|
}
|
|
if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) {
|
|
if (r->rtm_flags & RTM_F_CLONED) {
|
|
printf("%c cache ", _SL_);
|
|
}
|
|
if (ci->rta_expires) {
|
|
printf(" expires %dsec", ci->rta_expires / get_hz());
|
|
}
|
|
if (ci->rta_error != 0) {
|
|
printf(" error %d", ci->rta_error);
|
|
}
|
|
} else if (ci) {
|
|
if (ci->rta_error != 0)
|
|
printf(" error %d", ci->rta_error);
|
|
}
|
|
}
|
|
if (tb[RTA_IIF] && G_filter.iif == 0) {
|
|
printf(" iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF])));
|
|
}
|
|
bb_putchar('\n');
|
|
return 0;
|
|
}
|
|
|
|
/* Return value becomes exitcode. It's okay to not return at all */
|
|
static int iproute_modify(int cmd, unsigned flags, char **argv)
|
|
{
|
|
static const char keywords[] ALIGN1 =
|
|
"src\0""via\0""mtu\0""lock\0""scope\0""protocol\0"IF_FEATURE_IP_RULE("table\0")
|
|
"dev\0""oif\0""to\0""metric\0""onlink\0";
|
|
enum {
|
|
ARG_src,
|
|
ARG_via,
|
|
ARG_mtu, PARM_lock,
|
|
ARG_scope,
|
|
ARG_protocol,
|
|
IF_FEATURE_IP_RULE(ARG_table,)
|
|
ARG_dev,
|
|
ARG_oif,
|
|
ARG_to,
|
|
ARG_metric,
|
|
ARG_onlink,
|
|
};
|
|
enum {
|
|
gw_ok = 1 << 0,
|
|
dst_ok = 1 << 1,
|
|
proto_ok = 1 << 2,
|
|
type_ok = 1 << 3
|
|
};
|
|
struct rtnl_handle rth;
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct rtmsg r;
|
|
char buf[1024];
|
|
} req;
|
|
char mxbuf[256];
|
|
struct rtattr * mxrta = (void*)mxbuf;
|
|
unsigned mxlock = 0;
|
|
char *d = NULL;
|
|
smalluint ok = 0;
|
|
smalluint scope_ok = 0;
|
|
int arg;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
|
|
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST | flags;
|
|
req.n.nlmsg_type = cmd;
|
|
req.r.rtm_family = preferred_family;
|
|
if (RT_TABLE_MAIN != 0) /* if it is zero, memset already did it */
|
|
req.r.rtm_table = RT_TABLE_MAIN;
|
|
if (RT_SCOPE_NOWHERE != 0)
|
|
req.r.rtm_scope = RT_SCOPE_NOWHERE;
|
|
|
|
if (cmd != RTM_DELROUTE) {
|
|
req.r.rtm_scope = RT_SCOPE_UNIVERSE;
|
|
if (RTPROT_BOOT != 0)
|
|
req.r.rtm_protocol = RTPROT_BOOT;
|
|
if (RTN_UNICAST != 0)
|
|
req.r.rtm_type = RTN_UNICAST;
|
|
}
|
|
|
|
mxrta->rta_type = RTA_METRICS;
|
|
mxrta->rta_len = RTA_LENGTH(0);
|
|
|
|
while (*argv) {
|
|
arg = index_in_substrings(keywords, *argv);
|
|
if (arg == ARG_src) {
|
|
inet_prefix addr;
|
|
NEXT_ARG();
|
|
get_addr(&addr, *argv, req.r.rtm_family);
|
|
if (req.r.rtm_family == AF_UNSPEC)
|
|
req.r.rtm_family = addr.family;
|
|
addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &addr.data, addr.bytelen);
|
|
} else if (arg == ARG_via) {
|
|
inet_prefix addr;
|
|
ok |= gw_ok;
|
|
NEXT_ARG();
|
|
get_addr(&addr, *argv, req.r.rtm_family);
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = addr.family;
|
|
}
|
|
addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data, addr.bytelen);
|
|
} else if (arg == ARG_mtu) {
|
|
unsigned mtu;
|
|
NEXT_ARG();
|
|
if (index_in_strings(keywords, *argv) == PARM_lock) {
|
|
mxlock |= (1 << RTAX_MTU);
|
|
NEXT_ARG();
|
|
}
|
|
mtu = get_unsigned(*argv, "mtu");
|
|
rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu);
|
|
} else if (arg == ARG_scope) {
|
|
uint32_t scope;
|
|
NEXT_ARG();
|
|
if (rtnl_rtscope_a2n(&scope, *argv))
|
|
invarg_1_to_2(*argv, "scope");
|
|
req.r.rtm_scope = scope;
|
|
scope_ok = 1;
|
|
} else if (arg == ARG_protocol) {
|
|
uint32_t prot;
|
|
NEXT_ARG();
|
|
if (rtnl_rtprot_a2n(&prot, *argv))
|
|
invarg_1_to_2(*argv, "protocol");
|
|
req.r.rtm_protocol = prot;
|
|
ok |= proto_ok;
|
|
#if ENABLE_FEATURE_IP_RULE
|
|
} else if (arg == ARG_table) {
|
|
uint32_t tid;
|
|
NEXT_ARG();
|
|
if (rtnl_rttable_a2n(&tid, *argv))
|
|
invarg_1_to_2(*argv, "table");
|
|
if (tid < 256)
|
|
req.r.rtm_table = tid;
|
|
else {
|
|
req.r.rtm_table = RT_TABLE_UNSPEC;
|
|
addattr32(&req.n, sizeof(req), RTA_TABLE, tid);
|
|
}
|
|
#endif
|
|
} else if (arg == ARG_dev || arg == ARG_oif) {
|
|
NEXT_ARG();
|
|
d = *argv;
|
|
} else if (arg == ARG_metric) {
|
|
uint32_t metric;
|
|
NEXT_ARG();
|
|
metric = get_u32(*argv, "metric");
|
|
addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric);
|
|
} else if (arg == ARG_onlink) {
|
|
req.r.rtm_flags |= RTNH_F_ONLINK;
|
|
} else {
|
|
int type;
|
|
inet_prefix dst;
|
|
|
|
if (arg == ARG_to) {
|
|
NEXT_ARG();
|
|
}
|
|
if ((**argv < '0' || **argv > '9')
|
|
&& rtnl_rtntype_a2n(&type, *argv) == 0
|
|
) {
|
|
NEXT_ARG();
|
|
req.r.rtm_type = type;
|
|
ok |= type_ok;
|
|
}
|
|
|
|
if (ok & dst_ok) {
|
|
duparg2("to", *argv);
|
|
}
|
|
get_prefix(&dst, *argv, req.r.rtm_family);
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = dst.family;
|
|
}
|
|
req.r.rtm_dst_len = dst.bitlen;
|
|
ok |= dst_ok;
|
|
if (dst.bytelen) {
|
|
addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen);
|
|
}
|
|
}
|
|
argv++;
|
|
}
|
|
|
|
xrtnl_open(&rth);
|
|
|
|
if (d) {
|
|
int idx;
|
|
|
|
ll_init_map(&rth);
|
|
|
|
if (d) {
|
|
idx = xll_name_to_index(d);
|
|
addattr32(&req.n, sizeof(req), RTA_OIF, idx);
|
|
}
|
|
}
|
|
|
|
if (mxrta->rta_len > RTA_LENGTH(0)) {
|
|
if (mxlock) {
|
|
rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock);
|
|
}
|
|
addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta));
|
|
}
|
|
|
|
if (!scope_ok) {
|
|
if (req.r.rtm_type == RTN_LOCAL || req.r.rtm_type == RTN_NAT)
|
|
req.r.rtm_scope = RT_SCOPE_HOST;
|
|
else
|
|
if (req.r.rtm_type == RTN_BROADCAST
|
|
|| req.r.rtm_type == RTN_MULTICAST
|
|
|| req.r.rtm_type == RTN_ANYCAST
|
|
) {
|
|
req.r.rtm_scope = RT_SCOPE_LINK;
|
|
}
|
|
else if (req.r.rtm_type == RTN_UNICAST || req.r.rtm_type == RTN_UNSPEC) {
|
|
if (cmd == RTM_DELROUTE)
|
|
req.r.rtm_scope = RT_SCOPE_NOWHERE;
|
|
else if (!(ok & gw_ok))
|
|
req.r.rtm_scope = RT_SCOPE_LINK;
|
|
}
|
|
}
|
|
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = AF_INET;
|
|
}
|
|
|
|
if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) {
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtnl_rtcache_request(struct rtnl_handle *rth, int family)
|
|
{
|
|
struct {
|
|
struct nlmsghdr nlh;
|
|
struct rtmsg rtm;
|
|
} req;
|
|
struct sockaddr_nl nladdr;
|
|
|
|
memset(&nladdr, 0, sizeof(nladdr));
|
|
memset(&req, 0, sizeof(req));
|
|
nladdr.nl_family = AF_NETLINK;
|
|
|
|
req.nlh.nlmsg_len = sizeof(req);
|
|
if (RTM_GETROUTE)
|
|
req.nlh.nlmsg_type = RTM_GETROUTE;
|
|
if (NLM_F_ROOT | NLM_F_REQUEST)
|
|
req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST;
|
|
/*req.nlh.nlmsg_pid = 0; - memset did it already */
|
|
req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
|
|
req.rtm.rtm_family = family;
|
|
if (RTM_F_CLONED)
|
|
req.rtm.rtm_flags = RTM_F_CLONED;
|
|
|
|
return xsendto(rth->fd, (void*)&req, sizeof(req), (struct sockaddr*)&nladdr, sizeof(nladdr));
|
|
}
|
|
|
|
static void iproute_flush_cache(void)
|
|
{
|
|
static const char fn[] ALIGN1 = "/proc/sys/net/ipv4/route/flush";
|
|
int flush_fd = open_or_warn(fn, O_WRONLY);
|
|
|
|
if (flush_fd < 0) {
|
|
return;
|
|
}
|
|
|
|
if (write(flush_fd, "-1", 2) < 2) {
|
|
bb_perror_msg("can't flush routing cache");
|
|
return;
|
|
}
|
|
close(flush_fd);
|
|
}
|
|
|
|
static void iproute_reset_filter(void)
|
|
{
|
|
memset(&G_filter, 0, sizeof(G_filter));
|
|
G_filter.mdst.bitlen = -1;
|
|
G_filter.msrc.bitlen = -1;
|
|
}
|
|
|
|
/* Return value becomes exitcode. It's okay to not return at all */
|
|
static int iproute_list_or_flush(char **argv, int flush)
|
|
{
|
|
int do_ipv6 = preferred_family;
|
|
struct rtnl_handle rth;
|
|
char *id = NULL;
|
|
char *od = NULL;
|
|
static const char keywords[] ALIGN1 =
|
|
/* "ip route list/flush" parameters: */
|
|
"protocol\0" "dev\0" "oif\0" "iif\0"
|
|
"via\0" "table\0" "cache\0"
|
|
"from\0" "to\0"
|
|
/* and possible further keywords */
|
|
"all\0"
|
|
"root\0"
|
|
"match\0"
|
|
"exact\0"
|
|
"main\0"
|
|
;
|
|
enum {
|
|
KW_proto, KW_dev, KW_oif, KW_iif,
|
|
KW_via, KW_table, KW_cache,
|
|
KW_from, KW_to,
|
|
/* */
|
|
KW_all,
|
|
KW_root,
|
|
KW_match,
|
|
KW_exact,
|
|
KW_main,
|
|
};
|
|
int arg, parm;
|
|
|
|
iproute_reset_filter();
|
|
G_filter.tb = RT_TABLE_MAIN;
|
|
|
|
if (flush && !*argv)
|
|
bb_error_msg_and_die(bb_msg_requires_arg, "\"ip route flush\"");
|
|
|
|
while (*argv) {
|
|
arg = index_in_substrings(keywords, *argv);
|
|
if (arg == KW_proto) {
|
|
uint32_t prot = 0;
|
|
NEXT_ARG();
|
|
//G_filter.protocolmask = -1;
|
|
if (rtnl_rtprot_a2n(&prot, *argv)) {
|
|
if (index_in_strings(keywords, *argv) != KW_all)
|
|
invarg_1_to_2(*argv, "protocol");
|
|
prot = 0;
|
|
//G_filter.protocolmask = 0;
|
|
}
|
|
//G_filter.protocol = prot;
|
|
} else if (arg == KW_dev || arg == KW_oif) {
|
|
NEXT_ARG();
|
|
od = *argv;
|
|
} else if (arg == KW_iif) {
|
|
NEXT_ARG();
|
|
id = *argv;
|
|
} else if (arg == KW_via) {
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.rvia, *argv, do_ipv6);
|
|
} else if (arg == KW_table) { /* table all/cache/main */
|
|
NEXT_ARG();
|
|
parm = index_in_substrings(keywords, *argv);
|
|
if (parm == KW_cache)
|
|
G_filter.tb = -1;
|
|
else if (parm == KW_all)
|
|
G_filter.tb = 0;
|
|
else if (parm != KW_main) {
|
|
#if ENABLE_FEATURE_IP_RULE
|
|
uint32_t tid;
|
|
if (rtnl_rttable_a2n(&tid, *argv))
|
|
invarg_1_to_2(*argv, "table");
|
|
G_filter.tb = tid;
|
|
#else
|
|
invarg_1_to_2(*argv, "table");
|
|
#endif
|
|
}
|
|
} else if (arg == KW_cache) {
|
|
/* The command 'ip route flush cache' is used by OpenSWAN.
|
|
* Assuming it's a synonym for 'ip route flush table cache' */
|
|
G_filter.tb = -1;
|
|
} else if (arg == KW_from) {
|
|
NEXT_ARG();
|
|
parm = index_in_substrings(keywords, *argv);
|
|
if (parm == KW_root) {
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.rsrc, *argv, do_ipv6);
|
|
} else if (parm == KW_match) {
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.msrc, *argv, do_ipv6);
|
|
} else {
|
|
if (parm == KW_exact)
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.msrc, *argv, do_ipv6);
|
|
G_filter.rsrc = G_filter.msrc;
|
|
}
|
|
} else { /* "to" is the default parameter */
|
|
if (arg == KW_to) {
|
|
NEXT_ARG();
|
|
arg = index_in_substrings(keywords, *argv);
|
|
}
|
|
/* parm = arg; - would be more plausible, but we reuse 'arg' here */
|
|
if (arg == KW_root) {
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.rdst, *argv, do_ipv6);
|
|
} else if (arg == KW_match) {
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.mdst, *argv, do_ipv6);
|
|
} else { /* "to exact" is the default */
|
|
if (arg == KW_exact)
|
|
NEXT_ARG();
|
|
get_prefix(&G_filter.mdst, *argv, do_ipv6);
|
|
G_filter.rdst = G_filter.mdst;
|
|
}
|
|
}
|
|
argv++;
|
|
}
|
|
|
|
if (do_ipv6 == AF_UNSPEC && G_filter.tb) {
|
|
do_ipv6 = AF_INET;
|
|
}
|
|
|
|
xrtnl_open(&rth);
|
|
ll_init_map(&rth);
|
|
|
|
if (id || od) {
|
|
int idx;
|
|
|
|
if (id) {
|
|
idx = xll_name_to_index(id);
|
|
G_filter.iif = idx;
|
|
}
|
|
if (od) {
|
|
idx = xll_name_to_index(od);
|
|
G_filter.oif = idx;
|
|
}
|
|
}
|
|
|
|
if (flush) {
|
|
char flushb[4096-512];
|
|
|
|
if (G_filter.tb == -1) { /* "flush table cache" */
|
|
if (do_ipv6 != AF_INET6)
|
|
iproute_flush_cache();
|
|
if (do_ipv6 == AF_INET)
|
|
return 0;
|
|
}
|
|
|
|
G_filter.flushb = flushb;
|
|
G_filter.flushp = 0;
|
|
G_filter.flushe = sizeof(flushb);
|
|
G_filter.rth = &rth;
|
|
|
|
for (;;) {
|
|
xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
|
|
G_filter.flushed = 0;
|
|
xrtnl_dump_filter(&rth, print_route, NULL);
|
|
if (G_filter.flushed == 0)
|
|
return 0;
|
|
if (flush_update())
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (G_filter.tb != -1) {
|
|
xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
|
|
} else if (rtnl_rtcache_request(&rth, do_ipv6) < 0) {
|
|
bb_perror_msg_and_die("can't send dump request");
|
|
}
|
|
xrtnl_dump_filter(&rth, print_route, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Return value becomes exitcode. It's okay to not return at all */
|
|
static int iproute_get(char **argv)
|
|
{
|
|
struct rtnl_handle rth;
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct rtmsg r;
|
|
char buf[1024];
|
|
} req;
|
|
char *idev = NULL;
|
|
char *odev = NULL;
|
|
bool connected = 0;
|
|
bool from_ok = 0;
|
|
static const char options[] ALIGN1 =
|
|
"from\0""iif\0""oif\0""dev\0""notify\0""connected\0""to\0";
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
|
|
iproute_reset_filter();
|
|
|
|
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
|
if (NLM_F_REQUEST)
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
if (RTM_GETROUTE)
|
|
req.n.nlmsg_type = RTM_GETROUTE;
|
|
req.r.rtm_family = preferred_family;
|
|
/*req.r.rtm_table = 0; - memset did this already */
|
|
/*req.r.rtm_protocol = 0;*/
|
|
/*req.r.rtm_scope = 0;*/
|
|
/*req.r.rtm_type = 0;*/
|
|
/*req.r.rtm_src_len = 0;*/
|
|
/*req.r.rtm_dst_len = 0;*/
|
|
/*req.r.rtm_tos = 0;*/
|
|
|
|
while (*argv) {
|
|
switch (index_in_strings(options, *argv)) {
|
|
case 0: /* from */
|
|
{
|
|
inet_prefix addr;
|
|
NEXT_ARG();
|
|
from_ok = 1;
|
|
get_prefix(&addr, *argv, req.r.rtm_family);
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = addr.family;
|
|
}
|
|
if (addr.bytelen) {
|
|
addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen);
|
|
}
|
|
req.r.rtm_src_len = addr.bitlen;
|
|
break;
|
|
}
|
|
case 1: /* iif */
|
|
NEXT_ARG();
|
|
idev = *argv;
|
|
break;
|
|
case 2: /* oif */
|
|
case 3: /* dev */
|
|
NEXT_ARG();
|
|
odev = *argv;
|
|
break;
|
|
case 4: /* notify */
|
|
req.r.rtm_flags |= RTM_F_NOTIFY;
|
|
break;
|
|
case 5: /* connected */
|
|
connected = 1;
|
|
break;
|
|
case 6: /* to */
|
|
NEXT_ARG();
|
|
default:
|
|
{
|
|
inet_prefix addr;
|
|
get_prefix(&addr, *argv, req.r.rtm_family);
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = addr.family;
|
|
}
|
|
if (addr.bytelen) {
|
|
addattr_l(&req.n, sizeof(req), RTA_DST, &addr.data, addr.bytelen);
|
|
}
|
|
req.r.rtm_dst_len = addr.bitlen;
|
|
}
|
|
}
|
|
argv++;
|
|
}
|
|
|
|
if (req.r.rtm_dst_len == 0) {
|
|
bb_error_msg_and_die("need at least destination address");
|
|
}
|
|
|
|
xrtnl_open(&rth);
|
|
|
|
ll_init_map(&rth);
|
|
|
|
if (idev || odev) {
|
|
int idx;
|
|
|
|
if (idev) {
|
|
idx = xll_name_to_index(idev);
|
|
addattr32(&req.n, sizeof(req), RTA_IIF, idx);
|
|
}
|
|
if (odev) {
|
|
idx = xll_name_to_index(odev);
|
|
addattr32(&req.n, sizeof(req), RTA_OIF, idx);
|
|
}
|
|
}
|
|
|
|
if (req.r.rtm_family == AF_UNSPEC) {
|
|
req.r.rtm_family = AF_INET;
|
|
}
|
|
|
|
if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) {
|
|
return 2;
|
|
}
|
|
|
|
if (connected && !from_ok) {
|
|
struct rtmsg *r = NLMSG_DATA(&req.n);
|
|
int len = req.n.nlmsg_len;
|
|
struct rtattr * tb[RTA_MAX+1];
|
|
|
|
print_route(NULL, &req.n, NULL);
|
|
|
|
if (req.n.nlmsg_type != RTM_NEWROUTE) {
|
|
bb_error_msg_and_die("not a route?");
|
|
}
|
|
len -= NLMSG_LENGTH(sizeof(*r));
|
|
if (len < 0) {
|
|
bb_error_msg_and_die("wrong len %d", len);
|
|
}
|
|
|
|
memset(tb, 0, sizeof(tb));
|
|
parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
|
|
|
|
if (tb[RTA_PREFSRC]) {
|
|
tb[RTA_PREFSRC]->rta_type = RTA_SRC;
|
|
r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]);
|
|
} else if (!tb[RTA_SRC]) {
|
|
bb_error_msg_and_die("can't connect the route");
|
|
}
|
|
if (!odev && tb[RTA_OIF]) {
|
|
tb[RTA_OIF]->rta_type = 0;
|
|
}
|
|
if (tb[RTA_GATEWAY]) {
|
|
tb[RTA_GATEWAY]->rta_type = 0;
|
|
}
|
|
if (!idev && tb[RTA_IIF]) {
|
|
tb[RTA_IIF]->rta_type = 0;
|
|
}
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
req.n.nlmsg_type = RTM_GETROUTE;
|
|
|
|
if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) {
|
|
return 2;
|
|
}
|
|
}
|
|
print_route(NULL, &req.n, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/* Return value becomes exitcode. It's okay to not return at all */
|
|
int FAST_FUNC do_iproute(char **argv)
|
|
{
|
|
static const char ip_route_commands[] ALIGN1 =
|
|
/*0-3*/ "add\0""append\0""change\0""chg\0"
|
|
/*4-7*/ "delete\0""get\0""list\0""show\0"
|
|
/*8..*/ "prepend\0""replace\0""test\0""flush\0";
|
|
int command_num;
|
|
unsigned flags = 0;
|
|
int cmd = RTM_NEWROUTE;
|
|
|
|
INIT_G();
|
|
|
|
if (!*argv)
|
|
return iproute_list_or_flush(argv, 0);
|
|
|
|
/* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */
|
|
/* It probably means that it is using "first match" rule */
|
|
command_num = index_in_substrings(ip_route_commands, *argv);
|
|
|
|
switch (command_num) {
|
|
case 0: /* add */
|
|
flags = NLM_F_CREATE|NLM_F_EXCL;
|
|
break;
|
|
case 1: /* append */
|
|
flags = NLM_F_CREATE|NLM_F_APPEND;
|
|
break;
|
|
case 2: /* change */
|
|
case 3: /* chg */
|
|
flags = NLM_F_REPLACE;
|
|
break;
|
|
case 4: /* delete */
|
|
cmd = RTM_DELROUTE;
|
|
break;
|
|
case 5: /* get */
|
|
return iproute_get(argv+1);
|
|
case 6: /* list */
|
|
case 7: /* show */
|
|
return iproute_list_or_flush(argv+1, 0);
|
|
case 8: /* prepend */
|
|
flags = NLM_F_CREATE;
|
|
break;
|
|
case 9: /* replace */
|
|
flags = NLM_F_CREATE|NLM_F_REPLACE;
|
|
break;
|
|
case 10: /* test */
|
|
flags = NLM_F_EXCL;
|
|
break;
|
|
case 11: /* flush */
|
|
return iproute_list_or_flush(argv+1, 1);
|
|
default:
|
|
invarg_1_to_2(*argv, applet_name);
|
|
}
|
|
|
|
return iproute_modify(cmd, flags, argv+1);
|
|
}
|