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