We started to use in-kernel filtering feature which allows to get only
needed tables (see iproute_dump_filter()). From the kernel side it's
implemented in net/ipv4/fib_frontend.c (inet_dump_fib), net/ipv6/ip6_fib.c
(inet6_dump_fib). The problem here is that behaviour of "ip route save"
was changed after
c7e6371bc ("ip route: Add protocol, table id and device to dump request").
If filters are used, then kernel returns ENOENT error if requested table
is absent, but in newly created net namespace even RT_TABLE_MAIN table
doesn't exist. It is really allocated, for instance, after issuing
"ip l set lo up".
Reproducer is fairly simple:
$ unshare -n ip route save > dump
Error: ipv4: FIB table does not exist.
Dump terminated
Expected result here is to get empty dump file (as it was before this
change).
v2: reworked, so, now it takes into account NLMSGERR_ATTR_MSG
(see nl_dump_ext_ack_done() function). We want to suppress error messages
in stderr about absent FIB table from kernel too.
v3: reworked to make code clearer. Introduced rtnl_suppressed_errors(),
rtnl_suppress_error() helpers. User may suppress up to 3 errors (may be
easily extended by changing SUPPRESS_ERRORS_INIT macro).
v4: reworked, rtnl_dump_filter_errhndlr() was introduced. Thanks
to Stephen Hemminger for comments and suggestions
v5: space fixes, commit message reformat, empty initializers
Fixes: c7e6371bc ("ip route: Add protocol, table id and device to dump request")
Cc: David Ahern <dsahern@gmail.com>
Cc: Stephen Hemminger <stephen@networkplumber.org>
Cc: Andrei Vagin <avagin@gmail.com>
Cc: Alexander Mikhalitsyn <alexander@mihalicyn.com>
Signed-off-by: Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
typedef int (*rtnl_filter_t)(struct nlmsghdr *n, void *);
+/**
+ * rtnl error handler called from
+ * rtnl_dump_done()
+ * rtnl_dump_error()
+ *
+ * Return value is a bitmask of the following values:
+ * RTNL_LET_NLERR
+ * error handled as usual
+ * RTNL_SUPPRESS_NLMSG_DONE_NLERR
+ * error in nlmsg_type == NLMSG_DONE will be suppressed
+ * RTNL_SUPPRESS_NLMSG_ERROR_NLERR
+ * error in nlmsg_type == NLMSG_ERROR will be suppressed
+ * and nlmsg will be skipped
+ * RTNL_SUPPRESS_NLERR - suppress error in both previous cases
+ */
+#define RTNL_LET_NLERR 0x01
+#define RTNL_SUPPRESS_NLMSG_DONE_NLERR 0x02
+#define RTNL_SUPPRESS_NLMSG_ERROR_NLERR 0x04
+#define RTNL_SUPPRESS_NLERR 0x06
+typedef int (*rtnl_err_hndlr_t)(struct nlmsghdr *n, void *);
+
typedef int (*rtnl_listen_filter_t)(struct rtnl_ctrl_data *,
struct nlmsghdr *n, void *);
struct rtnl_dump_filter_arg {
rtnl_filter_t filter;
void *arg1;
+ rtnl_err_hndlr_t errhndlr;
+ void *arg2;
__u16 nc_flags;
};
void *arg, __u16 nc_flags);
#define rtnl_dump_filter(rth, filter, arg) \
rtnl_dump_filter_nc(rth, filter, arg, 0)
+int rtnl_dump_filter_errhndlr_nc(struct rtnl_handle *rth,
+ rtnl_filter_t filter,
+ void *arg1,
+ rtnl_err_hndlr_t errhndlr,
+ void *arg2,
+ __u16 nc_flags);
+#define rtnl_dump_filter_errhndlr(rth, filter, farg, errhndlr, earg) \
+ rtnl_dump_filter_errhndlr_nc(rth, filter, farg, errhndlr, earg, 0)
+
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr **answer)
__attribute__((warn_unused_result));
}
}
+static int save_route_errhndlr(struct nlmsghdr *n, void *arg)
+{
+ int err = -*(int *)NLMSG_DATA(n);
+
+ if (n->nlmsg_type == NLMSG_DONE &&
+ filter.tb == RT_TABLE_MAIN &&
+ err == ENOENT)
+ return RTNL_SUPPRESS_NLMSG_DONE_NLERR;
+
+ return RTNL_LET_NLERR;
+}
+
static int iproute_list_flush_or_save(int argc, char **argv, int action)
{
int dump_family = preferred_family;
new_json_obj(json);
- if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+ if (rtnl_dump_filter_errhndlr(&rth, filter_fn, stdout,
+ save_route_errhndlr, NULL) < 0) {
fprintf(stderr, "Dump terminated\n");
return -2;
}
return sendmsg(rth->fd, &msg, 0);
}
-static int rtnl_dump_done(struct nlmsghdr *h)
+static int rtnl_dump_done(struct nlmsghdr *h,
+ const struct rtnl_dump_filter_arg *a)
{
int len = *(int *)NLMSG_DATA(h);
}
if (len < 0) {
+ errno = -len;
+
+ if (a->errhndlr(h, a->arg2) & RTNL_SUPPRESS_NLMSG_DONE_NLERR)
+ return 0;
+
/* check for any messages returned from kernel */
if (nl_dump_ext_ack_done(h, len))
return len;
- errno = -len;
switch (errno) {
case ENOENT:
case EOPNOTSUPP:
return 0;
}
-static void rtnl_dump_error(const struct rtnl_handle *rth,
- struct nlmsghdr *h)
+static int rtnl_dump_error(const struct rtnl_handle *rth,
+ struct nlmsghdr *h,
+ const struct rtnl_dump_filter_arg *a)
{
if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
if (rth->proto == NETLINK_SOCK_DIAG &&
(errno == ENOENT ||
errno == EOPNOTSUPP))
- return;
+ return -1;
+
+ if (a->errhndlr(h, a->arg2) & RTNL_SUPPRESS_NLMSG_ERROR_NLERR)
+ return 0;
if (!(rth->flags & RTNL_HANDLE_F_SUPPRESS_NLERR))
perror("RTNETLINK answers");
}
+
+ return -1;
}
static int __rtnl_recvmsg(int fd, struct msghdr *msg, int flags)
dump_intr = 1;
if (h->nlmsg_type == NLMSG_DONE) {
- err = rtnl_dump_done(h);
+ err = rtnl_dump_done(h, a);
if (err < 0) {
free(buf);
return -1;
}
if (h->nlmsg_type == NLMSG_ERROR) {
- rtnl_dump_error(rth, h);
- free(buf);
- return -1;
+ err = rtnl_dump_error(rth, h, a);
+ if (err < 0) {
+ free(buf);
+ return -1;
+ }
+
+ goto skip_it;
}
if (!rth->dump_fp) {
void *arg1, __u16 nc_flags)
{
const struct rtnl_dump_filter_arg a[2] = {
- { .filter = filter, .arg1 = arg1, .nc_flags = nc_flags, },
- { .filter = NULL, .arg1 = NULL, .nc_flags = 0, },
+ { .filter = filter, .arg1 = arg1,
+ .errhndlr = NULL, .arg2 = NULL, .nc_flags = nc_flags, },
+ { },
+ };
+
+ return rtnl_dump_filter_l(rth, a);
+}
+
+int rtnl_dump_filter_errhndlr_nc(struct rtnl_handle *rth,
+ rtnl_filter_t filter,
+ void *arg1,
+ rtnl_err_hndlr_t errhndlr,
+ void *arg2,
+ __u16 nc_flags)
+{
+ const struct rtnl_dump_filter_arg a[2] = {
+ { .filter = filter, .arg1 = arg1,
+ .errhndlr = errhndlr, .arg2 = arg2, .nc_flags = nc_flags, },
+ { },
};
return rtnl_dump_filter_l(rth, a);