X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=lib%2Flibnetlink.c;h=0d48a3d43cf03065dacbd419578ab10af56431a4;hb=bb5ae621d0c7b9caf3a101903783bd5a1c997fa4;hp=5b75b2db4e0b4cfd603d9b9d0edc4cdde5ed8a7d;hpb=5bffc12ef4266b40a3e8f4eacfc7321c4edb0325;p=mirror_iproute2.git diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 5b75b2db..0d48a3d4 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -23,9 +22,14 @@ #include #include #include +#include +#include +#include #include "libnetlink.h" +#define __aligned(x) __attribute__((aligned(x))) + #ifndef SOL_NETLINK #define SOL_NETLINK 270 #endif @@ -36,6 +40,139 @@ int rcvbuf = 1024 * 1024; +#ifdef HAVE_LIBMNL +#include + +static const enum mnl_attr_data_type extack_policy[NLMSGERR_ATTR_MAX + 1] = { + [NLMSGERR_ATTR_MSG] = MNL_TYPE_NUL_STRING, + [NLMSGERR_ATTR_OFFS] = MNL_TYPE_U32, +}; + +static int err_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + uint16_t type; + + if (mnl_attr_type_valid(attr, NLMSGERR_ATTR_MAX) < 0) { + fprintf(stderr, "Invalid extack attribute\n"); + return MNL_CB_ERROR; + } + + type = mnl_attr_get_type(attr); + if (mnl_attr_validate(attr, extack_policy[type]) < 0) { + fprintf(stderr, "extack attribute %d failed validation\n", + type); + return MNL_CB_ERROR; + } + + tb[type] = attr; + 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) +{ + struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + const struct nlmsghdr *err_nlh = NULL; + unsigned int hlen = sizeof(*err); + const char *msg = NULL; + uint32_t off = 0; + + /* no TLVs, nothing to do here */ + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) + return 0; + + /* if NLM_F_CAPPED is set then the inner err msg was capped */ + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + hlen += mnl_nlmsg_get_payload_len(&err->msg); + + 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 (tb[NLMSGERR_ATTR_OFFS]) { + off = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]); + + if (off > nlh->nlmsg_len) { + fprintf(stderr, + "Invalid offset for NLMSGERR_ATTR_OFFS\n"); + off = 0; + } else if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + err_nlh = &err->msg; + } + + if (errfn) + return errfn(msg, off, err_nlh); + + if (msg && *msg != '\0') { + bool is_err = !!err->error; + + 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; + } + + return 0; +} +#else +#warning "libmnl required for error support" + +/* No extended error ack without libmnl */ +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 + +/* Older kernels may not support strict dump and filtering */ +void rtnl_set_strict_dump(struct rtnl_handle *rth) +{ + int one = 1; + + if (setsockopt(rth->fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, + &one, sizeof(one)) < 0) + return; + + rth->flags |= RTNL_HANDLE_F_STRICT_CHK; +} + void rtnl_close(struct rtnl_handle *rth) { if (rth->fd >= 0) { @@ -49,6 +186,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, { socklen_t addr_len; int sndbuf = 32768; + int one = 1; memset(rth, 0, sizeof(*rth)); @@ -71,6 +209,10 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, return -1; } + /* Older kernels may no support extended ACK reporting */ + setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK, + &one, sizeof(one)); + memset(&rth->local, 0, sizeof(rth->local)); rth->local.nl_family = AF_NETLINK; rth->local.nl_groups = subscriptions; @@ -104,62 +246,287 @@ int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); } -int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +int rtnl_addrdump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) { - return rtnl_wilddump_req_filter(rth, family, type, RTEXT_FILTER_VF); + struct { + struct nlmsghdr nlh; + struct ifaddrmsg ifm; + char buf[128]; + } 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, + .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); } -int rtnl_wilddump_req_filter(struct rtnl_handle *rth, int family, int type, - __u32 filt_mask) +int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct ifaddrlblmsg ifal; + } 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, + .ifal.ifal_family = family, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +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, + .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlh.nlmsg_seq = rth->dump = ++rth->seq, + .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); +} + +int rtnl_ruledump_req(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct fib_rule_hdr frh; + } 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, + .frh.family = family + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +int rtnl_neighdump_req(struct rtnl_handle *rth, int family, + req_filter_fn_t filter_fn) +{ + struct { + struct nlmsghdr nlh; + struct ndmsg ndm; + char buf[256]; + } 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, + .ndm.ndm_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); +} + +int rtnl_neightbldump_req(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct ndtmsg ndtmsg; + } 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, + .ndtmsg.ndtm_family = family, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +int rtnl_mdbdump_req(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct br_port_msg bpm; + } 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, + .bpm.family = family, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +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 = 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, + .ncm.ncm_family = family, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +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 = 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, + .rtm.rtgen_family = family, + }; + + return send(rth->fd, &req, sizeof(req), 0); +} + +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 __attribute__ ((aligned(NLMSG_ALIGNTO))); - __u32 ext_filter_mask; } req = { - .nlh.nlmsg_len = sizeof(req), - .nlh.nlmsg_type = type, + .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, - .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_wilddump_req_filter_fn(struct rtnl_handle *rth, int family, int type, +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 __rtnl_linkdump_req(rth, family); +} + +int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int family, + __u32 filt_mask) +{ + if (family == AF_UNSPEC || family == AF_BRIDGE) { + 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 || family == AF_PACKET) { + 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_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth, + req_filter_fn_t filter_fn) { struct { struct nlmsghdr nlh; struct ifinfomsg ifm; - char buf[1024]; + char buf[128]; } req = { .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), - .nlh.nlmsg_type = type, + .nlh.nlmsg_type = RTM_GETNEIGH, .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, .nlh.nlmsg_seq = rth->dump = ++rth->seq, - .ifm.ifi_family = family, + .ifm.ifi_family = PF_BRIDGE, }; 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 send(rth->fd, &req, sizeof(req), 0); } -int rtnl_wilddump_stats_req_filter(struct rtnl_handle *rth, int fam, int type, - __u32 filt_mask) +int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) { struct { struct nlmsghdr nlh; @@ -168,7 +535,7 @@ int rtnl_wilddump_stats_req_filter(struct rtnl_handle *rth, int fam, int type, memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)); - req.nlh.nlmsg_type = type; + req.nlh.nlmsg_type = RTM_GETSTATS; req.nlh.nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST; req.nlh.nlmsg_pid = 0; req.nlh.nlmsg_seq = rth->dump = ++rth->seq; @@ -261,26 +628,38 @@ int rtnl_dump_request_n(struct rtnl_handle *rth, struct nlmsghdr *n) return sendmsg(rth->fd, &msg, 0); } -static int rtnl_dump_done(const struct rtnl_handle *rth, - struct nlmsghdr *h) +static int rtnl_dump_done(struct nlmsghdr *h) { int len = *(int *)NLMSG_DATA(h); - if (rth->proto == NETLINK_SOCK_DIAG) { - if (h->nlmsg_len < NLMSG_LENGTH(sizeof(int))) { - fprintf(stderr, "DONE truncated\n"); - return -1; - } + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(int))) { + fprintf(stderr, "DONE truncated\n"); + return -1; + } + if (len < 0) { + /* check for any messages returned from kernel */ + if (nl_dump_ext_ack_done(h, len)) + return len; - if (len < 0) { - errno = -len; - if (errno == ENOENT || errno == EOPNOTSUPP) - return -1; + errno = -len; + switch (errno) { + case ENOENT: + case EOPNOTSUPP: + return -1; + case EMSGSIZE: + fprintf(stderr, + "Error: Buffer too small for object.\n"); + break; + default: perror("RTNETLINK answers"); - return len; } + return len; } + + /* check for any messages returned from kernel */ + nl_dump_ext_ack(h, NULL); + return 0; } @@ -304,8 +683,68 @@ static void rtnl_dump_error(const struct rtnl_handle *rth, } } -int rtnl_dump_filter_l(struct rtnl_handle *rth, - const struct rtnl_dump_filter_arg *arg) +static int __rtnl_recvmsg(int fd, struct msghdr *msg, int flags) +{ + int len; + + do { + len = recvmsg(fd, msg, flags); + } while (len < 0 && (errno == EINTR || errno == EAGAIN)); + + if (len < 0) { + fprintf(stderr, "netlink receive error %s (%d)\n", + strerror(errno), errno); + return -errno; + } + + if (len == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -ENODATA; + } + + return len; +} + +static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer) +{ + struct iovec *iov = msg->msg_iov; + char *buf; + int len; + + iov->iov_base = NULL; + iov->iov_len = 0; + + len = __rtnl_recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC); + if (len < 0) + return len; + + if (len < 32768) + len = 32768; + buf = malloc(len); + if (!buf) { + fprintf(stderr, "malloc error: not enough buffer\n"); + return -ENOMEM; + } + + iov->iov_base = buf; + iov->iov_len = len; + + len = __rtnl_recvmsg(fd, msg, 0); + if (len < 0) { + free(buf); + return len; + } + + if (answer) + *answer = buf; + else + free(buf); + + return len; +} + +static int rtnl_dump_filter_l(struct rtnl_handle *rth, + const struct rtnl_dump_filter_arg *arg) { struct sockaddr_nl nladdr; struct iovec iov; @@ -315,31 +754,18 @@ int rtnl_dump_filter_l(struct rtnl_handle *rth, .msg_iov = &iov, .msg_iovlen = 1, }; - char buf[32768]; + char *buf; int dump_intr = 0; - iov.iov_base = buf; while (1) { int status; const struct rtnl_dump_filter_arg *a; int found_done = 0; int msglen = 0; - iov.iov_len = sizeof(buf); - status = recvmsg(rth->fd, &msg, 0); - - if (status < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - fprintf(stderr, "netlink receive error %s (%d)\n", - strerror(errno), errno); - return -1; - } - - if (status == 0) { - fprintf(stderr, "EOF on netlink\n"); - return -1; - } + status = rtnl_recvmsg(rth->fd, &msg, &buf); + if (status < 0) + return status; if (rth->dump_fp) fwrite(buf, 1, NLMSG_ALIGN(status), rth->dump_fp); @@ -363,9 +789,11 @@ int rtnl_dump_filter_l(struct rtnl_handle *rth, dump_intr = 1; if (h->nlmsg_type == NLMSG_DONE) { - err = rtnl_dump_done(rth, h); - if (err < 0) + err = rtnl_dump_done(h); + if (err < 0) { + free(buf); return -1; + } found_done = 1; break; /* process next filter */ @@ -373,19 +801,23 @@ int rtnl_dump_filter_l(struct rtnl_handle *rth, if (h->nlmsg_type == NLMSG_ERROR) { rtnl_dump_error(rth, h); + free(buf); return -1; } if (!rth->dump_fp) { - err = a->filter(&nladdr, h, a->arg1); - if (err < 0) + err = a->filter(h, a->arg1); + if (err < 0) { + free(buf); return err; + } } skip_it: h = NLMSG_NEXT(h, msglen); } } + free(buf); if (found_done) { if (dump_intr) @@ -417,30 +849,40 @@ int rtnl_dump_filter_nc(struct rtnl_handle *rth, return rtnl_dump_filter_l(rth, a); } -static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, - struct nlmsghdr *answer, size_t maxlen, - bool show_rtnl_err) +static void rtnl_talk_error(struct nlmsghdr *h, struct nlmsgerr *err, + nl_ext_ack_fn_t errfn) +{ + if (nl_dump_ext_ack(h, errfn)) + return; + + fprintf(stderr, "RTNETLINK answers: %s\n", + strerror(-err->error)); +} + + +static int __rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iov, + size_t iovlen, struct nlmsghdr **answer, + bool show_rtnl_err, nl_ext_ack_fn_t errfn) { - int status; - unsigned int seq; - struct nlmsghdr *h; struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK }; - struct iovec iov = { - .iov_base = n, - .iov_len = n->nlmsg_len - }; + struct iovec riov; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), - .msg_iov = &iov, - .msg_iovlen = 1, + .msg_iov = iov, + .msg_iovlen = iovlen, }; - char buf[32768] = {}; - - n->nlmsg_seq = seq = ++rtnl->seq; - - if (answer == NULL) - n->nlmsg_flags |= NLM_F_ACK; + unsigned int seq = 0; + struct nlmsghdr *h; + int i, status; + char *buf; + + for (i = 0; i < iovlen; i++) { + h = iov[i].iov_base; + h->nlmsg_seq = seq = ++rtnl->seq; + if (answer == NULL) + h->nlmsg_flags |= NLM_F_ACK; + } status = sendmsg(rtnl->fd, &msg, 0); if (status < 0) { @@ -448,22 +890,18 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, return -1; } - iov.iov_base = buf; + /* change msg to use the response iov */ + msg.msg_iov = &riov; + msg.msg_iovlen = 1; + i = 0; while (1) { - iov.iov_len = sizeof(buf); - status = recvmsg(rtnl->fd, &msg, 0); +next: + status = rtnl_recvmsg(rtnl->fd, &msg, &buf); + ++i; + + if (status < 0) + return status; - if (status < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - fprintf(stderr, "netlink receive error %s (%d)\n", - strerror(errno), errno); - return -1; - } - if (status == 0) { - fprintf(stderr, "EOF on netlink\n"); - return -1; - } if (msg.msg_namelen != sizeof(nladdr)) { fprintf(stderr, "sender address length == %d\n", @@ -477,6 +915,7 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, if (l < 0 || len > status) { if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Truncated message\n"); + free(buf); return -1; } fprintf(stderr, @@ -487,7 +926,7 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, if (nladdr.nl_pid != 0 || h->nlmsg_pid != rtnl->local.nl_pid || - h->nlmsg_seq != seq) { + h->nlmsg_seq > seq || h->nlmsg_seq < seq - iovlen) { /* Don't forget to skip that message. */ status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); @@ -496,27 +935,37 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, if (h->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); + int error = err->error; if (l < sizeof(struct nlmsgerr)) { fprintf(stderr, "ERROR truncated\n"); - } else if (!err->error) { - if (answer) - memcpy(answer, h, - MIN(maxlen, h->nlmsg_len)); - return 0; + free(buf); + return -1; } - if (rtnl->proto != NETLINK_SOCK_DIAG && show_rtnl_err) - fprintf(stderr, - "RTNETLINK answers: %s\n", - strerror(-err->error)); - errno = -err->error; - return -1; + if (!error) { + /* check messages from kernel */ + nl_dump_ext_ack(h, errfn); + } else { + errno = -error; + + if (rtnl->proto != NETLINK_SOCK_DIAG && + show_rtnl_err) + rtnl_talk_error(h, err, errfn); + } + + if (answer) + *answer = (struct nlmsghdr *)buf; + else + free(buf); + + if (i < iovlen) + goto next; + return error ? -i : 0; } if (answer) { - memcpy(answer, h, - MIN(maxlen, h->nlmsg_len)); + *answer = (struct nlmsghdr *)buf; return 0; } @@ -525,6 +974,7 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, status -= NLMSG_ALIGN(len); h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); } + free(buf); if (msg.msg_flags & MSG_TRUNC) { fprintf(stderr, "Message truncated\n"); @@ -538,16 +988,34 @@ static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, } } +static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, + struct nlmsghdr **answer, + bool show_rtnl_err, nl_ext_ack_fn_t errfn) +{ + struct iovec iov = { + .iov_base = n, + .iov_len = n->nlmsg_len + }; + + return __rtnl_talk_iov(rtnl, &iov, 1, answer, show_rtnl_err, errfn); +} + int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, - struct nlmsghdr *answer, size_t maxlen) + struct nlmsghdr **answer) { - return __rtnl_talk(rtnl, n, answer, maxlen, true); + return __rtnl_talk(rtnl, n, answer, true, NULL); +} + +int rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iovec, size_t iovlen, + struct nlmsghdr **answer) +{ + return __rtnl_talk_iov(rtnl, iovec, iovlen, answer, true, NULL); } int rtnl_talk_suppress_rtnl_errmsg(struct rtnl_handle *rtnl, struct nlmsghdr *n, - struct nlmsghdr *answer, size_t maxlen) + struct nlmsghdr **answer) { - return __rtnl_talk(rtnl, n, answer, maxlen, false); + return __rtnl_talk(rtnl, n, answer, false, NULL); } int rtnl_listen_all_nsid(struct rtnl_handle *rth) @@ -643,7 +1111,7 @@ int rtnl_listen(struct rtnl_handle *rtnl, exit(1); } - err = handler(&nladdr, &ctrl, h, jarg); + err = handler(&ctrl, h, jarg); if (err < 0) return err; @@ -665,7 +1133,6 @@ int rtnl_from_file(FILE *rtnl, rtnl_listen_filter_t handler, void *jarg) { int status; - struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK }; char buf[16384]; struct nlmsghdr *h = (struct nlmsghdr *)buf; @@ -704,7 +1171,7 @@ int rtnl_from_file(FILE *rtnl, rtnl_listen_filter_t handler, return -1; } - err = handler(&nladdr, NULL, h, jarg); + err = handler(NULL, h, jarg); if (err < 0) return err; } @@ -755,7 +1222,8 @@ int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; - memcpy(RTA_DATA(rta), data, alen); + if (alen) + memcpy(RTA_DATA(rta), data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } @@ -842,7 +1310,8 @@ int rta_addattr_l(struct rtattr *rta, int maxlen, int type, subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; - memcpy(RTA_DATA(subrta), data, alen); + if (alen) + memcpy(RTA_DATA(subrta), data, alen); rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); return 0; } @@ -901,23 +1370,6 @@ int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta, return 0; } -int parse_rtattr_byindex(struct rtattr *tb[], int max, - struct rtattr *rta, int len) -{ - int i = 0; - - memset(tb, 0, sizeof(struct rtattr *) * max); - while (RTA_OK(rta, len)) { - if (rta->rta_type <= max && i < max) - tb[i++] = rta; - rta = RTA_NEXT(rta, len); - } - if (len) - fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", - len, rta->rta_len); - return i; -} - struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len) { while (RTA_OK(rta, len)) {