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