@@ -850,12 +850,68 @@ ipstats_show_one(int ifindex, struct ipstats_stat_enabled *enabled)
return err;
}
-static int ipstats_dump_one(struct nlmsghdr *n, void *arg)
+/* The kernel resumes a split dump by reopening the top-level nest it stopped
+ * in and, for bridge xstats, the nest inside that. Everything below is whole
+ * and nothing at those two levels repeats, so join the two halves at the
+ * boundary and copy the rest.
+ */
+static int ipstats_merge_attrs(struct nlmsghdr *n, int maxlen,
+ struct rtattr *a, int alen,
+ struct rtattr *b, int blen, int depth)
+{
+ struct rtattr *last = NULL, *rta, *nest;
+ unsigned short type;
+ int rem;
+
+ if (depth == 2)
+ goto copy;
+
+ /* type 0 is 64-bit padding, possibly on either side of the split */
+ for (rta = a, rem = alen; RTA_OK(rta, rem); rta = RTA_NEXT(rta, rem))
+ if (rta->rta_type)
+ last = rta;
+ while (RTA_OK(b, blen) && !b->rta_type)
+ b = RTA_NEXT(b, blen);
+ if (!last || !RTA_OK(b, blen))
+ goto copy;
+
+ type = last->rta_type & NLA_TYPE_MASK;
+ if (type != (b->rta_type & NLA_TYPE_MASK) ||
+ (!depth && (type > IFLA_STATS_MAX || !ipstats_stat_ifla_max[type])))
+ goto copy;
+
+ if (addraw_l(n, maxlen, a, (char *)last - (char *)a))
+ return -EMSGSIZE;
+
+ nest = addattr_nest(n, maxlen, last->rta_type);
+ if (ipstats_merge_attrs(n, maxlen, RTA_DATA(last), RTA_PAYLOAD(last),
+ RTA_DATA(b), RTA_PAYLOAD(b), depth + 1))
+ return -EMSGSIZE;
+ addattr_nest_end(n, nest);
+
+ b = RTA_NEXT(b, blen);
+ return addraw_l(n, maxlen, b, blen) ? -EMSGSIZE : 0;
+copy:
+ if (addraw_l(n, maxlen, a, alen) || addraw_l(n, maxlen, b, blen))
+ return -EMSGSIZE;
+ return 0;
+}
+
+struct ipstats_dump_ctx {
+ struct ipstats_stat_enabled *enabled;
+ struct nlmsghdr *pending;
+};
+
+static int ipstats_dump_pending(struct ipstats_dump_ctx *ctx)
{
- struct ipstats_stat_enabled *enabled = arg;
int rc;
- rc = ipstats_process_ifsm(stdout, n, enabled);
+ if (ctx->pending == NULL)
+ return 0;
+
+ rc = ipstats_process_ifsm(stdout, ctx->pending, ctx->enabled);
+ free(ctx->pending);
+ ctx->pending = NULL;
if (rc)
return rc;
@@ -863,9 +919,78 @@ static int ipstats_dump_one(struct nlmsghdr *n, void *arg)
return 0;
}
+#define IFSM_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct if_stats_msg))
+
+static int ipstats_dump_merge(struct ipstats_dump_ctx *ctx, struct nlmsghdr *n)
+{
+ int hdrlen = NLMSG_LENGTH(sizeof(struct if_stats_msg));
+ int maxlen = ctx->pending->nlmsg_len + n->nlmsg_len;
+ struct nlmsghdr *merged;
+ int rc;
+
+ merged = malloc(maxlen);
+ if (merged == NULL) {
+ fprintf(stderr, "Error merging netlink answer: %s\n",
+ strerror(errno));
+ return -errno;
+ }
+
+ memcpy(merged, ctx->pending, hdrlen);
+ merged->nlmsg_len = hdrlen;
+
+ rc = ipstats_merge_attrs(merged, maxlen,
+ IFLA_STATS_RTA(NLMSG_DATA(ctx->pending)),
+ IFSM_PAYLOAD(ctx->pending),
+ IFLA_STATS_RTA(NLMSG_DATA(n)),
+ IFSM_PAYLOAD(n), 0);
+ if (rc) {
+ free(merged);
+ return rc;
+ }
+
+ free(ctx->pending);
+ ctx->pending = merged;
+ return 0;
+}
+
+static int ipstats_dump_one(struct nlmsghdr *n, void *arg)
+{
+ struct ipstats_dump_ctx *ctx = arg;
+ int rc;
+
+ if (IFSM_PAYLOAD(n) < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", n->nlmsg_len);
+ return -EINVAL;
+ }
+
+ if (ctx->pending != NULL) {
+ const struct if_stats_msg *pending = NLMSG_DATA(ctx->pending);
+ const struct if_stats_msg *ifsm = NLMSG_DATA(n);
+
+ if (pending->ifindex == ifsm->ifindex)
+ return ipstats_dump_merge(ctx, n);
+
+ rc = ipstats_dump_pending(ctx);
+ if (rc)
+ return rc;
+ }
+
+ ctx->pending = malloc(n->nlmsg_len);
+ if (ctx->pending == NULL) {
+ fprintf(stderr, "Error saving netlink answer: %s\n",
+ strerror(errno));
+ return -ENOMEM;
+ }
+
+ memcpy(ctx->pending, n, n->nlmsg_len);
+ return 0;
+}
+
static int ipstats_dump(struct ipstats_stat_enabled *enabled)
{
- int rc = 0;
+ struct ipstats_dump_ctx ctx = {
+ .enabled = enabled,
+ };
if (rtnl_statsdump_req_filter(&rth, PF_UNSPEC, 0,
ipstats_req_add_filters,@@ -874,12 +999,13 @@ static int ipstats_dump(struct ipstats_stat_enabled *enabled)
return -2;
}
- if (rtnl_dump_filter(&rth, ipstats_dump_one, enabled) < 0) {
+ if (rtnl_dump_filter(&rth, ipstats_dump_one, &ctx) < 0) {
fprintf(stderr, "Dump terminated\n");
- rc = -2;
+ free(ctx.pending);
+ return -2;
}
- return rc;
+ return ipstats_dump_pending(&ctx);
}
static int