Commit 3dec89b1 authored by Kui-Feng Lee's avatar Kui-Feng Lee Committed by David S. Miller
Browse files

net/ipv6: Remove expired routes with a separated list of routes.



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: default avatarKui-Feng Lee <thinker.li@gmail.com>
Reviewed-by: default avatarDavid Ahern <dsahern@kernel.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d1470851
Loading
Loading
Loading
Loading
+51 −13
Original line number Diff line number Diff line
@@ -179,6 +179,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]

@@ -247,19 +250,6 @@ static inline bool fib6_requires_src(const struct fib6_info *rt)
	return rt->fib6_src.plen > 0;
}

static inline void fib6_clean_expires(struct fib6_info *f6i)
{
	f6i->fib6_flags &= ~RTF_EXPIRES;
	f6i->expires = 0;
}

static inline void fib6_set_expires(struct fib6_info *f6i,
				    unsigned long expires)
{
	f6i->expires = expires;
	f6i->fib6_flags |= RTF_EXPIRES;
}

static inline bool fib6_check_expired(const struct fib6_info *f6i)
{
	if (f6i->fib6_flags & RTF_EXPIRES)
@@ -267,6 +257,11 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)
	return false;
}

static inline bool fib6_has_expires(const struct fib6_info *f6i)
{
	return f6i->fib6_flags & RTF_EXPIRES;
}

/* Function to safely get fn->fn_sernum for passed in rt
 * and store result in passed in cookie.
 * Return true if we can get cookie safely
@@ -388,6 +383,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)
};

@@ -504,6 +500,48 @@ void fib6_gc_cleanup(void);

int fib6_init(void);

/* fib6_info must be locked by the caller, and fib6_info->fib6_table can be
 * NULL.
 */
static inline void fib6_set_expires_locked(struct fib6_info *f6i,
					   unsigned long expires)
{
	struct fib6_table *tb6;

	tb6 = f6i->fib6_table;
	f6i->expires = expires;
	if (tb6 && !fib6_has_expires(f6i))
		hlist_add_head(&f6i->gc_link, &tb6->tb6_gc_hlist);
	f6i->fib6_flags |= RTF_EXPIRES;
}

/* fib6_info must be locked by the caller, and fib6_info->fib6_table can be
 * NULL.  If fib6_table is NULL, the fib6_info will no be inserted into the
 * list of GC candidates until it is inserted into a table.
 */
static inline void fib6_set_expires(struct fib6_info *f6i,
				    unsigned long expires)
{
	spin_lock_bh(&f6i->fib6_table->tb6_lock);
	fib6_set_expires_locked(f6i, expires);
	spin_unlock_bh(&f6i->fib6_table->tb6_lock);
}

static inline void fib6_clean_expires_locked(struct fib6_info *f6i)
{
	if (fib6_has_expires(f6i))
		hlist_del_init(&f6i->gc_link);
	f6i->fib6_flags &= ~RTF_EXPIRES;
	f6i->expires = 0;
}

static inline void fib6_clean_expires(struct fib6_info *f6i)
{
	spin_lock_bh(&f6i->fib6_table->tb6_lock);
	fib6_clean_expires_locked(f6i);
	spin_unlock_bh(&f6i->fib6_table->tb6_lock);
}

struct ipv6_route_iter {
	struct seq_net_private p;
	struct fib6_walker w;
+49 −6
Original line number Diff line number Diff line
@@ -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;
@@ -1057,6 +1060,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
				    lockdep_is_held(&table->tb6_lock));
		}
	}

	fib6_clean_expires_locked(rt);
}

/*
@@ -1118,9 +1123,10 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
				if (!(iter->fib6_flags & RTF_EXPIRES))
					return -EEXIST;
				if (!(rt->fib6_flags & RTF_EXPIRES))
					fib6_clean_expires(iter);
					fib6_clean_expires_locked(iter);
				else
					fib6_set_expires(iter, rt->expires);
					fib6_set_expires_locked(iter,
								rt->expires);

				if (rt->fib6_pmtu)
					fib6_metric_set(iter, RTAX_MTU,
@@ -1479,6 +1485,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 (fib6_has_expires(rt))
			hlist_add_head(&rt->gc_link, &table->tb6_gc_hlist);

		fib6_start_gc(info->nl_net, rt);
	}

@@ -2285,9 +2295,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;

	/*
@@ -2295,7 +2304,7 @@ static int fib6_age(struct fib6_info *rt, void *arg)
	 *	Routes are expired even if they are in use.
	 */

	if (rt->fib6_flags & RTF_EXPIRES && rt->expires) {
	if (fib6_has_expires(rt) && rt->expires) {
		if (time_after(now, rt->expires)) {
			RT6_TRACE("expiring %p\n", rt);
			return -1;
@@ -2312,6 +2321,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;
@@ -2327,7 +2370,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;

+3 −3
Original line number Diff line number Diff line
@@ -3761,10 +3761,10 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
		rt->dst_nocount = true;

	if (cfg->fc_flags & RTF_EXPIRES)
		fib6_set_expires(rt, jiffies +
		fib6_set_expires_locked(rt, jiffies +
					clock_t_to_jiffies(cfg->fc_expires));
	else
		fib6_clean_expires(rt);
		fib6_clean_expires_locked(rt);

	if (cfg->fc_protocol == RTPROT_UNSPEC)
		cfg->fc_protocol = RTPROT_BOOT;