162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/l3mdev/l3mdev.c - L3 master device implementation
462306a36Sopenharmony_ci * Copyright (c) 2015 Cumulus Networks
562306a36Sopenharmony_ci * Copyright (c) 2015 David Ahern <dsa@cumulusnetworks.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/netdevice.h>
962306a36Sopenharmony_ci#include <net/fib_rules.h>
1062306a36Sopenharmony_ci#include <net/l3mdev.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistatic DEFINE_SPINLOCK(l3mdev_lock);
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistruct l3mdev_handler {
1562306a36Sopenharmony_ci	lookup_by_table_id_t dev_lookup;
1662306a36Sopenharmony_ci};
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic struct l3mdev_handler l3mdev_handlers[L3MDEV_TYPE_MAX + 1];
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int l3mdev_check_type(enum l3mdev_type l3type)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	if (l3type <= L3MDEV_TYPE_UNSPEC || l3type > L3MDEV_TYPE_MAX)
2362306a36Sopenharmony_ci		return -EINVAL;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	return 0;
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ciint l3mdev_table_lookup_register(enum l3mdev_type l3type,
2962306a36Sopenharmony_ci				 lookup_by_table_id_t fn)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	struct l3mdev_handler *hdlr;
3262306a36Sopenharmony_ci	int res;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	res = l3mdev_check_type(l3type);
3562306a36Sopenharmony_ci	if (res)
3662306a36Sopenharmony_ci		return res;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	spin_lock(&l3mdev_lock);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	if (hdlr->dev_lookup) {
4362306a36Sopenharmony_ci		res = -EBUSY;
4462306a36Sopenharmony_ci		goto unlock;
4562306a36Sopenharmony_ci	}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	hdlr->dev_lookup = fn;
4862306a36Sopenharmony_ci	res = 0;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciunlock:
5162306a36Sopenharmony_ci	spin_unlock(&l3mdev_lock);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return res;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_table_lookup_register);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_civoid l3mdev_table_lookup_unregister(enum l3mdev_type l3type,
5862306a36Sopenharmony_ci				    lookup_by_table_id_t fn)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct l3mdev_handler *hdlr;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (l3mdev_check_type(l3type))
6362306a36Sopenharmony_ci		return;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	spin_lock(&l3mdev_lock);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (hdlr->dev_lookup == fn)
7062306a36Sopenharmony_ci		hdlr->dev_lookup = NULL;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	spin_unlock(&l3mdev_lock);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_table_lookup_unregister);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ciint l3mdev_ifindex_lookup_by_table_id(enum l3mdev_type l3type,
7762306a36Sopenharmony_ci				      struct net *net, u32 table_id)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	lookup_by_table_id_t lookup;
8062306a36Sopenharmony_ci	struct l3mdev_handler *hdlr;
8162306a36Sopenharmony_ci	int ifindex = -EINVAL;
8262306a36Sopenharmony_ci	int res;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	res = l3mdev_check_type(l3type);
8562306a36Sopenharmony_ci	if (res)
8662306a36Sopenharmony_ci		return res;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	spin_lock(&l3mdev_lock);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	lookup = hdlr->dev_lookup;
9362306a36Sopenharmony_ci	if (!lookup)
9462306a36Sopenharmony_ci		goto unlock;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	ifindex = lookup(net, table_id);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ciunlock:
9962306a36Sopenharmony_ci	spin_unlock(&l3mdev_lock);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return ifindex;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_ifindex_lookup_by_table_id);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/**
10662306a36Sopenharmony_ci *	l3mdev_master_ifindex_rcu - get index of L3 master device
10762306a36Sopenharmony_ci *	@dev: targeted interface
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ciint l3mdev_master_ifindex_rcu(const struct net_device *dev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	int ifindex = 0;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (!dev)
11562306a36Sopenharmony_ci		return 0;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (netif_is_l3_master(dev)) {
11862306a36Sopenharmony_ci		ifindex = dev->ifindex;
11962306a36Sopenharmony_ci	} else if (netif_is_l3_slave(dev)) {
12062306a36Sopenharmony_ci		struct net_device *master;
12162306a36Sopenharmony_ci		struct net_device *_dev = (struct net_device *)dev;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		/* netdev_master_upper_dev_get_rcu calls
12462306a36Sopenharmony_ci		 * list_first_or_null_rcu to walk the upper dev list.
12562306a36Sopenharmony_ci		 * list_first_or_null_rcu does not handle a const arg. We aren't
12662306a36Sopenharmony_ci		 * making changes, just want the master device from that list so
12762306a36Sopenharmony_ci		 * typecast to remove the const
12862306a36Sopenharmony_ci		 */
12962306a36Sopenharmony_ci		master = netdev_master_upper_dev_get_rcu(_dev);
13062306a36Sopenharmony_ci		if (master)
13162306a36Sopenharmony_ci			ifindex = master->ifindex;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return ifindex;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_master_ifindex_rcu);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci/**
13962306a36Sopenharmony_ci *	l3mdev_master_upper_ifindex_by_index_rcu - get index of upper l3 master
14062306a36Sopenharmony_ci *					       device
14162306a36Sopenharmony_ci *	@net: network namespace for device index lookup
14262306a36Sopenharmony_ci *	@ifindex: targeted interface
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_ciint l3mdev_master_upper_ifindex_by_index_rcu(struct net *net, int ifindex)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct net_device *dev;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	dev = dev_get_by_index_rcu(net, ifindex);
14962306a36Sopenharmony_ci	while (dev && !netif_is_l3_master(dev))
15062306a36Sopenharmony_ci		dev = netdev_master_upper_dev_get_rcu(dev);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return dev ? dev->ifindex : 0;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_master_upper_ifindex_by_index_rcu);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci/**
15762306a36Sopenharmony_ci *	l3mdev_fib_table_rcu - get FIB table id associated with an L3
15862306a36Sopenharmony_ci *                             master interface
15962306a36Sopenharmony_ci *	@dev: targeted interface
16062306a36Sopenharmony_ci */
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ciu32 l3mdev_fib_table_rcu(const struct net_device *dev)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	u32 tb_id = 0;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (!dev)
16762306a36Sopenharmony_ci		return 0;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (netif_is_l3_master(dev)) {
17062306a36Sopenharmony_ci		if (dev->l3mdev_ops->l3mdev_fib_table)
17162306a36Sopenharmony_ci			tb_id = dev->l3mdev_ops->l3mdev_fib_table(dev);
17262306a36Sopenharmony_ci	} else if (netif_is_l3_slave(dev)) {
17362306a36Sopenharmony_ci		/* Users of netdev_master_upper_dev_get_rcu need non-const,
17462306a36Sopenharmony_ci		 * but current inet_*type functions take a const
17562306a36Sopenharmony_ci		 */
17662306a36Sopenharmony_ci		struct net_device *_dev = (struct net_device *) dev;
17762306a36Sopenharmony_ci		const struct net_device *master;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		master = netdev_master_upper_dev_get_rcu(_dev);
18062306a36Sopenharmony_ci		if (master &&
18162306a36Sopenharmony_ci		    master->l3mdev_ops->l3mdev_fib_table)
18262306a36Sopenharmony_ci			tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return tb_id;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_fib_table_rcu);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciu32 l3mdev_fib_table_by_index(struct net *net, int ifindex)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct net_device *dev;
19262306a36Sopenharmony_ci	u32 tb_id = 0;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (!ifindex)
19562306a36Sopenharmony_ci		return 0;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	rcu_read_lock();
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	dev = dev_get_by_index_rcu(net, ifindex);
20062306a36Sopenharmony_ci	if (dev)
20162306a36Sopenharmony_ci		tb_id = l3mdev_fib_table_rcu(dev);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	rcu_read_unlock();
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	return tb_id;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_fib_table_by_index);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci/**
21062306a36Sopenharmony_ci *	l3mdev_link_scope_lookup - IPv6 route lookup based on flow for link
21162306a36Sopenharmony_ci *			     local and multicast addresses
21262306a36Sopenharmony_ci *	@net: network namespace for device index lookup
21362306a36Sopenharmony_ci *	@fl6: IPv6 flow struct for lookup
21462306a36Sopenharmony_ci *	This function does not hold refcnt on the returned dst.
21562306a36Sopenharmony_ci *	Caller must hold rcu_read_lock().
21662306a36Sopenharmony_ci */
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistruct dst_entry *l3mdev_link_scope_lookup(struct net *net,
21962306a36Sopenharmony_ci					   struct flowi6 *fl6)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	struct dst_entry *dst = NULL;
22262306a36Sopenharmony_ci	struct net_device *dev;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	WARN_ON_ONCE(!rcu_read_lock_held());
22562306a36Sopenharmony_ci	if (fl6->flowi6_oif) {
22662306a36Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
22762306a36Sopenharmony_ci		if (dev && netif_is_l3_slave(dev))
22862306a36Sopenharmony_ci			dev = netdev_master_upper_dev_get_rcu(dev);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		if (dev && netif_is_l3_master(dev) &&
23162306a36Sopenharmony_ci		    dev->l3mdev_ops->l3mdev_link_scope_lookup)
23262306a36Sopenharmony_ci			dst = dev->l3mdev_ops->l3mdev_link_scope_lookup(dev, fl6);
23362306a36Sopenharmony_ci	}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return dst;
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_link_scope_lookup);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci/**
24062306a36Sopenharmony_ci *	l3mdev_fib_rule_match - Determine if flowi references an
24162306a36Sopenharmony_ci *				L3 master device
24262306a36Sopenharmony_ci *	@net: network namespace for device index lookup
24362306a36Sopenharmony_ci *	@fl:  flow struct
24462306a36Sopenharmony_ci *	@arg: store the table the rule matched with here
24562306a36Sopenharmony_ci */
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ciint l3mdev_fib_rule_match(struct net *net, struct flowi *fl,
24862306a36Sopenharmony_ci			  struct fib_lookup_arg *arg)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct net_device *dev;
25162306a36Sopenharmony_ci	int rc = 0;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* update flow ensures flowi_l3mdev is set when relevant */
25462306a36Sopenharmony_ci	if (!fl->flowi_l3mdev)
25562306a36Sopenharmony_ci		return 0;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	rcu_read_lock();
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	dev = dev_get_by_index_rcu(net, fl->flowi_l3mdev);
26062306a36Sopenharmony_ci	if (dev && netif_is_l3_master(dev) &&
26162306a36Sopenharmony_ci	    dev->l3mdev_ops->l3mdev_fib_table) {
26262306a36Sopenharmony_ci		arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
26362306a36Sopenharmony_ci		rc = 1;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	rcu_read_unlock();
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return rc;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_civoid l3mdev_update_flow(struct net *net, struct flowi *fl)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	struct net_device *dev;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	rcu_read_lock();
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (fl->flowi_oif) {
27862306a36Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl->flowi_oif);
27962306a36Sopenharmony_ci		if (dev) {
28062306a36Sopenharmony_ci			if (!fl->flowi_l3mdev)
28162306a36Sopenharmony_ci				fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci			/* oif set to L3mdev directs lookup to its table;
28462306a36Sopenharmony_ci			 * reset to avoid oif match in fib_lookup
28562306a36Sopenharmony_ci			 */
28662306a36Sopenharmony_ci			if (netif_is_l3_master(dev))
28762306a36Sopenharmony_ci				fl->flowi_oif = 0;
28862306a36Sopenharmony_ci			goto out;
28962306a36Sopenharmony_ci		}
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	if (fl->flowi_iif > LOOPBACK_IFINDEX && !fl->flowi_l3mdev) {
29362306a36Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl->flowi_iif);
29462306a36Sopenharmony_ci		if (dev)
29562306a36Sopenharmony_ci			fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ciout:
29962306a36Sopenharmony_ci	rcu_read_unlock();
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_update_flow);
302