A per-cpu IPv6 route of a nexthop object records fib6_sernum of the netns it was created in, and rt6_is_valid() compares it with fib6_sernum of the netns its device belongs to when the route is checked. Every netns starts its fib6_sernum at 1 and increments it on each tree change, so different netns often hold the same value. When a device moves to another netns, such a route remains usable until nexthop_free_rcu() releases it, and fib6_info_destroy_rcu() queues that callback from inside an RCU callback, after the device has moved. If the fib6_sernum of the new netns matches the cached one in that window, ip6_dst_check() accepts the route and a socket in the old netns keeps sending through a device that now belongs to another netns. Take every fib6_sernum, including the initial one, from one global sequence, so that a cached route never matches the fib6_sernum of another netns. A connected UDP socket in netns A caches a route through an IPv6 nexthop object on a veth. The veth is moved to netns B and brought up with one RTM_NEWLINK, then the socket sends 200000 datagrams. A route is added to and removed from B 0 to 20 times before the move, 63 trials: before after trials sending after move 3 0 datagrams sent after move 8187 0 Fixes: 8f34e53b60b3 ("ipv6: Use global sernum for dst validation with nexthop objects") Signed-off-by: Qiliang Yuan --- net/ipv6/af_inet6.c | 1 - net/ipv6/ip6_fib.c | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index a9b8b4b42a1af..db4cccfdc565c 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -945,7 +945,6 @@ static int __net_init inet6_net_init(struct net *net) net->ipv6.sysctl.max_dst_opts_len = IP6_DEFAULT_MAX_DST_OPTS_LEN; net->ipv6.sysctl.max_hbh_opts_len = IP6_DEFAULT_MAX_HBH_OPTS_LEN; net->ipv6.sysctl.fib_notify_on_flag_change = 0; - atomic_set(&net->ipv6.fib6_sernum, 1); net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; net->ipv6.sysctl.ioam6_id_wide = IOAM6_DEFAULT_ID_WIDE; diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 0dc6ebee6e568..c66466be64451 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -89,13 +89,21 @@ static void fib6_walker_unlink(struct net *net, struct fib6_walker *w) write_unlock_bh(&net->ipv6.fib6_walker_lock); } +/* + * Draw every fib6_sernum from one sequence shared by all netns, so that a + * route cached against one netns never matches the sernum of another one, + * e.g. after its device moved. + */ +static atomic_t fib6_sernum_seq; + static int fib6_new_sernum(struct net *net) { - int new, old = atomic_read(&net->ipv6.fib6_sernum); + int new, old = atomic_read(&fib6_sernum_seq); do { new = old < INT_MAX ? old + 1 : 1; - } while (!atomic_try_cmpxchg(&net->ipv6.fib6_sernum, &old, new)); + } while (!atomic_try_cmpxchg(&fib6_sernum_seq, &old, new)); + atomic_set(&net->ipv6.fib6_sernum, new); return new; } @@ -2466,6 +2474,8 @@ static int __net_init fib6_net_init(struct net *net) size_t size = sizeof(struct hlist_head) * FIB6_TABLE_HASHSZ; int err; + fib6_new_sernum(net); + err = fib6_notifier_init(net); if (err) return err; -- 2.43.0