diff mbox series

[net-next,v3,3/5] net/ipv6: Remove expired routes with a separated list of routes.

Message ID 20240202082200.227031-4-thinker.li@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series Remove expired routes with a separated list of routes. | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1550 this patch: 1550
netdev/build_tools success Errors and warnings before: 1 this patch: 0
netdev/cc_maintainers success CCed 0 of 0 maintainers
netdev/build_clang success Errors and warnings before: 1068 this patch: 1068
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1579 this patch: 1579
netdev/checkpatch warning WARNING: line length of 105 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 90 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns WARNING: line length of 98 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-02-04--21-00 (tests: 721)

Commit Message

Kui-Feng Lee Feb. 2, 2024, 8:21 a.m. UTC
From: Kui-Feng Lee <thinker.li@gmail.com>

FIB6 GC walks trees of fib6_tables to remove expired routes. Walking a tree
can be expensive if the number of routes in a table is big, even if most of
them are permanent. Checking routes in a separated list of routes having
expiration will avoid this potential issue.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 include/net/ip6_fib.h | 35 +++++++++++++++++++++++++-
 net/ipv6/addrconf.c   | 50 +++++++++++++++++++++++++++++++------
 net/ipv6/ip6_fib.c    | 58 +++++++++++++++++++++++++++++++++++++++----
 net/ipv6/ndisc.c      | 11 +++++++-
 net/ipv6/route.c      | 14 +++++++++--
 5 files changed, 152 insertions(+), 16 deletions(-)

Comments

Hangbin Liu Feb. 4, 2024, 10:29 a.m. UTC | #1
On Fri, Feb 02, 2024 at 12:21:58AM -0800, thinker.li@gmail.com wrote:
> From: Kui-Feng Lee <thinker.li@gmail.com>
> 
> FIB6 GC walks trees of fib6_tables to remove expired routes. Walking a tree
> can be expensive if the number of routes in a table is big, even if most of
> them are permanent. Checking routes in a separated list of routes having
> expiration will avoid this potential issue.
> 
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>

FYI, I will wait for David Ahern to help review this patch.

Thanks
Hangbin
David Ahern Feb. 4, 2024, 4:10 p.m. UTC | #2
On 2/4/24 3:29 AM, Hangbin Liu wrote:
> FYI, I will wait for David Ahern to help review this patch.


I will get to it later today
David Ahern Feb. 5, 2024, 4:45 a.m. UTC | #3
On 2/2/24 1:21 AM, thinker.li@gmail.com wrote:
> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
> index 733ace18806c..36bfa987c314 100644
> --- a/net/ipv6/addrconf.c
> +++ b/net/ipv6/addrconf.c
> @@ -1255,6 +1255,7 @@ static void
>  cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires,
>  		     bool del_rt, bool del_peer)
>  {
> +	struct fib6_table *table;
>  	struct fib6_info *f6i;
>  
>  	f6i = addrconf_get_prefix_route(del_peer ? &ifp->peer_addr : &ifp->addr,

addrconf_get_prefix_route walks the table, so you know it is already
there ...

> @@ -1264,8 +1265,18 @@ cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires,
>  		if (del_rt)
>  			ip6_del_rt(dev_net(ifp->idev->dev), f6i, false);
>  		else {
> -			if (!(f6i->fib6_flags & RTF_EXPIRES))
> +			if (!(f6i->fib6_flags & RTF_EXPIRES)) {
> +				table = f6i->fib6_table;
> +				spin_lock_bh(&table->tb6_lock);
>  				fib6_set_expires(f6i, expires);
> +				/* If fib6_node is null, the f6i is just
> +				 * removed from the table.
> +				 */
> +				if (rcu_dereference_protected(f6i->fib6_node,

... meaning this check should not be needed

> +							      lockdep_is_held(&table->tb6_lock)))
> +					fib6_add_gc_list(f6i);
> +				spin_unlock_bh(&table->tb6_lock);
> +			}
>  			fib6_info_release(f6i);
>  		}
>  	}
> @@ -2706,6 +2717,7 @@ EXPORT_SYMBOL_GPL(addrconf_prefix_rcv_add_addr);
>  void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
>  {
>  	struct prefix_info *pinfo;
> +	struct fib6_table *table;
>  	__u32 valid_lft;
>  	__u32 prefered_lft;
>  	int addr_type, err;
> @@ -2782,11 +2794,23 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
>  			if (valid_lft == 0) {
>  				ip6_del_rt(net, rt, false);
>  				rt = NULL;
> -			} else if (addrconf_finite_timeout(rt_expires)) {
> -				/* not infinity */
> -				fib6_set_expires(rt, jiffies + rt_expires);
>  			} else {
> -				fib6_clean_expires(rt);
> +				table = rt->fib6_table;
> +				spin_lock_bh(&table->tb6_lock);

when it comes to locking, I prefer the lock and unlock lines to *pop* -
meaning newline on both sides so it is clear and stands out.

> +				if (addrconf_finite_timeout(rt_expires)) {
> +					/* not infinity */
> +					fib6_set_expires(rt, jiffies + rt_expires);
> +					/* If fib6_node is null, the f6i is
> +					 * just removed from the table.
> +					 */
> +					if (rcu_dereference_protected(rt->fib6_node,

similarly here, this code is entered because rt is set based on
addrconf_get_prefix_route.

> +								      lockdep_is_held(&table->tb6_lock)))
> +						fib6_add_gc_list(rt);
> +				} else {
> +					fib6_clean_expires(rt);
> +					fib6_remove_gc_list(rt);
> +				}
> +				spin_unlock_bh(&table->tb6_lock);
>  			}
>  		} else if (valid_lft) {
>  			clock_t expires = 0;
> @@ -4741,6 +4765,7 @@ static int modify_prefix_route(struct inet6_ifaddr *ifp,
>  			       unsigned long expires, u32 flags,
>  			       bool modify_peer)
>  {
> +	struct fib6_table *table;
>  	struct fib6_info *f6i;
>  	u32 prio;
>  
> @@ -4761,10 +4786,21 @@ static int modify_prefix_route(struct inet6_ifaddr *ifp,
>  				      ifp->rt_priority, ifp->idev->dev,
>  				      expires, flags, GFP_KERNEL);
>  	} else {
> -		if (!expires)
> +		table = f6i->fib6_table;
> +		spin_lock_bh(&table->tb6_lock);
> +		if (!expires) {
>  			fib6_clean_expires(f6i);
> -		else
> +			fib6_remove_gc_list(f6i);
> +		} else {
>  			fib6_set_expires(f6i, expires);
> +			/* If fib6_node is null, the f6i is just removed
> +			 * from the table.
> +			 */
> +			if (rcu_dereference_protected(f6i->fib6_node,

and here as well. f6i is set based on a table lookup.

> +						      lockdep_is_held(&table->tb6_lock)))
> +				fib6_add_gc_list(f6i);
> +		}
> +		spin_unlock_bh(&table->tb6_lock);
>  
>  		fib6_info_release(f6i);
>  	}

...

> diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
> index a68462668158..5ca9fd4f7945 100644
> --- a/net/ipv6/ndisc.c
> +++ b/net/ipv6/ndisc.c
> @@ -1410,8 +1410,17 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
>  		inet6_rt_notify(RTM_NEWROUTE, rt, &nlinfo, NLM_F_REPLACE);
>  	}
>  
> -	if (rt)
> +	if (rt) {
> +		spin_lock_bh(&rt->fib6_table->tb6_lock);
>  		fib6_set_expires(rt, jiffies + (HZ * lifetime));
> +		/* If fib6_node is null, the f6i is just removed from the
> +		 * table.
> +		 */

How about:
		/* If fib6_node is NULL, the route was removed between
		 * the rt6_get_dflt_router or rt6_add_dflt_router calls
		 * above and here.
		 */

> +		if (rcu_dereference_protected(rt->fib6_node,> +					      lockdep_is_held(&rt->fib6_table->tb6_lock)))
> +			fib6_add_gc_list(rt);
> +		spin_unlock_bh(&rt->fib6_table->tb6_lock);
> +	}
>  	if (in6_dev->cnf.accept_ra_min_hop_limit < 256 &&
>  	    ra_msg->icmph.icmp6_hop_limit) {
>  		if (in6_dev->cnf.accept_ra_min_hop_limit <= ra_msg->icmph.icmp6_hop_limit) {
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index dd6ff5b20918..cfaf226ecf98 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -989,10 +989,20 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
>  				 (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
>  
>  	if (rt) {
> -		if (!addrconf_finite_timeout(lifetime))
> +		spin_lock_bh(&rt->fib6_table->tb6_lock);
> +		if (!addrconf_finite_timeout(lifetime)) {
>  			fib6_clean_expires(rt);
> -		else
> +			fib6_remove_gc_list(rt);
> +		} else {
>  			fib6_set_expires(rt, jiffies + HZ * lifetime);
> +			/* If fib6_node is null, the f6i is just removed
> +			 * from the table.
> +			 */

Similarly, enhance the comment:
			/* If fib6_node is NULL, the route was removed
			 * between the get or add calls above and here.
			 */
> +			if (rcu_dereference_protected(rt->fib6_node,
> +						      lockdep_is_held(&rt->fib6_table->tb6_lock)))
> +				fib6_add_gc_list(rt);
> +		}
> +		spin_unlock_bh(&rt->fib6_table->tb6_lock);
>  
>  		fib6_info_release(rt);
>  	}
David Ahern Feb. 5, 2024, 3:24 p.m. UTC | #4
On 2/4/24 9:45 PM, David Ahern wrote:
>> @@ -1264,8 +1265,18 @@ cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires,
>>  		if (del_rt)
>>  			ip6_del_rt(dev_net(ifp->idev->dev), f6i, false);
>>  		else {
>> -			if (!(f6i->fib6_flags & RTF_EXPIRES))
>> +			if (!(f6i->fib6_flags & RTF_EXPIRES)) {
>> +				table = f6i->fib6_table;
>> +				spin_lock_bh(&table->tb6_lock);
>>  				fib6_set_expires(f6i, expires);
>> +				/* If fib6_node is null, the f6i is just
>> +				 * removed from the table.
>> +				 */
>> +				if (rcu_dereference_protected(f6i->fib6_node,
> 
> ... meaning this check should not be needed

reviewing this patch again this morning, and yes, I believe this check
is needed here and other places. Given that all of the instances check
if the route entry is still in the table, it should be consolidated into
fb6_add_gc_list.

> 
>> +							      lockdep_is_held(&table->tb6_lock)))
>> +					fib6_add_gc_list(f6i);
>> +				spin_unlock_bh(&table->tb6_lock);
>> +			}
>>  			fib6_info_release(f6i);
>>  		}
>>  	}
diff mbox series

Patch

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 360b12e61850..fc581aa7802f 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -173,6 +173,9 @@  struct fib6_info {
 
 	refcount_t			fib6_ref;
 	unsigned long			expires;
+
+	struct hlist_node		gc_link;
+
 	struct dst_metrics		*fib6_metrics;
 #define fib6_pmtu		fib6_metrics->metrics[RTAX_MTU-1]
 
@@ -241,12 +244,18 @@  static inline bool fib6_requires_src(const struct fib6_info *rt)
 	return rt->fib6_src.plen > 0;
 }
 
+/* The callers should hold f6i->fib6_table->tb6_lock if a route has ever
+ * been added to a table before.
+ */
 static inline void fib6_clean_expires(struct fib6_info *f6i)
 {
 	f6i->fib6_flags &= ~RTF_EXPIRES;
 	f6i->expires = 0;
 }
 
+/* The callers should hold f6i->fib6_table->tb6_lock if a route has ever
+ * been added to a table before.
+ */
 static inline void fib6_set_expires(struct fib6_info *f6i,
 				    unsigned long expires)
 {
@@ -327,8 +336,10 @@  static inline bool fib6_info_hold_safe(struct fib6_info *f6i)
 
 static inline void fib6_info_release(struct fib6_info *f6i)
 {
-	if (f6i && refcount_dec_and_test(&f6i->fib6_ref))
+	if (f6i && refcount_dec_and_test(&f6i->fib6_ref)) {
+		DEBUG_NET_WARN_ON_ONCE(!hlist_unhashed(&f6i->gc_link));
 		call_rcu(&f6i->rcu, fib6_info_destroy_rcu);
+	}
 }
 
 enum fib6_walk_state {
@@ -382,6 +393,7 @@  struct fib6_table {
 	struct inet_peer_base	tb6_peers;
 	unsigned int		flags;
 	unsigned int		fib_seq;
+	struct hlist_head       tb6_gc_hlist;	/* GC candidates */
 #define RT6_TABLE_HAS_DFLT_ROUTER	BIT(0)
 };
 
@@ -498,6 +510,27 @@  void fib6_gc_cleanup(void);
 
 int fib6_init(void);
 
+/* Add the route to the gc list if it is not already there
+ *
+ * The callers should hold f6i->fib6_table->tb6_lock and make sure the
+ * route is on a table.
+ */
+static inline void fib6_add_gc_list(struct fib6_info *f6i)
+{
+	if (hlist_unhashed(&f6i->gc_link))
+		hlist_add_head(&f6i->gc_link, &f6i->fib6_table->tb6_gc_hlist);
+}
+
+/* Remove the route from the gc list if it is on the list.
+ *
+ * The callers should hold f6i->fib6_table->tb6_lock.
+ */
+static inline void fib6_remove_gc_list(struct fib6_info *f6i)
+{
+	if (!hlist_unhashed(&f6i->gc_link))
+		hlist_del_init(&f6i->gc_link);
+}
+
 struct ipv6_route_iter {
 	struct seq_net_private p;
 	struct fib6_walker w;
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 733ace18806c..36bfa987c314 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1255,6 +1255,7 @@  static void
 cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires,
 		     bool del_rt, bool del_peer)
 {
+	struct fib6_table *table;
 	struct fib6_info *f6i;
 
 	f6i = addrconf_get_prefix_route(del_peer ? &ifp->peer_addr : &ifp->addr,
@@ -1264,8 +1265,18 @@  cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires,
 		if (del_rt)
 			ip6_del_rt(dev_net(ifp->idev->dev), f6i, false);
 		else {
-			if (!(f6i->fib6_flags & RTF_EXPIRES))
+			if (!(f6i->fib6_flags & RTF_EXPIRES)) {
+				table = f6i->fib6_table;
+				spin_lock_bh(&table->tb6_lock);
 				fib6_set_expires(f6i, expires);
+				/* If fib6_node is null, the f6i is just
+				 * removed from the table.
+				 */
+				if (rcu_dereference_protected(f6i->fib6_node,
+							      lockdep_is_held(&table->tb6_lock)))
+					fib6_add_gc_list(f6i);
+				spin_unlock_bh(&table->tb6_lock);
+			}
 			fib6_info_release(f6i);
 		}
 	}
@@ -2706,6 +2717,7 @@  EXPORT_SYMBOL_GPL(addrconf_prefix_rcv_add_addr);
 void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 {
 	struct prefix_info *pinfo;
+	struct fib6_table *table;
 	__u32 valid_lft;
 	__u32 prefered_lft;
 	int addr_type, err;
@@ -2782,11 +2794,23 @@  void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 			if (valid_lft == 0) {
 				ip6_del_rt(net, rt, false);
 				rt = NULL;
-			} else if (addrconf_finite_timeout(rt_expires)) {
-				/* not infinity */
-				fib6_set_expires(rt, jiffies + rt_expires);
 			} else {
-				fib6_clean_expires(rt);
+				table = rt->fib6_table;
+				spin_lock_bh(&table->tb6_lock);
+				if (addrconf_finite_timeout(rt_expires)) {
+					/* not infinity */
+					fib6_set_expires(rt, jiffies + rt_expires);
+					/* If fib6_node is null, the f6i is
+					 * just removed from the table.
+					 */
+					if (rcu_dereference_protected(rt->fib6_node,
+								      lockdep_is_held(&table->tb6_lock)))
+						fib6_add_gc_list(rt);
+				} else {
+					fib6_clean_expires(rt);
+					fib6_remove_gc_list(rt);
+				}
+				spin_unlock_bh(&table->tb6_lock);
 			}
 		} else if (valid_lft) {
 			clock_t expires = 0;
@@ -4741,6 +4765,7 @@  static int modify_prefix_route(struct inet6_ifaddr *ifp,
 			       unsigned long expires, u32 flags,
 			       bool modify_peer)
 {
+	struct fib6_table *table;
 	struct fib6_info *f6i;
 	u32 prio;
 
@@ -4761,10 +4786,21 @@  static int modify_prefix_route(struct inet6_ifaddr *ifp,
 				      ifp->rt_priority, ifp->idev->dev,
 				      expires, flags, GFP_KERNEL);
 	} else {
-		if (!expires)
+		table = f6i->fib6_table;
+		spin_lock_bh(&table->tb6_lock);
+		if (!expires) {
 			fib6_clean_expires(f6i);
-		else
+			fib6_remove_gc_list(f6i);
+		} else {
 			fib6_set_expires(f6i, expires);
+			/* If fib6_node is null, the f6i is just removed
+			 * from the table.
+			 */
+			if (rcu_dereference_protected(f6i->fib6_node,
+						      lockdep_is_held(&table->tb6_lock)))
+				fib6_add_gc_list(f6i);
+		}
+		spin_unlock_bh(&table->tb6_lock);
 
 		fib6_info_release(f6i);
 	}
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 38a0348b1d17..d53dc519d317 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -160,6 +160,8 @@  struct fib6_info *fib6_info_alloc(gfp_t gfp_flags, bool with_fib6_nh)
 	INIT_LIST_HEAD(&f6i->fib6_siblings);
 	refcount_set(&f6i->fib6_ref, 1);
 
+	INIT_HLIST_NODE(&f6i->gc_link);
+
 	return f6i;
 }
 
@@ -246,6 +248,7 @@  static struct fib6_table *fib6_alloc_table(struct net *net, u32 id)
 				   net->ipv6.fib6_null_entry);
 		table->tb6_root.fn_flags = RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO;
 		inet_peer_base_init(&table->tb6_peers);
+		INIT_HLIST_HEAD(&table->tb6_gc_hlist);
 	}
 
 	return table;
@@ -1055,6 +1058,9 @@  static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
 				    lockdep_is_held(&table->tb6_lock));
 		}
 	}
+
+	fib6_clean_expires(rt);
+	fib6_remove_gc_list(rt);
 }
 
 /*
@@ -1115,10 +1121,13 @@  static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
 					rt->fib6_nsiblings = 0;
 				if (!(iter->fib6_flags & RTF_EXPIRES))
 					return -EEXIST;
-				if (!(rt->fib6_flags & RTF_EXPIRES))
+				if (!(rt->fib6_flags & RTF_EXPIRES)) {
 					fib6_clean_expires(iter);
-				else
+					fib6_remove_gc_list(iter);
+				} else {
 					fib6_set_expires(iter, rt->expires);
+					fib6_add_gc_list(iter);
+				}
 
 				if (rt->fib6_pmtu)
 					fib6_metric_set(iter, RTAX_MTU,
@@ -1477,6 +1486,10 @@  int fib6_add(struct fib6_node *root, struct fib6_info *rt,
 		if (rt->nh)
 			list_add(&rt->nh_list, &rt->nh->f6i_list);
 		__fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));
+
+		if (rt->fib6_flags & RTF_EXPIRES)
+			fib6_add_gc_list(rt);
+
 		fib6_start_gc(info->nl_net, rt);
 	}
 
@@ -2280,9 +2293,8 @@  static void fib6_flush_trees(struct net *net)
  *	Garbage collection
  */
 
-static int fib6_age(struct fib6_info *rt, void *arg)
+static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
 {
-	struct fib6_gc_args *gc_args = arg;
 	unsigned long now = jiffies;
 
 	/*
@@ -2307,6 +2319,40 @@  static int fib6_age(struct fib6_info *rt, void *arg)
 	return 0;
 }
 
+static void fib6_gc_table(struct net *net,
+			  struct fib6_table *tb6,
+			  struct fib6_gc_args *gc_args)
+{
+	struct fib6_info *rt;
+	struct hlist_node *n;
+	struct nl_info info = {
+		.nl_net = net,
+		.skip_notify = false,
+	};
+
+	hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link)
+		if (fib6_age(rt, gc_args) == -1)
+			fib6_del(rt, &info);
+}
+
+static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)
+{
+	struct fib6_table *table;
+	struct hlist_head *head;
+	unsigned int h;
+
+	rcu_read_lock();
+	for (h = 0; h < FIB6_TABLE_HASHSZ; h++) {
+		head = &net->ipv6.fib_table_hash[h];
+		hlist_for_each_entry_rcu(table, head, tb6_hlist) {
+			spin_lock_bh(&table->tb6_lock);
+			fib6_gc_table(net, table, gc_args);
+			spin_unlock_bh(&table->tb6_lock);
+		}
+	}
+	rcu_read_unlock();
+}
+
 void fib6_run_gc(unsigned long expires, struct net *net, bool force)
 {
 	struct fib6_gc_args gc_args;
@@ -2322,7 +2368,7 @@  void fib6_run_gc(unsigned long expires, struct net *net, bool force)
 			  net->ipv6.sysctl.ip6_rt_gc_interval;
 	gc_args.more = 0;
 
-	fib6_clean_all(net, fib6_age, &gc_args);
+	fib6_gc_all(net, &gc_args);
 	now = jiffies;
 	net->ipv6.ip6_rt_last_gc = now;
 
@@ -2382,6 +2428,7 @@  static int __net_init fib6_net_init(struct net *net)
 	net->ipv6.fib6_main_tbl->tb6_root.fn_flags =
 		RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO;
 	inet_peer_base_init(&net->ipv6.fib6_main_tbl->tb6_peers);
+	INIT_HLIST_HEAD(&net->ipv6.fib6_main_tbl->tb6_gc_hlist);
 
 #ifdef CONFIG_IPV6_MULTIPLE_TABLES
 	net->ipv6.fib6_local_tbl = kzalloc(sizeof(*net->ipv6.fib6_local_tbl),
@@ -2394,6 +2441,7 @@  static int __net_init fib6_net_init(struct net *net)
 	net->ipv6.fib6_local_tbl->tb6_root.fn_flags =
 		RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO;
 	inet_peer_base_init(&net->ipv6.fib6_local_tbl->tb6_peers);
+	INIT_HLIST_HEAD(&net->ipv6.fib6_local_tbl->tb6_gc_hlist);
 #endif
 	fib6_tables_init(net);
 
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index a68462668158..5ca9fd4f7945 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -1410,8 +1410,17 @@  static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 		inet6_rt_notify(RTM_NEWROUTE, rt, &nlinfo, NLM_F_REPLACE);
 	}
 
-	if (rt)
+	if (rt) {
+		spin_lock_bh(&rt->fib6_table->tb6_lock);
 		fib6_set_expires(rt, jiffies + (HZ * lifetime));
+		/* If fib6_node is null, the f6i is just removed from the
+		 * table.
+		 */
+		if (rcu_dereference_protected(rt->fib6_node,
+					      lockdep_is_held(&rt->fib6_table->tb6_lock)))
+			fib6_add_gc_list(rt);
+		spin_unlock_bh(&rt->fib6_table->tb6_lock);
+	}
 	if (in6_dev->cnf.accept_ra_min_hop_limit < 256 &&
 	    ra_msg->icmph.icmp6_hop_limit) {
 		if (in6_dev->cnf.accept_ra_min_hop_limit <= ra_msg->icmph.icmp6_hop_limit) {
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index dd6ff5b20918..cfaf226ecf98 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -989,10 +989,20 @@  int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
 				 (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
 
 	if (rt) {
-		if (!addrconf_finite_timeout(lifetime))
+		spin_lock_bh(&rt->fib6_table->tb6_lock);
+		if (!addrconf_finite_timeout(lifetime)) {
 			fib6_clean_expires(rt);
-		else
+			fib6_remove_gc_list(rt);
+		} else {
 			fib6_set_expires(rt, jiffies + HZ * lifetime);
+			/* If fib6_node is null, the f6i is just removed
+			 * from the table.
+			 */
+			if (rcu_dereference_protected(rt->fib6_node,
+						      lockdep_is_held(&rt->fib6_table->tb6_lock)))
+				fib6_add_gc_list(rt);
+		}
+		spin_unlock_bh(&rt->fib6_table->tb6_lock);
 
 		fib6_info_release(rt);
 	}