18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * net/l3mdev/l3mdev.c - L3 master device implementation
48c2ecf20Sopenharmony_ci * Copyright (c) 2015 Cumulus Networks
58c2ecf20Sopenharmony_ci * Copyright (c) 2015 David Ahern <dsa@cumulusnetworks.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
98c2ecf20Sopenharmony_ci#include <net/fib_rules.h>
108c2ecf20Sopenharmony_ci#include <net/l3mdev.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(l3mdev_lock);
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistruct l3mdev_handler {
158c2ecf20Sopenharmony_ci	lookup_by_table_id_t dev_lookup;
168c2ecf20Sopenharmony_ci};
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic struct l3mdev_handler l3mdev_handlers[L3MDEV_TYPE_MAX + 1];
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic int l3mdev_check_type(enum l3mdev_type l3type)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	if (l3type <= L3MDEV_TYPE_UNSPEC || l3type > L3MDEV_TYPE_MAX)
238c2ecf20Sopenharmony_ci		return -EINVAL;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	return 0;
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ciint l3mdev_table_lookup_register(enum l3mdev_type l3type,
298c2ecf20Sopenharmony_ci				 lookup_by_table_id_t fn)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	struct l3mdev_handler *hdlr;
328c2ecf20Sopenharmony_ci	int res;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	res = l3mdev_check_type(l3type);
358c2ecf20Sopenharmony_ci	if (res)
368c2ecf20Sopenharmony_ci		return res;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	spin_lock(&l3mdev_lock);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	if (hdlr->dev_lookup) {
438c2ecf20Sopenharmony_ci		res = -EBUSY;
448c2ecf20Sopenharmony_ci		goto unlock;
458c2ecf20Sopenharmony_ci	}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	hdlr->dev_lookup = fn;
488c2ecf20Sopenharmony_ci	res = 0;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ciunlock:
518c2ecf20Sopenharmony_ci	spin_unlock(&l3mdev_lock);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	return res;
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_table_lookup_register);
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_civoid l3mdev_table_lookup_unregister(enum l3mdev_type l3type,
588c2ecf20Sopenharmony_ci				    lookup_by_table_id_t fn)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct l3mdev_handler *hdlr;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	if (l3mdev_check_type(l3type))
638c2ecf20Sopenharmony_ci		return;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	spin_lock(&l3mdev_lock);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (hdlr->dev_lookup == fn)
708c2ecf20Sopenharmony_ci		hdlr->dev_lookup = NULL;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	spin_unlock(&l3mdev_lock);
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_table_lookup_unregister);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ciint l3mdev_ifindex_lookup_by_table_id(enum l3mdev_type l3type,
778c2ecf20Sopenharmony_ci				      struct net *net, u32 table_id)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	lookup_by_table_id_t lookup;
808c2ecf20Sopenharmony_ci	struct l3mdev_handler *hdlr;
818c2ecf20Sopenharmony_ci	int ifindex = -EINVAL;
828c2ecf20Sopenharmony_ci	int res;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	res = l3mdev_check_type(l3type);
858c2ecf20Sopenharmony_ci	if (res)
868c2ecf20Sopenharmony_ci		return res;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	hdlr = &l3mdev_handlers[l3type];
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	spin_lock(&l3mdev_lock);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	lookup = hdlr->dev_lookup;
938c2ecf20Sopenharmony_ci	if (!lookup)
948c2ecf20Sopenharmony_ci		goto unlock;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	ifindex = lookup(net, table_id);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ciunlock:
998c2ecf20Sopenharmony_ci	spin_unlock(&l3mdev_lock);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	return ifindex;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_ifindex_lookup_by_table_id);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci/**
1068c2ecf20Sopenharmony_ci *	l3mdev_master_ifindex - get index of L3 master device
1078c2ecf20Sopenharmony_ci *	@dev: targeted interface
1088c2ecf20Sopenharmony_ci */
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ciint l3mdev_master_ifindex_rcu(const struct net_device *dev)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	int ifindex = 0;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	if (!dev)
1158c2ecf20Sopenharmony_ci		return 0;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (netif_is_l3_master(dev)) {
1188c2ecf20Sopenharmony_ci		ifindex = dev->ifindex;
1198c2ecf20Sopenharmony_ci	} else if (netif_is_l3_slave(dev)) {
1208c2ecf20Sopenharmony_ci		struct net_device *master;
1218c2ecf20Sopenharmony_ci		struct net_device *_dev = (struct net_device *)dev;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci		/* netdev_master_upper_dev_get_rcu calls
1248c2ecf20Sopenharmony_ci		 * list_first_or_null_rcu to walk the upper dev list.
1258c2ecf20Sopenharmony_ci		 * list_first_or_null_rcu does not handle a const arg. We aren't
1268c2ecf20Sopenharmony_ci		 * making changes, just want the master device from that list so
1278c2ecf20Sopenharmony_ci		 * typecast to remove the const
1288c2ecf20Sopenharmony_ci		 */
1298c2ecf20Sopenharmony_ci		master = netdev_master_upper_dev_get_rcu(_dev);
1308c2ecf20Sopenharmony_ci		if (master)
1318c2ecf20Sopenharmony_ci			ifindex = master->ifindex;
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	return ifindex;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_master_ifindex_rcu);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci/**
1398c2ecf20Sopenharmony_ci *	l3mdev_master_upper_ifindex_by_index - get index of upper l3 master
1408c2ecf20Sopenharmony_ci *					       device
1418c2ecf20Sopenharmony_ci *	@net: network namespace for device index lookup
1428c2ecf20Sopenharmony_ci *	@ifindex: targeted interface
1438c2ecf20Sopenharmony_ci */
1448c2ecf20Sopenharmony_ciint l3mdev_master_upper_ifindex_by_index_rcu(struct net *net, int ifindex)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	struct net_device *dev;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	dev = dev_get_by_index_rcu(net, ifindex);
1498c2ecf20Sopenharmony_ci	while (dev && !netif_is_l3_master(dev))
1508c2ecf20Sopenharmony_ci		dev = netdev_master_upper_dev_get_rcu(dev);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	return dev ? dev->ifindex : 0;
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_master_upper_ifindex_by_index_rcu);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci/**
1578c2ecf20Sopenharmony_ci *	l3mdev_fib_table_rcu - get FIB table id associated with an L3
1588c2ecf20Sopenharmony_ci *                             master interface
1598c2ecf20Sopenharmony_ci *	@dev: targeted interface
1608c2ecf20Sopenharmony_ci */
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ciu32 l3mdev_fib_table_rcu(const struct net_device *dev)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	u32 tb_id = 0;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	if (!dev)
1678c2ecf20Sopenharmony_ci		return 0;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (netif_is_l3_master(dev)) {
1708c2ecf20Sopenharmony_ci		if (dev->l3mdev_ops->l3mdev_fib_table)
1718c2ecf20Sopenharmony_ci			tb_id = dev->l3mdev_ops->l3mdev_fib_table(dev);
1728c2ecf20Sopenharmony_ci	} else if (netif_is_l3_slave(dev)) {
1738c2ecf20Sopenharmony_ci		/* Users of netdev_master_upper_dev_get_rcu need non-const,
1748c2ecf20Sopenharmony_ci		 * but current inet_*type functions take a const
1758c2ecf20Sopenharmony_ci		 */
1768c2ecf20Sopenharmony_ci		struct net_device *_dev = (struct net_device *) dev;
1778c2ecf20Sopenharmony_ci		const struct net_device *master;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		master = netdev_master_upper_dev_get_rcu(_dev);
1808c2ecf20Sopenharmony_ci		if (master &&
1818c2ecf20Sopenharmony_ci		    master->l3mdev_ops->l3mdev_fib_table)
1828c2ecf20Sopenharmony_ci			tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	return tb_id;
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_fib_table_rcu);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ciu32 l3mdev_fib_table_by_index(struct net *net, int ifindex)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	struct net_device *dev;
1928c2ecf20Sopenharmony_ci	u32 tb_id = 0;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	if (!ifindex)
1958c2ecf20Sopenharmony_ci		return 0;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	rcu_read_lock();
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	dev = dev_get_by_index_rcu(net, ifindex);
2008c2ecf20Sopenharmony_ci	if (dev)
2018c2ecf20Sopenharmony_ci		tb_id = l3mdev_fib_table_rcu(dev);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	rcu_read_unlock();
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return tb_id;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_fib_table_by_index);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci/**
2108c2ecf20Sopenharmony_ci *	l3mdev_link_scope_lookup - IPv6 route lookup based on flow for link
2118c2ecf20Sopenharmony_ci *			     local and multicast addresses
2128c2ecf20Sopenharmony_ci *	@net: network namespace for device index lookup
2138c2ecf20Sopenharmony_ci *	@fl6: IPv6 flow struct for lookup
2148c2ecf20Sopenharmony_ci *	This function does not hold refcnt on the returned dst.
2158c2ecf20Sopenharmony_ci *	Caller must hold rcu_read_lock().
2168c2ecf20Sopenharmony_ci */
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_cistruct dst_entry *l3mdev_link_scope_lookup(struct net *net,
2198c2ecf20Sopenharmony_ci					   struct flowi6 *fl6)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	struct dst_entry *dst = NULL;
2228c2ecf20Sopenharmony_ci	struct net_device *dev;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	WARN_ON_ONCE(!rcu_read_lock_held());
2258c2ecf20Sopenharmony_ci	if (fl6->flowi6_oif) {
2268c2ecf20Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
2278c2ecf20Sopenharmony_ci		if (dev && netif_is_l3_slave(dev))
2288c2ecf20Sopenharmony_ci			dev = netdev_master_upper_dev_get_rcu(dev);
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		if (dev && netif_is_l3_master(dev) &&
2318c2ecf20Sopenharmony_ci		    dev->l3mdev_ops->l3mdev_link_scope_lookup)
2328c2ecf20Sopenharmony_ci			dst = dev->l3mdev_ops->l3mdev_link_scope_lookup(dev, fl6);
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return dst;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_link_scope_lookup);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci/**
2408c2ecf20Sopenharmony_ci *	l3mdev_fib_rule_match - Determine if flowi references an
2418c2ecf20Sopenharmony_ci *				L3 master device
2428c2ecf20Sopenharmony_ci *	@net: network namespace for device index lookup
2438c2ecf20Sopenharmony_ci *	@fl:  flow struct
2448c2ecf20Sopenharmony_ci */
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ciint l3mdev_fib_rule_match(struct net *net, struct flowi *fl,
2478c2ecf20Sopenharmony_ci			  struct fib_lookup_arg *arg)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct net_device *dev;
2508c2ecf20Sopenharmony_ci	int rc = 0;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	rcu_read_lock();
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	dev = dev_get_by_index_rcu(net, fl->flowi_oif);
2558c2ecf20Sopenharmony_ci	if (dev && netif_is_l3_master(dev) &&
2568c2ecf20Sopenharmony_ci	    dev->l3mdev_ops->l3mdev_fib_table) {
2578c2ecf20Sopenharmony_ci		arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
2588c2ecf20Sopenharmony_ci		rc = 1;
2598c2ecf20Sopenharmony_ci		goto out;
2608c2ecf20Sopenharmony_ci	}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	dev = dev_get_by_index_rcu(net, fl->flowi_iif);
2638c2ecf20Sopenharmony_ci	if (dev && netif_is_l3_master(dev) &&
2648c2ecf20Sopenharmony_ci	    dev->l3mdev_ops->l3mdev_fib_table) {
2658c2ecf20Sopenharmony_ci		arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
2668c2ecf20Sopenharmony_ci		rc = 1;
2678c2ecf20Sopenharmony_ci		goto out;
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ciout:
2718c2ecf20Sopenharmony_ci	rcu_read_unlock();
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return rc;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_civoid l3mdev_update_flow(struct net *net, struct flowi *fl)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	struct net_device *dev;
2798c2ecf20Sopenharmony_ci	int ifindex;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	rcu_read_lock();
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	if (fl->flowi_oif) {
2848c2ecf20Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl->flowi_oif);
2858c2ecf20Sopenharmony_ci		if (dev) {
2868c2ecf20Sopenharmony_ci			ifindex = l3mdev_master_ifindex_rcu(dev);
2878c2ecf20Sopenharmony_ci			if (ifindex) {
2888c2ecf20Sopenharmony_ci				fl->flowi_oif = ifindex;
2898c2ecf20Sopenharmony_ci				fl->flowi_flags |= FLOWI_FLAG_SKIP_NH_OIF;
2908c2ecf20Sopenharmony_ci				goto out;
2918c2ecf20Sopenharmony_ci			}
2928c2ecf20Sopenharmony_ci		}
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	if (fl->flowi_iif) {
2968c2ecf20Sopenharmony_ci		dev = dev_get_by_index_rcu(net, fl->flowi_iif);
2978c2ecf20Sopenharmony_ci		if (dev) {
2988c2ecf20Sopenharmony_ci			ifindex = l3mdev_master_ifindex_rcu(dev);
2998c2ecf20Sopenharmony_ci			if (ifindex) {
3008c2ecf20Sopenharmony_ci				fl->flowi_iif = ifindex;
3018c2ecf20Sopenharmony_ci				fl->flowi_flags |= FLOWI_FLAG_SKIP_NH_OIF;
3028c2ecf20Sopenharmony_ci			}
3038c2ecf20Sopenharmony_ci		}
3048c2ecf20Sopenharmony_ci	}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ciout:
3078c2ecf20Sopenharmony_ci	rcu_read_unlock();
3088c2ecf20Sopenharmony_ci}
3098c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(l3mdev_update_flow);
310