diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 9dc2c182a263..7620974826a5 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -154,6 +154,7 @@ struct rt6_rtnl_dump_arg {
struct sk_buff *skb;
struct netlink_callback *cb;
struct net *net;
+ unsigned int rtm_flags;
};
int rt6_dump_route(struct rt6_info *rt, void *p_arg);diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index 7fb206bc42f9..5a070f24f111 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -276,7 +276,10 @@ enum rt_scope_t {
#define RTM_F_EQUALIZE 0x400 /* Multipath equalizer: NI */
#define RTM_F_PREFIX 0x800 /* Prefix addresses */
#define RTM_F_LOOKUP_TABLE 0x1000 /* set rtm_table to FIB lookup result */
-#define RTM_F_ALL_NEXTHOPS 0x2000 /* delete all nexthops (IPv6) */
+#define RTM_F_ALL_NEXTHOPS 0x2000 /* IPv6 flag:
+ * delete: remove all nexthops
+ * dump: nexthops can use RTA_MULTIPATH
+ */
/* Reserved table identifiers */
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index ef5485204522..ef2bf33e34e8 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -308,16 +308,27 @@ static void __net_init fib6_tables_init(struct net *net)
static int fib6_dump_node(struct fib6_walker *w)
{
+ struct rt6_rtnl_dump_arg *arg = (struct rt6_rtnl_dump_arg *)w->args;
int res;
struct rt6_info *rt;
for (rt = w->leaf; rt; rt = rt->dst.rt6_next) {
- res = rt6_dump_route(rt, w->args);
+ res = rt6_dump_route(rt, arg);
if (res < 0) {
/* Frame is full, suspend walking */
w->leaf = rt;
return 1;
}
+
+ /* if multipath routes are dumped in one route with
+ * the RTA_MULTIPATH attribute, then jump rt to point
+ * to the last sibling of this route (no need to dump
+ * the sibling routes again)
+ */
+ if ((arg->rtm_flags & RTM_F_ALL_NEXTHOPS) && rt->rt6i_nsiblings)
+ rt = list_last_entry(&rt->rt6i_siblings,
+ struct rt6_info,
+ rt6i_siblings);
}
w->leaf = NULL;
return 0;@@ -398,6 +409,7 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
struct fib6_walker *w;
struct fib6_table *tb;
struct hlist_head *head;
+ unsigned int flags = 0;
int res = 0;
s_h = cb->args[0];
@@ -422,9 +434,23 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
cb->args[2] = (long)w;
}
+ /* sadly, the size of the ancillary header can vary: the correct
+ * header is struct rtmsg as passed by libnl. iproute2 sends
+ * ifinfomsg [+ filter]. Technically, passing only rtgenmsg is
+ * sufficient since the header has not been parsed before. Luckily,
+ * the struct sizes sufficiently vary to detect the permutations.
+ * For the dump request to contain flags, require rtmsg header.
+ */
+ if (nlmsg_len(cb->nlh) == sizeof(struct rtmsg)) {
+ struct rtmsg *rtm = nlmsg_data(cb->nlh);
+
+ flags = rtm->rtm_flags;
+ }
+
arg.skb = skb;
arg.cb = cb;
arg.net = net;
+ arg.rtm_flags = flags;
w->args = &arg;
rcu_read_lock();diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index f207d4d0a782..ee57adf1a765 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3195,11 +3195,65 @@ static inline size_t rt6_nlmsg_size(struct rt6_info *rt)
+ lwtunnel_get_encap_size(rt->dst.lwtstate);
}
+static int rt6_nexthop_info(struct sk_buff *skb, struct rt6_info *rt,
+ unsigned int *flags)
+{
+ if (!netif_carrier_ok(rt->dst.dev)) {
+ *flags |= RTNH_F_LINKDOWN;
+ if (rt->rt6i_idev->cnf.ignore_routes_with_linkdown)
+ *flags |= RTNH_F_DEAD;
+ }
+
+ if (rt->rt6i_flags & RTF_GATEWAY) {
+ if (nla_put_in6_addr(skb, RTA_GATEWAY, &rt->rt6i_gateway) < 0)
+ goto nla_put_failure;
+ }
+
+ if (rt->dst.dev &&
+ nla_put_u32(skb, RTA_OIF, rt->dst.dev->ifindex))
+ goto nla_put_failure;
+
+ if (rt->dst.lwtstate &&
+ lwtunnel_fill_encap(skb, rt->dst.lwtstate) < 0)
+ goto nla_put_failure;
+
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
+static int rt6_add_nexthop(struct sk_buff *skb, struct rt6_info *rt)
+{
+ struct rtnexthop *rtnh;
+ unsigned int flags = 0;
+
+ rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh));
+ if (!rtnh)
+ goto nla_put_failure;
+
+ rtnh->rtnh_hops = 0;
+ rtnh->rtnh_ifindex = rt->dst.dev ? rt->dst.dev->ifindex : 0;
+
+ if (rt6_nexthop_info(skb, rt, &flags) < 0)
+ goto nla_put_failure;
+
+ rtnh->rtnh_flags = flags;
+
+ /* length of rtnetlink header + attributes */
+ rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh;
+
+ return 0;
+
+nla_put_failure:
+ return -EMSGSIZE;
+}
+
static int rt6_fill_node(struct net *net,
struct sk_buff *skb, struct rt6_info *rt,
struct in6_addr *dst, struct in6_addr *src,
int iif, int type, u32 portid, u32 seq,
- unsigned int flags)
+ unsigned int nlm_flags, unsigned int rtm_flags)
{
u32 metrics[RTAX_MAX];
struct rtmsg *rtm;@@ -3207,7 +3261,7 @@ static int rt6_fill_node(struct net *net,
long expires;
u32 table;
- nlh = nlmsg_put(skb, portid, seq, type, sizeof(*rtm), flags);
+ nlh = nlmsg_put(skb, portid, seq, type, sizeof(*rtm), nlm_flags);
if (!nlh)
return -EMSGSIZE;
@@ -3246,11 +3300,6 @@ static int rt6_fill_node(struct net *net,
else
rtm->rtm_type = RTN_UNICAST;
rtm->rtm_flags = 0;
- if (!netif_carrier_ok(rt->dst.dev)) {
- rtm->rtm_flags |= RTNH_F_LINKDOWN;
- if (rt->rt6i_idev->cnf.ignore_routes_with_linkdown)
- rtm->rtm_flags |= RTNH_F_DEAD;
- }
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
rtm->rtm_protocol = rt->rt6i_protocol;
if (rt->rt6i_flags & RTF_DYNAMIC)@@ -3314,17 +3363,36 @@ static int rt6_fill_node(struct net *net,
if (rtnetlink_put_metrics(skb, metrics) < 0)
goto nla_put_failure;
- if (rt->rt6i_flags & RTF_GATEWAY) {
- if (nla_put_in6_addr(skb, RTA_GATEWAY, &rt->rt6i_gateway) < 0)
- goto nla_put_failure;
- }
-
- if (rt->dst.dev &&
- nla_put_u32(skb, RTA_OIF, rt->dst.dev->ifindex))
- goto nla_put_failure;
if (nla_put_u32(skb, RTA_PRIORITY, rt->rt6i_metric))
goto nla_put_failure;
+ /* if user wants nexthops included via the RTA_MULTIPATH
+ * attribute, then walk the siblings list and add each
+ * as a nexthop
+ */
+ if ((rtm_flags & RTM_F_ALL_NEXTHOPS) && rt->rt6i_nsiblings) {
+ struct rt6_info *sibling, *next_sibling;
+ struct nlattr *mp;
+
+ mp = nla_nest_start(skb, RTA_MULTIPATH);
+ if (!mp)
+ goto nla_put_failure;
+
+ if (rt6_add_nexthop(skb, rt) < 0)
+ goto nla_put_failure;
+
+ list_for_each_entry_safe(sibling, next_sibling,
+ &rt->rt6i_siblings, rt6i_siblings) {
+ if (rt6_add_nexthop(skb, sibling) < 0)
+ goto nla_put_failure;
+ }
+
+ nla_nest_end(skb, mp);
+ } else {
+ if (rt6_nexthop_info(skb, rt, &rtm->rtm_flags) < 0)
+ goto nla_put_failure;
+ }
+
expires = (rt->rt6i_flags & RTF_EXPIRES) ? rt->dst.expires - jiffies : 0;
if (rtnl_put_cacheinfo(skb, &rt->dst, 0, expires, rt->dst.error) < 0)@@ -3333,8 +3401,6 @@ static int rt6_fill_node(struct net *net,
if (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt->rt6i_flags)))
goto nla_put_failure;
- if (lwtunnel_fill_encap(skb, rt->dst.lwtstate) < 0)
- goto nla_put_failure;
nlmsg_end(skb, nlh);
return 0;
@@ -3362,7 +3428,7 @@ int rt6_dump_route(struct rt6_info *rt, void *p_arg)
return rt6_fill_node(arg->net,
arg->skb, rt, NULL, NULL, 0, RTM_NEWROUTE,
NETLINK_CB(arg->cb->skb).portid, arg->cb->nlh->nlmsg_seq,
- NLM_F_MULTI);
+ NLM_F_MULTI, arg->rtm_flags);
}
static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh)
@@ -3453,7 +3519,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh)
err = rt6_fill_node(net, skb, rt, &fl6.daddr, &fl6.saddr, iif,
RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
- nlh->nlmsg_seq, 0);
+ nlh->nlmsg_seq, 0, 0);
if (err < 0) {
kfree_skb(skb);
goto errout;@@ -3480,7 +3546,7 @@ void inet6_rt_notify(int event, struct rt6_info *rt, struct nl_info *info,
goto errout;
err = rt6_fill_node(net, skb, rt, NULL, NULL, 0,
- event, info->portid, seq, nlm_flags);
+ event, info->portid, seq, nlm_flags, 0);
if (err < 0) {
/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
WARN_ON(err == -EMSGSIZE);--
2.1.4