From 2750252d7e850793a7d50987abb974141eb8525e Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 4 Oct 2018 14:07:15 -0700 Subject: [PATCH 01/12] libnetlink: dump extack string in done message Print any extack message that has been appended to a NLMSG_DONE message. To avoid duplication, move the existing print code to a new helper. Signed-off-by: David Ahern --- lib/libnetlink.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 95457109..e28e1ab6 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -67,6 +67,14 @@ static int err_attr_cb(const struct nlattr *attr, void *data) return MNL_CB_OK; } +static void print_ext_ack_msg(bool is_err, const char *msg) +{ + fprintf(stderr, "%s: %s", is_err ? "Error" : "Warning", msg); + if (msg[strlen(msg) - 1] != '.') + fprintf(stderr, "."); + fprintf(stderr, "\n"); +} + /* dump netlink extended ack error message */ int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn) { @@ -108,12 +116,29 @@ int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn) if (msg && *msg != '\0') { bool is_err = !!err->error; - fprintf(stderr, "%s: %s", - is_err ? "Error" : "Warning", msg); - if (msg[strlen(msg) - 1] != '.') - fprintf(stderr, "."); - fprintf(stderr, "\n"); + print_ext_ack_msg(is_err, msg); + return is_err ? 1 : 0; + } + return 0; +} + +static int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error) +{ + struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; + unsigned int hlen = sizeof(int); + const char *msg = NULL; + + if (mnl_attr_parse(nlh, hlen, err_attr_cb, tb) != MNL_CB_OK) + return 0; + + if (tb[NLMSGERR_ATTR_MSG]) + msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]); + + if (msg && *msg != '\0') { + bool is_err = !!error; + + print_ext_ack_msg(is_err, msg); return is_err ? 1 : 0; } @@ -127,6 +152,11 @@ int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn) { return 0; } + +static int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error) +{ + return 0; +} #endif void rtnl_close(struct rtnl_handle *rth) @@ -512,6 +542,10 @@ static int rtnl_dump_done(struct nlmsghdr *h) } if (len < 0) { + /* check for any messages returned from kernel */ + if (nl_dump_ext_ack_done(h, len)) + return len; + errno = -len; switch (errno) { case ENOENT: From 92e03242c4c0a5c6ac3571153dc7323b0b45a430 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Fri, 19 Oct 2018 15:34:44 -0700 Subject: [PATCH 02/12] libnetlink: Use NLMSG_LENGTH to set nlmsg_len Change nlmsg_len from sizeof(req) to use NLMSG_LENGTH on the header. 2 of the inner headers are not 4-byte aligned, so add a 0-length buf after the header with the __aligned(NLMSG_ALIGNTO) to ensure the size of the request is large enough. Use NLMSG_ALIGN in NLMSG_LENGTH to set nlmsg_len. Signed-off-by: David Ahern --- lib/libnetlink.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/libnetlink.c b/lib/libnetlink.c index e28e1ab6..8ab2355f 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -28,6 +28,8 @@ #include "libnetlink.h" +#define __aligned(x) __attribute__((aligned(x))) + #ifndef SOL_NETLINK #define SOL_NETLINK 270 #endif @@ -238,7 +240,7 @@ int rtnl_addrdump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct ifaddrmsg ifm; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), .nlh.nlmsg_type = RTM_GETADDR, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -254,7 +256,7 @@ int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct ifaddrlblmsg ifal; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrlblmsg)), .nlh.nlmsg_type = RTM_GETADDRLABEL, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -270,7 +272,7 @@ int rtnl_routedump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct rtmsg rtm; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), .nlh.nlmsg_type = RTM_GETROUTE, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -286,7 +288,7 @@ int rtnl_ruledump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct fib_rule_hdr frh; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)), .nlh.nlmsg_type = RTM_GETRULE, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -302,7 +304,7 @@ int rtnl_neighdump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct ndmsg ndm; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), .nlh.nlmsg_type = RTM_GETNEIGH, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -318,7 +320,7 @@ int rtnl_neightbldump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct ndtmsg ndtmsg; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndtmsg)), .nlh.nlmsg_type = RTM_GETNEIGHTBL, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -334,7 +336,7 @@ int rtnl_mdbdump_req(struct rtnl_handle *rth, int family) struct nlmsghdr nlh; struct br_port_msg bpm; } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg)), .nlh.nlmsg_type = RTM_GETMDB, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -349,8 +351,9 @@ int rtnl_netconfdump_req(struct rtnl_handle *rth, int family) struct { struct nlmsghdr nlh; struct netconfmsg ncm; + char buf[0] __aligned(NLMSG_ALIGNTO); } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct netconfmsg))), .nlh.nlmsg_type = RTM_GETNETCONF, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -365,8 +368,9 @@ int rtnl_nsiddump_req(struct rtnl_handle *rth, int family) struct { struct nlmsghdr nlh; struct rtgenmsg rtm; + char buf[0] __aligned(NLMSG_ALIGNTO); } req = { - .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct rtgenmsg))), .nlh.nlmsg_type = RTM_GETNSID, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, @@ -388,7 +392,7 @@ int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int family, struct nlmsghdr nlh; struct ifinfomsg ifm; /* attribute has to be NLMSG aligned */ - struct rtattr ext_req __attribute__ ((aligned(NLMSG_ALIGNTO))); + struct rtattr ext_req __aligned(NLMSG_ALIGNTO); __u32 ext_filter_mask; } req = { .nlh.nlmsg_len = sizeof(req), From d97b16b2c90614b784f045f070f4326f5a3b5eaa Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 4 Oct 2018 14:12:39 -0700 Subject: [PATCH 03/12] libnetlink: linkdump_req: Only AF_UNSPEC family expects an ext_filter_mask Only AF_UNSPEC handled by rtnl_dump_ifinfo expects an ext_filter_mask on a dump request. Update the linkdump request functions to only set and send ext_filter_mask for AF_UNSPEC. Signed-off-by: David Ahern --- lib/libnetlink.c | 103 ++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 8ab2355f..b9c37fd3 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -380,41 +380,11 @@ int rtnl_nsiddump_req(struct rtnl_handle *rth, int family) return send(rth->fd, &req, sizeof(req), 0); } -int rtnl_linkdump_req(struct rtnl_handle *rth, int family) -{ - return rtnl_linkdump_req_filter(rth, family, RTEXT_FILTER_VF); -} - -int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int family, - __u32 filt_mask) +static int __rtnl_linkdump_req(struct rtnl_handle *rth, int family) { struct { struct nlmsghdr nlh; struct ifinfomsg ifm; - /* attribute has to be NLMSG aligned */ - struct rtattr ext_req __aligned(NLMSG_ALIGNTO); - __u32 ext_filter_mask; - } req = { - .nlh.nlmsg_len = sizeof(req), - .nlh.nlmsg_type = RTM_GETLINK, - .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, - .nlh.nlmsg_seq = rth->dump = ++rth->seq, - .ifm.ifi_family = family, - .ext_req.rta_type = IFLA_EXT_MASK, - .ext_req.rta_len = RTA_LENGTH(sizeof(__u32)), - .ext_filter_mask = filt_mask, - }; - - return send(rth->fd, &req, sizeof(req), 0); -} - -int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int family, - req_filter_fn_t filter_fn) -{ - struct { - struct nlmsghdr nlh; - struct ifinfomsg ifm; - char buf[1024]; } req = { .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), .nlh.nlmsg_type = RTM_GETLINK, @@ -422,16 +392,73 @@ int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int family, .nlh.nlmsg_seq = rth->dump = ++rth->seq, .ifm.ifi_family = family, }; - int err; - if (!filter_fn) - return -EINVAL; + return send(rth->fd, &req, sizeof(req), 0); +} - err = filter_fn(&req.nlh, sizeof(req)); - if (err) - return err; +int rtnl_linkdump_req(struct rtnl_handle *rth, int family) +{ + if (family == AF_UNSPEC) + return rtnl_linkdump_req_filter(rth, family, RTEXT_FILTER_VF); - return send(rth->fd, &req, req.nlh.nlmsg_len, 0); + return __rtnl_linkdump_req(rth, family); +} + +int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int family, + __u32 filt_mask) +{ + if (family == AF_UNSPEC) { + struct { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + /* attribute has to be NLMSG aligned */ + struct rtattr ext_req __aligned(NLMSG_ALIGNTO); + __u32 ext_filter_mask; + } req = { + .nlh.nlmsg_len = sizeof(req), + .nlh.nlmsg_type = RTM_GETLINK, + .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlh.nlmsg_seq = rth->dump = ++rth->seq, + .ifm.ifi_family = family, + .ext_req.rta_type = IFLA_EXT_MASK, + .ext_req.rta_len = RTA_LENGTH(sizeof(__u32)), + .ext_filter_mask = filt_mask, + }; + + return send(rth->fd, &req, sizeof(req), 0); + } + + return __rtnl_linkdump_req(rth, family); +} + +int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) +{ + if (family == AF_UNSPEC) { + struct { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[1024]; + } req = { + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .nlh.nlmsg_type = RTM_GETLINK, + .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlh.nlmsg_seq = rth->dump = ++rth->seq, + .ifm.ifi_family = family, + }; + int err; + + if (!filter_fn) + return -EINVAL; + + err = filter_fn(&req.nlh, sizeof(req)); + if (err) + return err; + + return send(rth->fd, &req, req.nlh.nlmsg_len, 0); + } + + return __rtnl_linkdump_req(rth, family); } int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) From 43fd93ae46ad223dd558e00169a05c9b5b617b64 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Tue, 2 Oct 2018 19:08:15 -0700 Subject: [PATCH 04/12] ip route: Remove rtnl_rtcache_request Add a filter option to rtnl_routedump_req and use it to set rtm_flags removing the need for rtnl_rtcache_request for dump requests. Signed-off-by: David Ahern --- include/libnetlink.h | 7 ++++--- ip/ipmroute.c | 2 +- ip/iproute.c | 43 ++++++++++++++----------------------------- lib/libnetlink.c | 12 +++++++++++- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/include/libnetlink.h b/include/libnetlink.h index 138840d5..b0051f39 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -47,11 +47,14 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, void rtnl_close(struct rtnl_handle *rth); +typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen); + int rtnl_addrdump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); -int rtnl_routedump_req(struct rtnl_handle *rth, int family) +int rtnl_routedump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) __attribute__((warn_unused_result)); int rtnl_ruledump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); @@ -71,8 +74,6 @@ int rtnl_linkdump_req(struct rtnl_handle *rth, int fam) int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) __attribute__((warn_unused_result)); -typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen); - int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int fam, req_filter_fn_t fn) __attribute__((warn_unused_result)); diff --git a/ip/ipmroute.c b/ip/ipmroute.c index 4d8867d3..de7a035f 100644 --- a/ip/ipmroute.c +++ b/ip/ipmroute.c @@ -283,7 +283,7 @@ static int mroute_list(int argc, char **argv) filter.iif = idx; } - if (rtnl_routedump_req(&rth, filter.af) < 0) { + if (rtnl_routedump_req(&rth, filter.af, NULL) < 0) { perror("Cannot send dump request"); return 1; } diff --git a/ip/iproute.c b/ip/iproute.c index d3472469..3c0be0a9 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -1535,24 +1535,6 @@ static int iproute_modify(int cmd, unsigned int flags, int argc, char **argv) return 0; } -static int rtnl_rtcache_request(struct rtnl_handle *rth, int family) -{ - struct { - struct nlmsghdr nlh; - struct rtmsg rtm; - } req = { - .nlh.nlmsg_len = sizeof(req), - .nlh.nlmsg_type = RTM_GETROUTE, - .nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST, - .nlh.nlmsg_seq = rth->dump = ++rth->seq, - .rtm.rtm_family = family, - .rtm.rtm_flags = RTM_F_CLONED, - }; - struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK }; - - return sendto(rth->fd, (void *)&req, sizeof(req), 0, (struct sockaddr *)&nladdr, sizeof(nladdr)); -} - static int iproute_flush_cache(void) { #define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush" @@ -1644,7 +1626,7 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) filter.flushe = sizeof(flushb); for (;;) { - if (rtnl_routedump_req(&rth, do_ipv6) < 0) { + if (rtnl_routedump_req(&rth, do_ipv6, NULL) < 0) { perror("Cannot send dump request"); return -2; } @@ -1684,6 +1666,16 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) } } +static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen) +{ + struct rtmsg *rtm = NLMSG_DATA(nlh); + + if (filter.cloned) + rtm->rtm_flags |= RTM_F_CLONED; + + return 0; +} + static int iproute_list_flush_or_save(int argc, char **argv, int action) { int do_ipv6 = preferred_family; @@ -1889,16 +1881,9 @@ static int iproute_list_flush_or_save(int argc, char **argv, int action) if (action == IPROUTE_FLUSH) return iproute_flush(do_ipv6, filter_fn); - if (!filter.cloned) { - if (rtnl_routedump_req(&rth, do_ipv6) < 0) { - perror("Cannot send dump request"); - return -2; - } - } else { - if (rtnl_rtcache_request(&rth, do_ipv6) < 0) { - perror("Cannot send dump request"); - return -2; - } + if (rtnl_routedump_req(&rth, do_ipv6, iproute_dump_filter) < 0) { + perror("Cannot send dump request"); + return -2; } new_json_obj(json); diff --git a/lib/libnetlink.c b/lib/libnetlink.c index b9c37fd3..56a1cade 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -266,11 +266,13 @@ int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family) return send(rth->fd, &req, sizeof(req), 0); } -int rtnl_routedump_req(struct rtnl_handle *rth, int family) +int rtnl_routedump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) { struct { struct nlmsghdr nlh; struct rtmsg rtm; + char buf[128]; } req = { .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), .nlh.nlmsg_type = RTM_GETROUTE, @@ -279,6 +281,14 @@ int rtnl_routedump_req(struct rtnl_handle *rth, int family) .rtm.rtm_family = family, }; + if (filter_fn) { + int err; + + err = filter_fn(&req.nlh, sizeof(req)); + if (err) + return err; + } + return send(rth->fd, &req, sizeof(req), 0); } From c7e6371bc4afe6d42700d3174a6c56cba5833844 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Fri, 19 Oct 2018 15:41:39 -0700 Subject: [PATCH 05/12] ip route: Add protocol, table id and device to dump request Add protocol, table id and device to dump request if set in filter. If kernel side filtering is supported it is used to reduce the amount of data sent to userspace. Older kernels do not parse attributes on a route dump request, so these are silently ignored and ip will do the filtering in userspace. Signed-off-by: David Ahern --- ip/iproute.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ip/iproute.c b/ip/iproute.c index 3c0be0a9..5bffb9d8 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -1669,10 +1669,24 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen) { struct rtmsg *rtm = NLMSG_DATA(nlh); + int err; + rtm->rtm_protocol = filter.protocol; if (filter.cloned) rtm->rtm_flags |= RTM_F_CLONED; + if (filter.tb) { + err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb); + if (err) + return err; + } + + if (filter.oif) { + err = addattr32(nlh, reqlen, RTA_OIF, filter.oif); + if (err) + return err; + } + return 0; } From 98ce99273f24f74c34d39e887b9f16b6e1a36ffc Mon Sep 17 00:00:00 2001 From: David Ahern Date: Wed, 19 Dec 2018 13:11:15 -0800 Subject: [PATCH 06/12] mroute: fix up family handling Only ipv4 and ipv6 have multicast routing. Set family accordingly and just return for other cases. Signed-off-by: David Ahern --- ip/ipmroute.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ip/ipmroute.c b/ip/ipmroute.c index de7a035f..b8f0bc49 100644 --- a/ip/ipmroute.c +++ b/ip/ipmroute.c @@ -223,18 +223,20 @@ void ipmroute_reset_filter(int ifindex) static int mroute_list(int argc, char **argv) { char *id = NULL; - int family; + int family = preferred_family; ipmroute_reset_filter(0); - if (preferred_family == AF_UNSPEC) - family = AF_INET; - else - family = AF_INET6; - if (family == AF_INET) { + if (family == AF_INET || family == AF_UNSPEC) { + family = RTNL_FAMILY_IPMR; filter.af = RTNL_FAMILY_IPMR; filter.tb = RT_TABLE_DEFAULT; /* for backward compatibility */ - } else + } else if (family == AF_INET6) { + family = RTNL_FAMILY_IP6MR; filter.af = RTNL_FAMILY_IP6MR; + } else { + /* family does not have multicast routing */ + return 0; + } filter.msrc.family = filter.mdst.family = family; From e41ede893960df15958fd146d857fa2cdc4df50e Mon Sep 17 00:00:00 2001 From: David Ahern Date: Mon, 15 Oct 2018 10:30:34 -0700 Subject: [PATCH 07/12] mroute: Add table id attribute for kernel side filtering Similar to 'ip route' add the table id to the dump request for kernel side filtering if it is supported. Signed-off-by: David Ahern --- ip/ipmroute.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ip/ipmroute.c b/ip/ipmroute.c index b8f0bc49..b29c78e4 100644 --- a/ip/ipmroute.c +++ b/ip/ipmroute.c @@ -220,6 +220,19 @@ void ipmroute_reset_filter(int ifindex) filter.iif = ifindex; } +static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen) +{ + int err; + + if (filter.tb) { + err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb); + if (err) + return err; + } + + return 0; +} + static int mroute_list(int argc, char **argv) { char *id = NULL; @@ -285,7 +298,7 @@ static int mroute_list(int argc, char **argv) filter.iif = idx; } - if (rtnl_routedump_req(&rth, filter.af, NULL) < 0) { + if (rtnl_routedump_req(&rth, filter.af, iproute_dump_filter) < 0) { perror("Cannot send dump request"); return 1; } From 7ca9cee8d88b99e7b4039346f9e829f1b20c718a Mon Sep 17 00:00:00 2001 From: David Ahern Date: Wed, 19 Dec 2018 13:28:47 -0800 Subject: [PATCH 08/12] ip address: Split ip_linkaddr_list into link and addr functions Split ip_linkaddr_list into one function that generates a list of devices and a second that generates the list of addresses. Signed-off-by: David Ahern --- ip/ip_common.h | 3 +-- ip/ipaddress.c | 45 +++++++++++++++++++++++---------------------- ip/ipvrf.c | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ip/ip_common.h b/ip/ip_common.h index 53668f59..d67575c6 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -84,8 +84,7 @@ int do_seg6(int argc, char **argv); int iplink_get(char *name, __u32 filt_mask); int iplink_ifla_xstats(int argc, char **argv); -int ip_linkaddr_list(int family, req_filter_fn_t filter_fn, - struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo); +int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo); void free_nlmsg_chain(struct nlmsg_chain *info); static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb) diff --git a/ip/ipaddress.c b/ip/ipaddress.c index 016662e9..746dbfc5 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -1766,8 +1766,7 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen) * caller can walk lists as desired and must call free_nlmsg_chain for * both when done */ -int ip_linkaddr_list(int family, req_filter_fn_t filter_fn, - struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo) +int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo) { if (rtnl_linkdump_req_filter_fn(&rth, preferred_family, filter_fn) < 0) { @@ -1780,16 +1779,19 @@ int ip_linkaddr_list(int family, req_filter_fn_t filter_fn, return 1; } - if (ainfo) { - if (rtnl_addrdump_req(&rth, family) < 0) { - perror("Cannot send dump request"); - return 1; - } + return 0; +} - if (rtnl_dump_filter(&rth, store_nlmsg, ainfo) < 0) { - fprintf(stderr, "Dump terminated\n"); - return 1; - } +static int ip_addr_list(struct nlmsg_chain *ainfo) +{ + if (rtnl_addrdump_req(&rth, filter.family) < 0) { + perror("Cannot send dump request"); + return 1; + } + + if (rtnl_dump_filter(&rth, store_nlmsg, ainfo) < 0) { + fprintf(stderr, "Dump terminated\n"); + return 1; } return 0; @@ -1798,7 +1800,7 @@ int ip_linkaddr_list(int family, req_filter_fn_t filter_fn, static int ipaddr_list_flush_or_save(int argc, char **argv, int action) { struct nlmsg_chain linfo = { NULL, NULL}; - struct nlmsg_chain _ainfo = { NULL, NULL}, *ainfo = NULL; + struct nlmsg_chain _ainfo = { NULL, NULL}, *ainfo = &_ainfo; struct nlmsg_list *l; char *filter_dev = NULL; int no_link = 0; @@ -1940,19 +1942,18 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) goto out; } - if (filter.family != AF_PACKET) { - ainfo = &_ainfo; - - if (filter.oneline) - no_link = 1; - } - - if (ip_linkaddr_list(filter.family, iplink_filter_req, - &linfo, ainfo) != 0) + if (ip_link_list(iplink_filter_req, &linfo) != 0) goto out; - if (filter.family != AF_PACKET) + if (filter.family != AF_PACKET) { + if (filter.oneline) + no_link = 1; + + if (ip_addr_list(ainfo) != 0) + goto out; + ipaddr_filter(&linfo, ainfo); + } for (l = linfo.head; l; l = l->next) { struct nlmsghdr *n = &l->h; diff --git a/ip/ipvrf.c b/ip/ipvrf.c index 8a6b7f97..08a0d45b 100644 --- a/ip/ipvrf.c +++ b/ip/ipvrf.c @@ -589,7 +589,7 @@ static int ipvrf_show(int argc, char **argv) return 0; } - if (ip_linkaddr_list(0, ipvrf_filter_req, &linfo, NULL) == 0) { + if (ip_link_list(ipvrf_filter_req, &linfo) == 0) { struct nlmsg_list *l; unsigned nvrf = 0; int n; From 8847097850571f389ecb396bfabc359582a49fee Mon Sep 17 00:00:00 2001 From: David Ahern Date: Wed, 19 Dec 2018 13:30:44 -0800 Subject: [PATCH 09/12] ip address: Set device index in dump request Add a filter function to rtnl_addrdump_req to set device index in the address dump request if the user is filtering addresses by device. In addition, add a new ipaddr_link_get to do a single RTM_GETLINK request instead of a device dump yet still store the data in the linfo list. Signed-off-by: David Ahern --- include/libnetlink.h | 3 ++- ip/ipaddress.c | 59 ++++++++++++++++++++++++++++++++++++++------ lib/libnetlink.c | 12 ++++++++- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/include/libnetlink.h b/include/libnetlink.h index b0051f39..2621bc99 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -49,7 +49,8 @@ void rtnl_close(struct rtnl_handle *rth); typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen); -int rtnl_addrdump_req(struct rtnl_handle *rth, int family) +int rtnl_addrdump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) __attribute__((warn_unused_result)); int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); diff --git a/ip/ipaddress.c b/ip/ipaddress.c index 746dbfc5..2bc33f3a 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -1679,6 +1679,15 @@ static void ipaddr_filter(struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo) } } +static int ipaddr_dump_filter(struct nlmsghdr *nlh, int reqlen) +{ + struct ifaddrmsg *ifa = NLMSG_DATA(nlh); + + ifa->ifa_index = filter.ifindex; + + return 0; +} + static int ipaddr_flush(void) { int round = 0; @@ -1689,7 +1698,8 @@ static int ipaddr_flush(void) filter.flushe = sizeof(flushb); while ((max_flush_loops == 0) || (round < max_flush_loops)) { - if (rtnl_addrdump_req(&rth, filter.family) < 0) { + if (rtnl_addrdump_req(&rth, filter.family, + ipaddr_dump_filter) < 0) { perror("Cannot send dump request"); exit(1); } @@ -1762,6 +1772,36 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen) return 0; } +static int ipaddr_link_get(int index, struct nlmsg_chain *linfo) +{ + struct iplink_req req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETLINK, + .i.ifi_family = filter.family, + .i.ifi_index = index, + }; + __u32 filt_mask = RTEXT_FILTER_VF; + struct nlmsghdr *answer; + + if (!show_stats) + filt_mask |= RTEXT_FILTER_SKIP_STATS; + + addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask); + + if (rtnl_talk(&rth, &req.n, &answer) < 0) { + perror("Cannot send link request"); + return 1; + } + + if (store_nlmsg(answer, linfo) < 0) { + fprintf(stderr, "Failed to process link information\n"); + return 1; + } + + return 0; +} + /* fills in linfo with link data and optionally ainfo with address info * caller can walk lists as desired and must call free_nlmsg_chain for * both when done @@ -1784,7 +1824,7 @@ int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo) static int ip_addr_list(struct nlmsg_chain *ainfo) { - if (rtnl_addrdump_req(&rth, filter.family) < 0) { + if (rtnl_addrdump_req(&rth, filter.family, ipaddr_dump_filter) < 0) { perror("Cannot send dump request"); return 1; } @@ -1908,7 +1948,8 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) if (ipadd_save_prep()) exit(1); - if (rtnl_addrdump_req(&rth, preferred_family) < 0) { + if (rtnl_addrdump_req(&rth, preferred_family, + ipaddr_dump_filter) < 0) { perror("Cannot send dump request"); exit(1); } @@ -1942,8 +1983,13 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) goto out; } - if (ip_link_list(iplink_filter_req, &linfo) != 0) - goto out; + if (filter.ifindex) { + if (ipaddr_link_get(filter.ifindex, &linfo) != 0) + goto out; + } else { + if (ip_link_list(iplink_filter_req, &linfo) != 0) + goto out; + } if (filter.family != AF_PACKET) { if (filter.oneline) @@ -1972,8 +2018,7 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) fflush(stdout); out: - if (ainfo) - free_nlmsg_chain(ainfo); + free_nlmsg_chain(ainfo); free_nlmsg_chain(&linfo); delete_json_obj(); return 0; diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 56a1cade..0ddd646a 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -234,11 +234,13 @@ int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); } -int rtnl_addrdump_req(struct rtnl_handle *rth, int family) +int rtnl_addrdump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) { struct { struct nlmsghdr nlh; struct ifaddrmsg ifm; + char buf[128]; } req = { .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), .nlh.nlmsg_type = RTM_GETADDR, @@ -247,6 +249,14 @@ int rtnl_addrdump_req(struct rtnl_handle *rth, int family) .ifm.ifa_family = family, }; + if (filter_fn) { + int err; + + err = filter_fn(&req.nlh, sizeof(req)); + if (err) + return err; + } + return send(rth->fd, &req, sizeof(req), 0); } From aea41afcfd6d6547dd2e80ddde8df7e3b2800482 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Fri, 5 Oct 2018 13:49:41 -0700 Subject: [PATCH 10/12] ip bridge: Set NETLINK_GET_STRICT_CHK on socket iproute2 has been updated for the new strict policy in the kernel. Add a helper to call setsockopt to enable the feature. Add a call to ip.c and bridge.c The setsockopt fails on older kernels and the error can be safely ignored - any new fields or attributes are ignored by the older kernel. Signed-off-by: David Ahern --- bridge/bridge.c | 4 ++++ include/libnetlink.h | 1 + ip/ip.c | 2 ++ lib/libnetlink.c | 9 +++++++++ 4 files changed, 16 insertions(+) diff --git a/bridge/bridge.c b/bridge/bridge.c index a3d8154b..a50d9d59 100644 --- a/bridge/bridge.c +++ b/bridge/bridge.c @@ -97,6 +97,8 @@ static int batch(const char *name) return EXIT_FAILURE; } + rtnl_set_strict_dump(&rth); + cmdlineno = 0; while (getcmdline(&line, &len, stdin) != -1) { char *largv[100]; @@ -205,6 +207,8 @@ main(int argc, char **argv) if (rtnl_open(&rth, 0) < 0) exit(1); + rtnl_set_strict_dump(&rth); + if (argc > 1) return do_cmd(argv[1], argc-1, argv+1); diff --git a/include/libnetlink.h b/include/libnetlink.h index 2621bc99..dc0c9c4e 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -46,6 +46,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, __attribute__((warn_unused_result)); void rtnl_close(struct rtnl_handle *rth); +void rtnl_set_strict_dump(struct rtnl_handle *rth); typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen); diff --git a/ip/ip.c b/ip/ip.c index a5bbacb4..e4131714 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -308,6 +308,8 @@ int main(int argc, char **argv) if (rtnl_open(&rth, 0) < 0) exit(1); + rtnl_set_strict_dump(&rth); + if (strlen(basename) > 2) return do_cmd(basename+2, argc, argv); diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 0ddd646a..4d7d0810 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -161,6 +161,15 @@ static int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error) } #endif +/* Older kernels may not support strict dump and filtering */ +void rtnl_set_strict_dump(struct rtnl_handle *rth) +{ + int one = 1; + + setsockopt(rth->fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, + &one, sizeof(one)); +} + void rtnl_close(struct rtnl_handle *rth) { if (rth->fd >= 0) { From 8d4f35de1705b07fd686d93b0854a6b9052be3e6 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Wed, 31 Oct 2018 12:29:45 -0700 Subject: [PATCH 11/12] ip route: Rename do_ipv6 to dump_family do_ipv6 is really the preferred dump family. Rename it to make that apparent. Signed-off-by: David Ahern --- ip/iproute.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ip/iproute.c b/ip/iproute.c index 5bffb9d8..0440366e 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -1604,7 +1604,7 @@ static int save_route_prep(void) return 0; } -static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) +static int iproute_flush(int family, rtnl_filter_t filter_fn) { time_t start = time(0); char flushb[4096-512]; @@ -1612,12 +1612,12 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) int ret; if (filter.cloned) { - if (do_ipv6 != AF_INET6) { + if (family != AF_INET6) { iproute_flush_cache(); if (show_stats) printf("*** IPv4 routing cache is flushed.\n"); } - if (do_ipv6 == AF_INET) + if (family == AF_INET) return 0; } @@ -1626,7 +1626,7 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) filter.flushe = sizeof(flushb); for (;;) { - if (rtnl_routedump_req(&rth, do_ipv6, NULL) < 0) { + if (rtnl_routedump_req(&rth, family, NULL) < 0) { perror("Cannot send dump request"); return -2; } @@ -1638,7 +1638,7 @@ static int iproute_flush(int do_ipv6, rtnl_filter_t filter_fn) if (filter.flushed == 0) { if (show_stats) { if (round == 0 && - (!filter.cloned || do_ipv6 == AF_INET6)) + (!filter.cloned || family == AF_INET6)) printf("Nothing to flush.\n"); else printf("*** Flush is complete after %d round%s ***\n", @@ -1692,7 +1692,7 @@ static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen) static int iproute_list_flush_or_save(int argc, char **argv, int action) { - int do_ipv6 = preferred_family; + int dump_family = preferred_family; char *id = NULL; char *od = NULL; unsigned int mark = 0; @@ -1811,13 +1811,13 @@ static int iproute_list_flush_or_save(int argc, char **argv, int action) NEXT_ARG(); family = read_family(*argv); if (family == AF_UNSPEC) - family = do_ipv6; + family = dump_family; else NEXT_ARG(); get_prefix(&filter.rvia, *argv, family); } else if (strcmp(*argv, "src") == 0) { NEXT_ARG(); - get_prefix(&filter.rprefsrc, *argv, do_ipv6); + get_prefix(&filter.rprefsrc, *argv, dump_family); } else if (matches(*argv, "realms") == 0) { __u32 realm; @@ -1837,15 +1837,15 @@ static int iproute_list_flush_or_save(int argc, char **argv, int action) NEXT_ARG(); if (matches(*argv, "root") == 0) { NEXT_ARG(); - get_prefix(&filter.rsrc, *argv, do_ipv6); + get_prefix(&filter.rsrc, *argv, dump_family); } else if (matches(*argv, "match") == 0) { NEXT_ARG(); - get_prefix(&filter.msrc, *argv, do_ipv6); + get_prefix(&filter.msrc, *argv, dump_family); } else { if (matches(*argv, "exact") == 0) { NEXT_ARG(); } - get_prefix(&filter.msrc, *argv, do_ipv6); + get_prefix(&filter.msrc, *argv, dump_family); filter.rsrc = filter.msrc; } } else { @@ -1854,23 +1854,23 @@ static int iproute_list_flush_or_save(int argc, char **argv, int action) } if (matches(*argv, "root") == 0) { NEXT_ARG(); - get_prefix(&filter.rdst, *argv, do_ipv6); + get_prefix(&filter.rdst, *argv, dump_family); } else if (matches(*argv, "match") == 0) { NEXT_ARG(); - get_prefix(&filter.mdst, *argv, do_ipv6); + get_prefix(&filter.mdst, *argv, dump_family); } else { if (matches(*argv, "exact") == 0) { NEXT_ARG(); } - get_prefix(&filter.mdst, *argv, do_ipv6); + get_prefix(&filter.mdst, *argv, dump_family); filter.rdst = filter.mdst; } } argc--; argv++; } - if (do_ipv6 == AF_UNSPEC && filter.tb) - do_ipv6 = AF_INET; + if (dump_family == AF_UNSPEC && filter.tb) + dump_family = AF_INET; if (id || od) { int idx; @@ -1893,9 +1893,9 @@ static int iproute_list_flush_or_save(int argc, char **argv, int action) filter.mark = mark; if (action == IPROUTE_FLUSH) - return iproute_flush(do_ipv6, filter_fn); + return iproute_flush(dump_family, filter_fn); - if (rtnl_routedump_req(&rth, do_ipv6, iproute_dump_filter) < 0) { + if (rtnl_routedump_req(&rth, dump_family, iproute_dump_filter) < 0) { perror("Cannot send dump request"); return -2; } From 6b83edc0610c40c353037ea80e8e630fe420dd00 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Wed, 19 Dec 2018 17:07:19 -0800 Subject: [PATCH 12/12] neighbor: Add support for protocol attribute Add support to set protocol on neigh entries and to print the protocol on dumps. Signed-off-by: David Ahern --- ip/ipneigh.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/ip/ipneigh.c b/ip/ipneigh.c index 6041c467..26ac2d1b 100644 --- a/ip/ipneigh.c +++ b/ip/ipneigh.c @@ -40,6 +40,7 @@ static struct int flushp; int flushe; int master; + int protocol; } filter; static void usage(void) __attribute__((noreturn)); @@ -48,7 +49,7 @@ static void usage(void) { fprintf(stderr, "Usage: ip neigh { add | del | change | replace }\n" " { ADDR [ lladdr LLADDR ] [ nud STATE ] | proxy ADDR } [ dev DEV ]\n"); - fprintf(stderr, " [ router ] [ extern_learn ]\n\n"); + fprintf(stderr, " [ router ] [ extern_learn ] [ protocol PROTO ]\n\n"); fprintf(stderr, " ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ]\n"); fprintf(stderr, " [ vrf NAME ]\n\n"); fprintf(stderr, "STATE := { permanent | noarp | stale | reachable | none |\n" @@ -148,6 +149,14 @@ static int ipneigh_modify(int cmd, int flags, int argc, char **argv) NEXT_ARG(); dev = *argv; dev_ok = 1; + } else if (matches(*argv, "protocol") == 0) { + __u32 proto; + + NEXT_ARG(); + if (rtnl_rtprot_a2n(&proto, *argv)) + invarg("\"protocol\" value is invalid\n", *argv); + if (addattr8(&req.n, sizeof(req), NDA_PROTOCOL, proto)) + return -1; } else { if (strcmp(*argv, "to") == 0) { NEXT_ARG(); @@ -244,6 +253,7 @@ int print_neigh(struct nlmsghdr *n, void *arg) int len = n->nlmsg_len; struct rtattr *tb[NDA_MAX+1]; static int logit = 1; + __u8 protocol = 0; if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH && n->nlmsg_type != RTM_GETNEIGH) { @@ -285,6 +295,12 @@ int print_neigh(struct nlmsghdr *n, void *arg) if (inet_addr_match_rta(&filter.pfx, tb[NDA_DST])) return 0; + if (tb[NDA_PROTOCOL]) + protocol = rta_getattr_u8(tb[NDA_PROTOCOL]); + + if (filter.protocol && filter.protocol != protocol) + return 0; + if (filter.unused_only && tb[NDA_CACHEINFO]) { struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]); @@ -371,6 +387,13 @@ int print_neigh(struct nlmsghdr *n, void *arg) if (r->ndm_state) print_neigh_state(r->ndm_state); + if (protocol) { + SPRINT_BUF(b1); + + print_string(PRINT_ANY, "protocol", " proto %s ", + rtnl_rtprot_n2a(protocol, b1, sizeof(b1))); + } + print_string(PRINT_FP, NULL, "\n", ""); close_json_object(); fflush(stdout); @@ -458,9 +481,19 @@ static int do_show_or_flush(int argc, char **argv, int flush) if (state == 0) state = 0x100; filter.state |= state; - } else if (strcmp(*argv, "proxy") == 0) + } else if (strcmp(*argv, "proxy") == 0) { req.ndm.ndm_flags = NTF_PROXY; - else { + } else if (matches(*argv, "protocol") == 0) { + __u32 prot; + + NEXT_ARG(); + if (rtnl_rtprot_a2n(&prot, *argv)) { + if (strcmp(*argv, "all")) + invarg("invalid \"protocol\"\n", *argv); + prot = 0; + } + filter.protocol = prot; + } else { if (strcmp(*argv, "to") == 0) { NEXT_ARG(); }