12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21b69c6d0SDavid Ahern /*
31b69c6d0SDavid Ahern * net/l3mdev/l3mdev.c - L3 master device implementation
41b69c6d0SDavid Ahern * Copyright (c) 2015 Cumulus Networks
51b69c6d0SDavid Ahern * Copyright (c) 2015 David Ahern <[email protected]>
61b69c6d0SDavid Ahern */
71b69c6d0SDavid Ahern
81b69c6d0SDavid Ahern #include <linux/netdevice.h>
996c63fa7SDavid Ahern #include <net/fib_rules.h>
101b69c6d0SDavid Ahern #include <net/l3mdev.h>
111b69c6d0SDavid Ahern
1249042c22SAndrea Mayer static DEFINE_SPINLOCK(l3mdev_lock);
1349042c22SAndrea Mayer
1449042c22SAndrea Mayer struct l3mdev_handler {
1549042c22SAndrea Mayer lookup_by_table_id_t dev_lookup;
1649042c22SAndrea Mayer };
1749042c22SAndrea Mayer
1849042c22SAndrea Mayer static struct l3mdev_handler l3mdev_handlers[L3MDEV_TYPE_MAX + 1];
1949042c22SAndrea Mayer
l3mdev_check_type(enum l3mdev_type l3type)2049042c22SAndrea Mayer static int l3mdev_check_type(enum l3mdev_type l3type)
2149042c22SAndrea Mayer {
2249042c22SAndrea Mayer if (l3type <= L3MDEV_TYPE_UNSPEC || l3type > L3MDEV_TYPE_MAX)
2349042c22SAndrea Mayer return -EINVAL;
2449042c22SAndrea Mayer
2549042c22SAndrea Mayer return 0;
2649042c22SAndrea Mayer }
2749042c22SAndrea Mayer
l3mdev_table_lookup_register(enum l3mdev_type l3type,lookup_by_table_id_t fn)2849042c22SAndrea Mayer int l3mdev_table_lookup_register(enum l3mdev_type l3type,
2949042c22SAndrea Mayer lookup_by_table_id_t fn)
3049042c22SAndrea Mayer {
3149042c22SAndrea Mayer struct l3mdev_handler *hdlr;
3249042c22SAndrea Mayer int res;
3349042c22SAndrea Mayer
3449042c22SAndrea Mayer res = l3mdev_check_type(l3type);
3549042c22SAndrea Mayer if (res)
3649042c22SAndrea Mayer return res;
3749042c22SAndrea Mayer
3849042c22SAndrea Mayer hdlr = &l3mdev_handlers[l3type];
3949042c22SAndrea Mayer
4049042c22SAndrea Mayer spin_lock(&l3mdev_lock);
4149042c22SAndrea Mayer
4249042c22SAndrea Mayer if (hdlr->dev_lookup) {
4349042c22SAndrea Mayer res = -EBUSY;
4449042c22SAndrea Mayer goto unlock;
4549042c22SAndrea Mayer }
4649042c22SAndrea Mayer
4749042c22SAndrea Mayer hdlr->dev_lookup = fn;
4849042c22SAndrea Mayer res = 0;
4949042c22SAndrea Mayer
5049042c22SAndrea Mayer unlock:
5149042c22SAndrea Mayer spin_unlock(&l3mdev_lock);
5249042c22SAndrea Mayer
5349042c22SAndrea Mayer return res;
5449042c22SAndrea Mayer }
5549042c22SAndrea Mayer EXPORT_SYMBOL_GPL(l3mdev_table_lookup_register);
5649042c22SAndrea Mayer
l3mdev_table_lookup_unregister(enum l3mdev_type l3type,lookup_by_table_id_t fn)5749042c22SAndrea Mayer void l3mdev_table_lookup_unregister(enum l3mdev_type l3type,
5849042c22SAndrea Mayer lookup_by_table_id_t fn)
5949042c22SAndrea Mayer {
6049042c22SAndrea Mayer struct l3mdev_handler *hdlr;
6149042c22SAndrea Mayer
6249042c22SAndrea Mayer if (l3mdev_check_type(l3type))
6349042c22SAndrea Mayer return;
6449042c22SAndrea Mayer
6549042c22SAndrea Mayer hdlr = &l3mdev_handlers[l3type];
6649042c22SAndrea Mayer
6749042c22SAndrea Mayer spin_lock(&l3mdev_lock);
6849042c22SAndrea Mayer
6949042c22SAndrea Mayer if (hdlr->dev_lookup == fn)
7049042c22SAndrea Mayer hdlr->dev_lookup = NULL;
7149042c22SAndrea Mayer
7249042c22SAndrea Mayer spin_unlock(&l3mdev_lock);
7349042c22SAndrea Mayer }
7449042c22SAndrea Mayer EXPORT_SYMBOL_GPL(l3mdev_table_lookup_unregister);
7549042c22SAndrea Mayer
l3mdev_ifindex_lookup_by_table_id(enum l3mdev_type l3type,struct net * net,u32 table_id)7649042c22SAndrea Mayer int l3mdev_ifindex_lookup_by_table_id(enum l3mdev_type l3type,
7749042c22SAndrea Mayer struct net *net, u32 table_id)
7849042c22SAndrea Mayer {
7949042c22SAndrea Mayer lookup_by_table_id_t lookup;
8049042c22SAndrea Mayer struct l3mdev_handler *hdlr;
8149042c22SAndrea Mayer int ifindex = -EINVAL;
8249042c22SAndrea Mayer int res;
8349042c22SAndrea Mayer
8449042c22SAndrea Mayer res = l3mdev_check_type(l3type);
8549042c22SAndrea Mayer if (res)
8649042c22SAndrea Mayer return res;
8749042c22SAndrea Mayer
8849042c22SAndrea Mayer hdlr = &l3mdev_handlers[l3type];
8949042c22SAndrea Mayer
9049042c22SAndrea Mayer spin_lock(&l3mdev_lock);
9149042c22SAndrea Mayer
9249042c22SAndrea Mayer lookup = hdlr->dev_lookup;
9349042c22SAndrea Mayer if (!lookup)
9449042c22SAndrea Mayer goto unlock;
9549042c22SAndrea Mayer
9649042c22SAndrea Mayer ifindex = lookup(net, table_id);
9749042c22SAndrea Mayer
9849042c22SAndrea Mayer unlock:
9949042c22SAndrea Mayer spin_unlock(&l3mdev_lock);
10049042c22SAndrea Mayer
10149042c22SAndrea Mayer return ifindex;
10249042c22SAndrea Mayer }
10349042c22SAndrea Mayer EXPORT_SYMBOL_GPL(l3mdev_ifindex_lookup_by_table_id);
10449042c22SAndrea Mayer
1051b69c6d0SDavid Ahern /**
10637569287SXiongfeng Wang * l3mdev_master_ifindex_rcu - get index of L3 master device
1071b69c6d0SDavid Ahern * @dev: targeted interface
1081b69c6d0SDavid Ahern */
1091b69c6d0SDavid Ahern
l3mdev_master_ifindex_rcu(const struct net_device * dev)1103f2fb9a8SDavid Ahern int l3mdev_master_ifindex_rcu(const struct net_device *dev)
1111b69c6d0SDavid Ahern {
1121b69c6d0SDavid Ahern int ifindex = 0;
1131b69c6d0SDavid Ahern
1141b69c6d0SDavid Ahern if (!dev)
1151b69c6d0SDavid Ahern return 0;
1161b69c6d0SDavid Ahern
1171b69c6d0SDavid Ahern if (netif_is_l3_master(dev)) {
1181b69c6d0SDavid Ahern ifindex = dev->ifindex;
119fee6d4c7SDavid Ahern } else if (netif_is_l3_slave(dev)) {
1201b69c6d0SDavid Ahern struct net_device *master;
1213f2fb9a8SDavid Ahern struct net_device *_dev = (struct net_device *)dev;
1221b69c6d0SDavid Ahern
1233f2fb9a8SDavid Ahern /* netdev_master_upper_dev_get_rcu calls
1243f2fb9a8SDavid Ahern * list_first_or_null_rcu to walk the upper dev list.
1253f2fb9a8SDavid Ahern * list_first_or_null_rcu does not handle a const arg. We aren't
1263f2fb9a8SDavid Ahern * making changes, just want the master device from that list so
1273f2fb9a8SDavid Ahern * typecast to remove the const
1283f2fb9a8SDavid Ahern */
1293f2fb9a8SDavid Ahern master = netdev_master_upper_dev_get_rcu(_dev);
130fee6d4c7SDavid Ahern if (master)
1311b69c6d0SDavid Ahern ifindex = master->ifindex;
1321b69c6d0SDavid Ahern }
1331b69c6d0SDavid Ahern
1341b69c6d0SDavid Ahern return ifindex;
1351b69c6d0SDavid Ahern }
1361b69c6d0SDavid Ahern EXPORT_SYMBOL_GPL(l3mdev_master_ifindex_rcu);
1371b69c6d0SDavid Ahern
1381b69c6d0SDavid Ahern /**
13937569287SXiongfeng Wang * l3mdev_master_upper_ifindex_by_index_rcu - get index of upper l3 master
1406a6d6681SAlexis Bauvin * device
1416a6d6681SAlexis Bauvin * @net: network namespace for device index lookup
1426a6d6681SAlexis Bauvin * @ifindex: targeted interface
1436a6d6681SAlexis Bauvin */
l3mdev_master_upper_ifindex_by_index_rcu(struct net * net,int ifindex)1446a6d6681SAlexis Bauvin int l3mdev_master_upper_ifindex_by_index_rcu(struct net *net, int ifindex)
1456a6d6681SAlexis Bauvin {
1466a6d6681SAlexis Bauvin struct net_device *dev;
1476a6d6681SAlexis Bauvin
1486a6d6681SAlexis Bauvin dev = dev_get_by_index_rcu(net, ifindex);
1496a6d6681SAlexis Bauvin while (dev && !netif_is_l3_master(dev))
15083daab06SDavid Ahern dev = netdev_master_upper_dev_get_rcu(dev);
1516a6d6681SAlexis Bauvin
1526a6d6681SAlexis Bauvin return dev ? dev->ifindex : 0;
1536a6d6681SAlexis Bauvin }
1546a6d6681SAlexis Bauvin EXPORT_SYMBOL_GPL(l3mdev_master_upper_ifindex_by_index_rcu);
1556a6d6681SAlexis Bauvin
1566a6d6681SAlexis Bauvin /**
157645f0897SMiaohe Lin * l3mdev_fib_table_rcu - get FIB table id associated with an L3
1581b69c6d0SDavid Ahern * master interface
1591b69c6d0SDavid Ahern * @dev: targeted interface
1601b69c6d0SDavid Ahern */
1611b69c6d0SDavid Ahern
l3mdev_fib_table_rcu(const struct net_device * dev)1621b69c6d0SDavid Ahern u32 l3mdev_fib_table_rcu(const struct net_device *dev)
1631b69c6d0SDavid Ahern {
1641b69c6d0SDavid Ahern u32 tb_id = 0;
1651b69c6d0SDavid Ahern
1661b69c6d0SDavid Ahern if (!dev)
1671b69c6d0SDavid Ahern return 0;
1681b69c6d0SDavid Ahern
1691b69c6d0SDavid Ahern if (netif_is_l3_master(dev)) {
1701b69c6d0SDavid Ahern if (dev->l3mdev_ops->l3mdev_fib_table)
1711b69c6d0SDavid Ahern tb_id = dev->l3mdev_ops->l3mdev_fib_table(dev);
172fee6d4c7SDavid Ahern } else if (netif_is_l3_slave(dev)) {
1731b69c6d0SDavid Ahern /* Users of netdev_master_upper_dev_get_rcu need non-const,
1741b69c6d0SDavid Ahern * but current inet_*type functions take a const
1751b69c6d0SDavid Ahern */
1761b69c6d0SDavid Ahern struct net_device *_dev = (struct net_device *) dev;
1771b69c6d0SDavid Ahern const struct net_device *master;
1781b69c6d0SDavid Ahern
1791b69c6d0SDavid Ahern master = netdev_master_upper_dev_get_rcu(_dev);
180fee6d4c7SDavid Ahern if (master &&
1811b69c6d0SDavid Ahern master->l3mdev_ops->l3mdev_fib_table)
1821b69c6d0SDavid Ahern tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
1831b69c6d0SDavid Ahern }
1841b69c6d0SDavid Ahern
1851b69c6d0SDavid Ahern return tb_id;
1861b69c6d0SDavid Ahern }
1871b69c6d0SDavid Ahern EXPORT_SYMBOL_GPL(l3mdev_fib_table_rcu);
1881b69c6d0SDavid Ahern
l3mdev_fib_table_by_index(struct net * net,int ifindex)1891b69c6d0SDavid Ahern u32 l3mdev_fib_table_by_index(struct net *net, int ifindex)
1901b69c6d0SDavid Ahern {
1911b69c6d0SDavid Ahern struct net_device *dev;
1921b69c6d0SDavid Ahern u32 tb_id = 0;
1931b69c6d0SDavid Ahern
1941b69c6d0SDavid Ahern if (!ifindex)
1951b69c6d0SDavid Ahern return 0;
1961b69c6d0SDavid Ahern
1971b69c6d0SDavid Ahern rcu_read_lock();
1981b69c6d0SDavid Ahern
1991b69c6d0SDavid Ahern dev = dev_get_by_index_rcu(net, ifindex);
2001b69c6d0SDavid Ahern if (dev)
2011b69c6d0SDavid Ahern tb_id = l3mdev_fib_table_rcu(dev);
2021b69c6d0SDavid Ahern
2031b69c6d0SDavid Ahern rcu_read_unlock();
2041b69c6d0SDavid Ahern
2051b69c6d0SDavid Ahern return tb_id;
2061b69c6d0SDavid Ahern }
2071b69c6d0SDavid Ahern EXPORT_SYMBOL_GPL(l3mdev_fib_table_by_index);
2084a65896fSDavid Ahern
2094a65896fSDavid Ahern /**
2104c1feac5SDavid Ahern * l3mdev_link_scope_lookup - IPv6 route lookup based on flow for link
2114c1feac5SDavid Ahern * local and multicast addresses
2124a65896fSDavid Ahern * @net: network namespace for device index lookup
2134a65896fSDavid Ahern * @fl6: IPv6 flow struct for lookup
2147d9e5f42SWei Wang * This function does not hold refcnt on the returned dst.
2157d9e5f42SWei Wang * Caller must hold rcu_read_lock().
2164a65896fSDavid Ahern */
2174a65896fSDavid Ahern
l3mdev_link_scope_lookup(struct net * net,struct flowi6 * fl6)2184c1feac5SDavid Ahern struct dst_entry *l3mdev_link_scope_lookup(struct net *net,
219cd2a9e62SDavid Ahern struct flowi6 *fl6)
2204a65896fSDavid Ahern {
2214a65896fSDavid Ahern struct dst_entry *dst = NULL;
2224a65896fSDavid Ahern struct net_device *dev;
2234a65896fSDavid Ahern
2247d9e5f42SWei Wang WARN_ON_ONCE(!rcu_read_lock_held());
2251ff23beeSDavid Ahern if (fl6->flowi6_oif) {
2261ff23beeSDavid Ahern dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
2271ff23beeSDavid Ahern if (dev && netif_is_l3_slave(dev))
2281ff23beeSDavid Ahern dev = netdev_master_upper_dev_get_rcu(dev);
2291ff23beeSDavid Ahern
2301ff23beeSDavid Ahern if (dev && netif_is_l3_master(dev) &&
2314c1feac5SDavid Ahern dev->l3mdev_ops->l3mdev_link_scope_lookup)
2324c1feac5SDavid Ahern dst = dev->l3mdev_ops->l3mdev_link_scope_lookup(dev, fl6);
2334a65896fSDavid Ahern }
2344a65896fSDavid Ahern
2354a65896fSDavid Ahern return dst;
2364a65896fSDavid Ahern }
2374c1feac5SDavid Ahern EXPORT_SYMBOL_GPL(l3mdev_link_scope_lookup);
2384a65896fSDavid Ahern
23996c63fa7SDavid Ahern /**
24096c63fa7SDavid Ahern * l3mdev_fib_rule_match - Determine if flowi references an
24196c63fa7SDavid Ahern * L3 master device
24296c63fa7SDavid Ahern * @net: network namespace for device index lookup
24396c63fa7SDavid Ahern * @fl: flow struct
2449d637f81SAndrew Lunn * @arg: store the table the rule matched with here
24596c63fa7SDavid Ahern */
24696c63fa7SDavid Ahern
l3mdev_fib_rule_match(struct net * net,struct flowi * fl,struct fib_lookup_arg * arg)24796c63fa7SDavid Ahern int l3mdev_fib_rule_match(struct net *net, struct flowi *fl,
24896c63fa7SDavid Ahern struct fib_lookup_arg *arg)
24996c63fa7SDavid Ahern {
25096c63fa7SDavid Ahern struct net_device *dev;
25196c63fa7SDavid Ahern int rc = 0;
25296c63fa7SDavid Ahern
25340867d74SDavid Ahern /* update flow ensures flowi_l3mdev is set when relevant */
25440867d74SDavid Ahern if (!fl->flowi_l3mdev)
25540867d74SDavid Ahern return 0;
25640867d74SDavid Ahern
25796c63fa7SDavid Ahern rcu_read_lock();
25896c63fa7SDavid Ahern
25940867d74SDavid Ahern dev = dev_get_by_index_rcu(net, fl->flowi_l3mdev);
26096c63fa7SDavid Ahern if (dev && netif_is_l3_master(dev) &&
26196c63fa7SDavid Ahern dev->l3mdev_ops->l3mdev_fib_table) {
26296c63fa7SDavid Ahern arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
26396c63fa7SDavid Ahern rc = 1;
26496c63fa7SDavid Ahern }
26596c63fa7SDavid Ahern
26696c63fa7SDavid Ahern rcu_read_unlock();
26796c63fa7SDavid Ahern
26896c63fa7SDavid Ahern return rc;
26996c63fa7SDavid Ahern }
2709ee0034bSDavid Ahern
l3mdev_update_flow(struct net * net,struct flowi * fl)2719ee0034bSDavid Ahern void l3mdev_update_flow(struct net *net, struct flowi *fl)
2729ee0034bSDavid Ahern {
2739ee0034bSDavid Ahern struct net_device *dev;
2749ee0034bSDavid Ahern
2759ee0034bSDavid Ahern rcu_read_lock();
2769ee0034bSDavid Ahern
2779ee0034bSDavid Ahern if (fl->flowi_oif) {
2789ee0034bSDavid Ahern dev = dev_get_by_index_rcu(net, fl->flowi_oif);
2799ee0034bSDavid Ahern if (dev) {
280*2d300ce0SIdo Schimmel if (!fl->flowi_l3mdev) {
28140867d74SDavid Ahern fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
282*2d300ce0SIdo Schimmel fl->flowi_flags |= FLOWI_FLAG_L3MDEV_OIF;
283*2d300ce0SIdo Schimmel }
28440867d74SDavid Ahern
28540867d74SDavid Ahern /* oif set to L3mdev directs lookup to its table;
28640867d74SDavid Ahern * reset to avoid oif match in fib_lookup
28740867d74SDavid Ahern */
28840867d74SDavid Ahern if (netif_is_l3_master(dev))
28940867d74SDavid Ahern fl->flowi_oif = 0;
2909ee0034bSDavid Ahern goto out;
2919ee0034bSDavid Ahern }
2929ee0034bSDavid Ahern }
2939ee0034bSDavid Ahern
29440867d74SDavid Ahern if (fl->flowi_iif > LOOPBACK_IFINDEX && !fl->flowi_l3mdev) {
2959ee0034bSDavid Ahern dev = dev_get_by_index_rcu(net, fl->flowi_iif);
29640867d74SDavid Ahern if (dev)
29740867d74SDavid Ahern fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
2989ee0034bSDavid Ahern }
2999ee0034bSDavid Ahern
3009ee0034bSDavid Ahern out:
3019ee0034bSDavid Ahern rcu_read_unlock();
3029ee0034bSDavid Ahern }
3039ee0034bSDavid Ahern EXPORT_SYMBOL_GPL(l3mdev_update_flow);
304