Merge tag 'scmi-fix-5.5-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep...
[linux-2.6-microblaze.git] / net / l3mdev / l3mdev.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * net/l3mdev/l3mdev.c - L3 master device implementation
4  * Copyright (c) 2015 Cumulus Networks
5  * Copyright (c) 2015 David Ahern <dsa@cumulusnetworks.com>
6  */
7
8 #include <linux/netdevice.h>
9 #include <net/fib_rules.h>
10 #include <net/l3mdev.h>
11
12 /**
13  *      l3mdev_master_ifindex - get index of L3 master device
14  *      @dev: targeted interface
15  */
16
17 int l3mdev_master_ifindex_rcu(const struct net_device *dev)
18 {
19         int ifindex = 0;
20
21         if (!dev)
22                 return 0;
23
24         if (netif_is_l3_master(dev)) {
25                 ifindex = dev->ifindex;
26         } else if (netif_is_l3_slave(dev)) {
27                 struct net_device *master;
28                 struct net_device *_dev = (struct net_device *)dev;
29
30                 /* netdev_master_upper_dev_get_rcu calls
31                  * list_first_or_null_rcu to walk the upper dev list.
32                  * list_first_or_null_rcu does not handle a const arg. We aren't
33                  * making changes, just want the master device from that list so
34                  * typecast to remove the const
35                  */
36                 master = netdev_master_upper_dev_get_rcu(_dev);
37                 if (master)
38                         ifindex = master->ifindex;
39         }
40
41         return ifindex;
42 }
43 EXPORT_SYMBOL_GPL(l3mdev_master_ifindex_rcu);
44
45 /**
46  *      l3mdev_master_upper_ifindex_by_index - get index of upper l3 master
47  *                                             device
48  *      @net: network namespace for device index lookup
49  *      @ifindex: targeted interface
50  */
51 int l3mdev_master_upper_ifindex_by_index_rcu(struct net *net, int ifindex)
52 {
53         struct net_device *dev;
54
55         dev = dev_get_by_index_rcu(net, ifindex);
56         while (dev && !netif_is_l3_master(dev))
57                 dev = netdev_master_upper_dev_get(dev);
58
59         return dev ? dev->ifindex : 0;
60 }
61 EXPORT_SYMBOL_GPL(l3mdev_master_upper_ifindex_by_index_rcu);
62
63 /**
64  *      l3mdev_fib_table - get FIB table id associated with an L3
65  *                             master interface
66  *      @dev: targeted interface
67  */
68
69 u32 l3mdev_fib_table_rcu(const struct net_device *dev)
70 {
71         u32 tb_id = 0;
72
73         if (!dev)
74                 return 0;
75
76         if (netif_is_l3_master(dev)) {
77                 if (dev->l3mdev_ops->l3mdev_fib_table)
78                         tb_id = dev->l3mdev_ops->l3mdev_fib_table(dev);
79         } else if (netif_is_l3_slave(dev)) {
80                 /* Users of netdev_master_upper_dev_get_rcu need non-const,
81                  * but current inet_*type functions take a const
82                  */
83                 struct net_device *_dev = (struct net_device *) dev;
84                 const struct net_device *master;
85
86                 master = netdev_master_upper_dev_get_rcu(_dev);
87                 if (master &&
88                     master->l3mdev_ops->l3mdev_fib_table)
89                         tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
90         }
91
92         return tb_id;
93 }
94 EXPORT_SYMBOL_GPL(l3mdev_fib_table_rcu);
95
96 u32 l3mdev_fib_table_by_index(struct net *net, int ifindex)
97 {
98         struct net_device *dev;
99         u32 tb_id = 0;
100
101         if (!ifindex)
102                 return 0;
103
104         rcu_read_lock();
105
106         dev = dev_get_by_index_rcu(net, ifindex);
107         if (dev)
108                 tb_id = l3mdev_fib_table_rcu(dev);
109
110         rcu_read_unlock();
111
112         return tb_id;
113 }
114 EXPORT_SYMBOL_GPL(l3mdev_fib_table_by_index);
115
116 /**
117  *      l3mdev_link_scope_lookup - IPv6 route lookup based on flow for link
118  *                           local and multicast addresses
119  *      @net: network namespace for device index lookup
120  *      @fl6: IPv6 flow struct for lookup
121  *      This function does not hold refcnt on the returned dst.
122  *      Caller must hold rcu_read_lock().
123  */
124
125 struct dst_entry *l3mdev_link_scope_lookup(struct net *net,
126                                            struct flowi6 *fl6)
127 {
128         struct dst_entry *dst = NULL;
129         struct net_device *dev;
130
131         WARN_ON_ONCE(!rcu_read_lock_held());
132         if (fl6->flowi6_oif) {
133                 dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
134                 if (dev && netif_is_l3_slave(dev))
135                         dev = netdev_master_upper_dev_get_rcu(dev);
136
137                 if (dev && netif_is_l3_master(dev) &&
138                     dev->l3mdev_ops->l3mdev_link_scope_lookup)
139                         dst = dev->l3mdev_ops->l3mdev_link_scope_lookup(dev, fl6);
140         }
141
142         return dst;
143 }
144 EXPORT_SYMBOL_GPL(l3mdev_link_scope_lookup);
145
146 /**
147  *      l3mdev_fib_rule_match - Determine if flowi references an
148  *                              L3 master device
149  *      @net: network namespace for device index lookup
150  *      @fl:  flow struct
151  */
152
153 int l3mdev_fib_rule_match(struct net *net, struct flowi *fl,
154                           struct fib_lookup_arg *arg)
155 {
156         struct net_device *dev;
157         int rc = 0;
158
159         rcu_read_lock();
160
161         dev = dev_get_by_index_rcu(net, fl->flowi_oif);
162         if (dev && netif_is_l3_master(dev) &&
163             dev->l3mdev_ops->l3mdev_fib_table) {
164                 arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
165                 rc = 1;
166                 goto out;
167         }
168
169         dev = dev_get_by_index_rcu(net, fl->flowi_iif);
170         if (dev && netif_is_l3_master(dev) &&
171             dev->l3mdev_ops->l3mdev_fib_table) {
172                 arg->table = dev->l3mdev_ops->l3mdev_fib_table(dev);
173                 rc = 1;
174                 goto out;
175         }
176
177 out:
178         rcu_read_unlock();
179
180         return rc;
181 }
182
183 void l3mdev_update_flow(struct net *net, struct flowi *fl)
184 {
185         struct net_device *dev;
186         int ifindex;
187
188         rcu_read_lock();
189
190         if (fl->flowi_oif) {
191                 dev = dev_get_by_index_rcu(net, fl->flowi_oif);
192                 if (dev) {
193                         ifindex = l3mdev_master_ifindex_rcu(dev);
194                         if (ifindex) {
195                                 fl->flowi_oif = ifindex;
196                                 fl->flowi_flags |= FLOWI_FLAG_SKIP_NH_OIF;
197                                 goto out;
198                         }
199                 }
200         }
201
202         if (fl->flowi_iif) {
203                 dev = dev_get_by_index_rcu(net, fl->flowi_iif);
204                 if (dev) {
205                         ifindex = l3mdev_master_ifindex_rcu(dev);
206                         if (ifindex) {
207                                 fl->flowi_iif = ifindex;
208                                 fl->flowi_flags |= FLOWI_FLAG_SKIP_NH_OIF;
209                         }
210                 }
211         }
212
213 out:
214         rcu_read_unlock();
215 }
216 EXPORT_SYMBOL_GPL(l3mdev_update_flow);