Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * net/l3mdev/l3mdev.c - L3 master device implementation
0004  * Copyright (c) 2015 Cumulus Networks
0005  * Copyright (c) 2015 David Ahern <dsa@cumulusnetworks.com>
0006  */
0007 
0008 #include <linux/netdevice.h>
0009 #include <net/fib_rules.h>
0010 #include <net/l3mdev.h>
0011 
0012 static DEFINE_SPINLOCK(l3mdev_lock);
0013 
0014 struct l3mdev_handler {
0015     lookup_by_table_id_t dev_lookup;
0016 };
0017 
0018 static struct l3mdev_handler l3mdev_handlers[L3MDEV_TYPE_MAX + 1];
0019 
0020 static int l3mdev_check_type(enum l3mdev_type l3type)
0021 {
0022     if (l3type <= L3MDEV_TYPE_UNSPEC || l3type > L3MDEV_TYPE_MAX)
0023         return -EINVAL;
0024 
0025     return 0;
0026 }
0027 
0028 int l3mdev_table_lookup_register(enum l3mdev_type l3type,
0029                  lookup_by_table_id_t fn)
0030 {
0031     struct l3mdev_handler *hdlr;
0032     int res;
0033 
0034     res = l3mdev_check_type(l3type);
0035     if (res)
0036         return res;
0037 
0038     hdlr = &l3mdev_handlers[l3type];
0039 
0040     spin_lock(&l3mdev_lock);
0041 
0042     if (hdlr->dev_lookup) {
0043         res = -EBUSY;
0044         goto unlock;
0045     }
0046 
0047     hdlr->dev_lookup = fn;
0048     res = 0;
0049 
0050 unlock:
0051     spin_unlock(&l3mdev_lock);
0052 
0053     return res;
0054 }
0055 EXPORT_SYMBOL_GPL(l3mdev_table_lookup_register);
0056 
0057 void l3mdev_table_lookup_unregister(enum l3mdev_type l3type,
0058                     lookup_by_table_id_t fn)
0059 {
0060     struct l3mdev_handler *hdlr;
0061 
0062     if (l3mdev_check_type(l3type))
0063         return;
0064 
0065     hdlr = &l3mdev_handlers[l3type];
0066 
0067     spin_lock(&l3mdev_lock);
0068 
0069     if (hdlr->dev_lookup == fn)
0070         hdlr->dev_lookup = NULL;
0071 
0072     spin_unlock(&l3mdev_lock);
0073 }
0074 EXPORT_SYMBOL_GPL(l3mdev_table_lookup_unregister);
0075 
0076 int l3mdev_ifindex_lookup_by_table_id(enum l3mdev_type l3type,
0077                       struct net *net, u32 table_id)
0078 {
0079     lookup_by_table_id_t lookup;
0080     struct l3mdev_handler *hdlr;
0081     int ifindex = -EINVAL;
0082     int res;
0083 
0084     res = l3mdev_check_type(l3type);
0085     if (res)
0086         return res;
0087 
0088     hdlr = &l3mdev_handlers[l3type];
0089 
0090     spin_lock(&l3mdev_lock);
0091 
0092     lookup = hdlr->dev_lookup;
0093     if (!lookup)
0094         goto unlock;
0095 
0096     ifindex = lookup(net, table_id);
0097 
0098 unlock:
0099     spin_unlock(&l3mdev_lock);
0100 
0101     return ifindex;
0102 }
0103 EXPORT_SYMBOL_GPL(l3mdev_ifindex_lookup_by_table_id);
0104 
0105 /**
0106  *  l3mdev_master_ifindex_rcu - get index of L3 master device
0107  *  @dev: targeted interface
0108  */
0109 
0110 int l3mdev_master_ifindex_rcu(const struct net_device *dev)
0111 {
0112     int ifindex = 0;
0113 
0114     if (!dev)
0115         return 0;
0116 
0117     if (netif_is_l3_master(dev)) {
0118         ifindex = dev->ifindex;
0119     } else if (netif_is_l3_slave(dev)) {
0120         struct net_device *master;
0121         struct net_device *_dev = (struct net_device *)dev;
0122 
0123         /* netdev_master_upper_dev_get_rcu calls
0124          * list_first_or_null_rcu to walk the upper dev list.
0125          * list_first_or_null_rcu does not handle a const arg. We aren't
0126          * making changes, just want the master device from that list so
0127          * typecast to remove the const
0128          */
0129         master = netdev_master_upper_dev_get_rcu(_dev);
0130         if (master)
0131             ifindex = master->ifindex;
0132     }
0133 
0134     return ifindex;
0135 }
0136 EXPORT_SYMBOL_GPL(l3mdev_master_ifindex_rcu);
0137 
0138 /**
0139  *  l3mdev_master_upper_ifindex_by_index_rcu - get index of upper l3 master
0140  *                         device
0141  *  @net: network namespace for device index lookup
0142  *  @ifindex: targeted interface
0143  */
0144 int l3mdev_master_upper_ifindex_by_index_rcu(struct net *net, int ifindex)
0145 {
0146     struct net_device *dev;
0147 
0148     dev = dev_get_by_index_rcu(net, ifindex);
0149     while (dev && !netif_is_l3_master(dev))
0150         dev = netdev_master_upper_dev_get_rcu(dev);
0151 
0152     return dev ? dev->ifindex : 0;
0153 }
0154 EXPORT_SYMBOL_GPL(l3mdev_master_upper_ifindex_by_index_rcu);
0155 
0156 /**
0157  *  l3mdev_fib_table_rcu - get FIB table id associated with an L3
0158  *                             master interface
0159  *  @dev: targeted interface
0160  */
0161 
0162 u32 l3mdev_fib_table_rcu(const struct net_device *dev)
0163 {
0164     u32 tb_id = 0;
0165 
0166     if (!dev)
0167         return 0;
0168 
0169     if (netif_is_l3_master(dev)) {
0170         if (dev->l3mdev_ops->l3mdev_fib_table)
0171             tb_id = dev->l3mdev_ops->l3mdev_fib_table(dev);
0172     } else if (netif_is_l3_slave(dev)) {
0173         /* Users of netdev_master_upper_dev_get_rcu need non-const,
0174          * but current inet_*type functions take a const
0175          */
0176         struct net_device *_dev = (struct net_device *) dev;
0177         const struct net_device *master;
0178 
0179         master = netdev_master_upper_dev_get_rcu(_dev);
0180         if (master &&
0181             master->l3mdev_ops->l3mdev_fib_table)
0182             tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
0183     }
0184 
0185     return tb_id;
0186 }
0187 EXPORT_SYMBOL_GPL(l3mdev_fib_table_rcu);
0188 
0189 u32 l3mdev_fib_table_by_index(struct net *net, int ifindex)
0190 {
0191     struct net_device *dev;
0192     u32 tb_id = 0;
0193 
0194     if (!ifindex)
0195         return 0;
0196 
0197     rcu_read_lock();
0198 
0199     dev = dev_get_by_index_rcu(net, ifindex);
0200     if (dev)
0201         tb_id = l3mdev_fib_table_rcu(dev);
0202 
0203     rcu_read_unlock();
0204 
0205     return tb_id;
0206 }
0207 EXPORT_SYMBOL_GPL(l3mdev_fib_table_by_index);
0208 
0209 /**
0210  *  l3mdev_link_scope_lookup - IPv6 route lookup based on flow for link
0211  *               local and multicast addresses
0212  *  @net: network namespace for device index lookup
0213  *  @fl6: IPv6 flow struct for lookup
0214  *  This function does not hold refcnt on the returned dst.
0215  *  Caller must hold rcu_read_lock().
0216  */
0217 
0218 struct dst_entry *l3mdev_link_scope_lookup(struct net *net,
0219                        struct flowi6 *fl6)
0220 {
0221     struct dst_entry *dst = NULL;
0222     struct net_device *dev;
0223 
0224     WARN_ON_ONCE(!rcu_read_lock_held());
0225     if (fl6->flowi6_oif) {
0226         dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
0227         if (dev && netif_is_l3_slave(dev))
0228             dev = netdev_master_upper_dev_get_rcu(dev);
0229 
0230         if (dev && netif_is_l3_master(dev) &&
0231             dev->l3mdev_ops->l3mdev_link_scope_lookup)
0232             dst = dev->l3mdev_ops->l3mdev_link_scope_lookup(dev, fl6);
0233     }
0234 
0235     return dst;
0236 }
0237 EXPORT_SYMBOL_GPL(l3mdev_link_scope_lookup);
0238 
0239 /**
0240  *  l3mdev_fib_rule_match - Determine if flowi references an
0241  *              L3 master device
0242  *  @net: network namespace for device index lookup
0243  *  @fl:  flow struct
0244  *  @arg: store the table the rule matched with here
0245  */
0246 
0247 int l3mdev_fib_rule_match(struct net *net, struct flowi *fl,
0248               struct fib_lookup_arg *arg)
0249 {
0250     struct net_device *dev;
0251     int rc = 0;
0252 
0253     /* update flow ensures flowi_l3mdev is set when relevant */
0254     if (!fl->flowi_l3mdev)
0255         return 0;
0256 
0257     rcu_read_lock();
0258 
0259     dev = dev_get_by_index_rcu(net, fl->flowi_l3mdev);
0260     if (dev && netif_is_l3_master(dev) &&
0261         dev->l3mdev_ops->l3mdev_fib_table) {
0262         arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
0263         rc = 1;
0264     }
0265 
0266     rcu_read_unlock();
0267 
0268     return rc;
0269 }
0270 
0271 void l3mdev_update_flow(struct net *net, struct flowi *fl)
0272 {
0273     struct net_device *dev;
0274 
0275     rcu_read_lock();
0276 
0277     if (fl->flowi_oif) {
0278         dev = dev_get_by_index_rcu(net, fl->flowi_oif);
0279         if (dev) {
0280             if (!fl->flowi_l3mdev)
0281                 fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
0282 
0283             /* oif set to L3mdev directs lookup to its table;
0284              * reset to avoid oif match in fib_lookup
0285              */
0286             if (netif_is_l3_master(dev))
0287                 fl->flowi_oif = 0;
0288             goto out;
0289         }
0290     }
0291 
0292     if (fl->flowi_iif > LOOPBACK_IFINDEX && !fl->flowi_l3mdev) {
0293         dev = dev_get_by_index_rcu(net, fl->flowi_iif);
0294         if (dev)
0295             fl->flowi_l3mdev = l3mdev_master_ifindex_rcu(dev);
0296     }
0297 
0298 out:
0299     rcu_read_unlock();
0300 }
0301 EXPORT_SYMBOL_GPL(l3mdev_update_flow);