Skip to content

Commit 1e47b48

Browse files
sbrivio-rhdavem330
authored andcommitted
ipv6: Dump route exceptions if requested
Since commit 2b760fc ("ipv6: hook up exception table to store dst cache"), route exceptions reside in a separate hash table, and won't be found by walking the FIB, so they won't be dumped to userspace on a RTM_GETROUTE message. This causes 'ip -6 route list cache' and 'ip -6 route flush cache' to have no function anymore: # ip -6 route get fc00:3::1 fc00:3::1 via fc00:1::2 dev veth_A-R1 src fc00:1::1 metric 1024 expires 539sec mtu 1400 pref medium # ip -6 route get fc00:4::1 fc00:4::1 via fc00:2::2 dev veth_A-R2 src fc00:2::1 metric 1024 expires 536sec mtu 1500 pref medium # ip -6 route list cache # ip -6 route flush cache # ip -6 route get fc00:3::1 fc00:3::1 via fc00:1::2 dev veth_A-R1 src fc00:1::1 metric 1024 expires 520sec mtu 1400 pref medium # ip -6 route get fc00:4::1 fc00:4::1 via fc00:2::2 dev veth_A-R2 src fc00:2::1 metric 1024 expires 519sec mtu 1500 pref medium because iproute2 lists cached routes using RTM_GETROUTE, and flushes them by listing all the routes, and deleting them with RTM_DELROUTE one by one. If cached routes are requested using the RTM_F_CLONED flag together with strict checking, or if no strict checking is requested (and hence we can't consistently apply filters), look up exceptions in the hash table associated with the current fib6_info in rt6_dump_route(), and, if present and not expired, add them to the dump. We might be unable to dump all the entries for a given node in a single message, so keep track of how many entries were handled for the current node in fib6_walker, and skip that amount in case we start from the same partially dumped node. When a partial dump restarts, as the starting node might change when 'sernum' changes, we have no guarantee that we need to skip the same amount of in-node entries. Therefore, we need two counters, and we need to zero the in-node counter if the node from which the dump is resumed differs. Note that, with the current version of iproute2, this only fixes the 'ip -6 route list cache': on a flush command, iproute2 doesn't pass RTM_F_CLONED and, due to this inconsistency, 'ip -6 route flush cache' is still unable to fetch the routes to be flushed. This will be addressed in a patch for iproute2. To flush cached routes, a procfs entry could be introduced instead: that's how it works for IPv4. We already have a rt6_flush_exception() function ready to be wired to it. However, this would not solve the issue for listing. Versions of iproute2 and kernel tested: iproute2 kernel 4.14.0 4.15.0 4.19.0 5.0.0 5.1.0 5.1.0, patched 3.18 list + + + + + + flush + + + + + + 4.4 list + + + + + + flush + + + + + + 4.9 list + + + + + + flush + + + + + + 4.14 list + + + + + + flush + + + + + + 4.15 list flush 4.19 list flush 5.0 list flush 5.1 list flush with list + + + + + + fix flush + + + + v7: - Explain usage of "skip" counters in commit message (suggested by David Ahern) v6: - Rebase onto net-next, use recently introduced nexthop walker - Make rt6_nh_dump_exceptions() a separate function (suggested by David Ahern) v5: - Use dump_routes and dump_exceptions from filter, ignore NLM_F_MATCH, update test results (flushing works with iproute2 < 5.0.0 now) v4: - Split NLM_F_MATCH and strict check handling in separate patches - Filter routes using RTM_F_CLONED: if it's not set, only return non-cached routes, and if it's set, only return cached routes: change requested by David Ahern and Martin Lau. This implies that iproute2 needs a separate patch to be able to flush IPv6 cached routes. This is not ideal because we can't fix the breakage caused by 2b760fc entirely in kernel. However, two years have passed since then, and this makes it more tolerable v3: - More descriptive comment about expired exceptions in rt6_dump_route() - Swap return values of rt6_dump_route() (suggested by Martin Lau) - Don't zero skip_in_node in case we don't dump anything in a given pass (also suggested by Martin Lau) - Remove check on RTM_F_CLONED altogether: in the current UAPI semantic, it's just a flag to indicate the route was cloned, not to filter on routes v2: Add tracking of number of entries to be skipped in current node after a partial dump. As we restart from the same node, if not all the exceptions for a given node fit in a single message, the dump will not terminate, as suggested by Martin Lau. This is a concrete possibility, setting up a big number of exceptions for the same route actually causes the issue, suggested by David Ahern. Reported-by: Jianlin Shi <[email protected]> Fixes: 2b760fc ("ipv6: hook up exception table to store dst cache") Signed-off-by: Stefano Brivio <[email protected]> Reviewed-by: David Ahern <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent bf9a8a0 commit 1e47b48

File tree

4 files changed

+116
-13
lines changed

4 files changed

+116
-13
lines changed

include/net/ip6_fib.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ struct fib6_walker {
316316
enum fib6_walk_state state;
317317
unsigned int skip;
318318
unsigned int count;
319+
unsigned int skip_in_node;
319320
int (*func)(struct fib6_walker *);
320321
void *args;
321322
};

include/net/ip6_route.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ struct rt6_rtnl_dump_arg {
197197
struct fib_dump_filter filter;
198198
};
199199

200-
int rt6_dump_route(struct fib6_info *f6i, void *p_arg);
200+
int rt6_dump_route(struct fib6_info *f6i, void *p_arg, unsigned int skip);
201201
void rt6_mtu_change(struct net_device *dev, unsigned int mtu);
202202
void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);
203203
void rt6_clean_tohost(struct net *net, struct in6_addr *gateway);

net/ipv6/ip6_fib.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,19 @@ static int fib6_dump_node(struct fib6_walker *w)
464464
struct fib6_info *rt;
465465

466466
for_each_fib6_walker_rt(w) {
467-
res = rt6_dump_route(rt, w->args);
467+
res = rt6_dump_route(rt, w->args, w->skip_in_node);
468468
if (res >= 0) {
469469
/* Frame is full, suspend walking */
470470
w->leaf = rt;
471+
472+
/* We'll restart from this node, so if some routes were
473+
* already dumped, skip them next time.
474+
*/
475+
w->skip_in_node += res;
476+
471477
return 1;
472478
}
479+
w->skip_in_node = 0;
473480

474481
/* Multipath routes are dumped in one route with the
475482
* RTA_MULTIPATH attribute. Jump 'rt' to point to the
@@ -521,6 +528,7 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
521528
if (cb->args[4] == 0) {
522529
w->count = 0;
523530
w->skip = 0;
531+
w->skip_in_node = 0;
524532

525533
spin_lock_bh(&table->tb6_lock);
526534
res = fib6_walk(net, w);
@@ -536,6 +544,7 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
536544
w->state = FWS_INIT;
537545
w->node = w->root;
538546
w->skip = w->count;
547+
w->skip_in_node = 0;
539548
} else
540549
w->skip = 0;
541550

@@ -2094,6 +2103,7 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root,
20942103
c.w.func = fib6_clean_node;
20952104
c.w.count = 0;
20962105
c.w.skip = 0;
2106+
c.w.skip_in_node = 0;
20972107
c.func = func;
20982108
c.sernum = sernum;
20992109
c.arg = arg;

net/ipv6/route.c

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5522,13 +5522,73 @@ static bool fib6_info_uses_dev(const struct fib6_info *f6i,
55225522
return false;
55235523
}
55245524

5525+
struct fib6_nh_exception_dump_walker {
5526+
struct rt6_rtnl_dump_arg *dump;
5527+
struct fib6_info *rt;
5528+
unsigned int flags;
5529+
unsigned int skip;
5530+
unsigned int count;
5531+
};
5532+
5533+
static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)
5534+
{
5535+
struct fib6_nh_exception_dump_walker *w = arg;
5536+
struct rt6_rtnl_dump_arg *dump = w->dump;
5537+
struct rt6_exception_bucket *bucket;
5538+
struct rt6_exception *rt6_ex;
5539+
int i, err;
5540+
5541+
bucket = fib6_nh_get_excptn_bucket(nh, NULL);
5542+
if (!bucket)
5543+
return 0;
5544+
5545+
for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
5546+
hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
5547+
if (w->skip) {
5548+
w->skip--;
5549+
continue;
5550+
}
5551+
5552+
/* Expiration of entries doesn't bump sernum, insertion
5553+
* does. Removal is triggered by insertion, so we can
5554+
* rely on the fact that if entries change between two
5555+
* partial dumps, this node is scanned again completely,
5556+
* see rt6_insert_exception() and fib6_dump_table().
5557+
*
5558+
* Count expired entries we go through as handled
5559+
* entries that we'll skip next time, in case of partial
5560+
* node dump. Otherwise, if entries expire meanwhile,
5561+
* we'll skip the wrong amount.
5562+
*/
5563+
if (rt6_check_expired(rt6_ex->rt6i)) {
5564+
w->count++;
5565+
continue;
5566+
}
5567+
5568+
err = rt6_fill_node(dump->net, dump->skb, w->rt,
5569+
&rt6_ex->rt6i->dst, NULL, NULL, 0,
5570+
RTM_NEWROUTE,
5571+
NETLINK_CB(dump->cb->skb).portid,
5572+
dump->cb->nlh->nlmsg_seq, w->flags);
5573+
if (err)
5574+
return err;
5575+
5576+
w->count++;
5577+
}
5578+
bucket++;
5579+
}
5580+
5581+
return 0;
5582+
}
5583+
55255584
/* Return -1 if done with node, number of handled routes on partial dump */
5526-
int rt6_dump_route(struct fib6_info *rt, void *p_arg)
5585+
int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)
55275586
{
55285587
struct rt6_rtnl_dump_arg *arg = (struct rt6_rtnl_dump_arg *) p_arg;
55295588
struct fib_dump_filter *filter = &arg->filter;
55305589
unsigned int flags = NLM_F_MULTI;
55315590
struct net *net = arg->net;
5591+
int count = 0;
55325592

55335593
if (rt == net->ipv6.fib6_null_entry)
55345594
return -1;
@@ -5538,19 +5598,51 @@ int rt6_dump_route(struct fib6_info *rt, void *p_arg)
55385598
/* success since this is not a prefix route */
55395599
return -1;
55405600
}
5541-
if (filter->filter_set) {
5542-
if ((filter->rt_type && rt->fib6_type != filter->rt_type) ||
5543-
(filter->dev && !fib6_info_uses_dev(rt, filter->dev)) ||
5544-
(filter->protocol && rt->fib6_protocol != filter->protocol)) {
5545-
return -1;
5546-
}
5601+
if (filter->filter_set &&
5602+
((filter->rt_type && rt->fib6_type != filter->rt_type) ||
5603+
(filter->dev && !fib6_info_uses_dev(rt, filter->dev)) ||
5604+
(filter->protocol && rt->fib6_protocol != filter->protocol))) {
5605+
return -1;
5606+
}
5607+
5608+
if (filter->filter_set ||
5609+
!filter->dump_routes || !filter->dump_exceptions) {
55475610
flags |= NLM_F_DUMP_FILTERED;
55485611
}
55495612

5550-
if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL, 0, RTM_NEWROUTE,
5551-
NETLINK_CB(arg->cb->skb).portid,
5552-
arg->cb->nlh->nlmsg_seq, flags))
5553-
return 0;
5613+
if (filter->dump_routes) {
5614+
if (skip) {
5615+
skip--;
5616+
} else {
5617+
if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL,
5618+
0, RTM_NEWROUTE,
5619+
NETLINK_CB(arg->cb->skb).portid,
5620+
arg->cb->nlh->nlmsg_seq, flags)) {
5621+
return 0;
5622+
}
5623+
count++;
5624+
}
5625+
}
5626+
5627+
if (filter->dump_exceptions) {
5628+
struct fib6_nh_exception_dump_walker w = { .dump = arg,
5629+
.rt = rt,
5630+
.flags = flags,
5631+
.skip = skip,
5632+
.count = 0 };
5633+
int err;
5634+
5635+
if (rt->nh) {
5636+
err = nexthop_for_each_fib6_nh(rt->nh,
5637+
rt6_nh_dump_exceptions,
5638+
&w);
5639+
} else {
5640+
err = rt6_nh_dump_exceptions(rt->fib6_nh, &w);
5641+
}
5642+
5643+
if (err)
5644+
return count += w.count;
5645+
}
55545646

55555647
return -1;
55565648
}

0 commit comments

Comments
 (0)