Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / net / netdevsim / fib.c
1 /*
2  * Copyright (c) 2018 Cumulus Networks. All rights reserved.
3  * Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
4  *
5  * This software is licensed under the GNU General License Version 2,
6  * June 1991 as shown in the file COPYING in the top-level directory of this
7  * source tree.
8  *
9  * THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
10  * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
11  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  * FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
13  * OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
14  * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
15  */
16
17 #include <net/fib_notifier.h>
18 #include <net/ip_fib.h>
19 #include <net/ip6_fib.h>
20 #include <net/fib_rules.h>
21 #include <net/netns/generic.h>
22
23 #include "netdevsim.h"
24
25 struct nsim_fib_entry {
26         u64 max;
27         u64 num;
28 };
29
30 struct nsim_per_fib_data {
31         struct nsim_fib_entry fib;
32         struct nsim_fib_entry rules;
33 };
34
35 struct nsim_fib_data {
36         struct nsim_per_fib_data ipv4;
37         struct nsim_per_fib_data ipv6;
38 };
39
40 static unsigned int nsim_fib_net_id;
41
42 u64 nsim_fib_get_val(struct net *net, enum nsim_resource_id res_id, bool max)
43 {
44         struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
45         struct nsim_fib_entry *entry;
46
47         switch (res_id) {
48         case NSIM_RESOURCE_IPV4_FIB:
49                 entry = &fib_data->ipv4.fib;
50                 break;
51         case NSIM_RESOURCE_IPV4_FIB_RULES:
52                 entry = &fib_data->ipv4.rules;
53                 break;
54         case NSIM_RESOURCE_IPV6_FIB:
55                 entry = &fib_data->ipv6.fib;
56                 break;
57         case NSIM_RESOURCE_IPV6_FIB_RULES:
58                 entry = &fib_data->ipv6.rules;
59                 break;
60         default:
61                 return 0;
62         }
63
64         return max ? entry->max : entry->num;
65 }
66
67 int nsim_fib_set_max(struct net *net, enum nsim_resource_id res_id, u64 val,
68                      struct netlink_ext_ack *extack)
69 {
70         struct nsim_fib_data *fib_data = net_generic(net, nsim_fib_net_id);
71         struct nsim_fib_entry *entry;
72         int err = 0;
73
74         switch (res_id) {
75         case NSIM_RESOURCE_IPV4_FIB:
76                 entry = &fib_data->ipv4.fib;
77                 break;
78         case NSIM_RESOURCE_IPV4_FIB_RULES:
79                 entry = &fib_data->ipv4.rules;
80                 break;
81         case NSIM_RESOURCE_IPV6_FIB:
82                 entry = &fib_data->ipv6.fib;
83                 break;
84         case NSIM_RESOURCE_IPV6_FIB_RULES:
85                 entry = &fib_data->ipv6.rules;
86                 break;
87         default:
88                 return 0;
89         }
90
91         /* not allowing a new max to be less than curren occupancy
92          * --> no means of evicting entries
93          */
94         if (val < entry->num) {
95                 NL_SET_ERR_MSG_MOD(extack, "New size is less than current occupancy");
96                 err = -EINVAL;
97         } else {
98                 entry->max = val;
99         }
100
101         return err;
102 }
103
104 static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
105                                  struct netlink_ext_ack *extack)
106 {
107         int err = 0;
108
109         if (add) {
110                 if (entry->num < entry->max) {
111                         entry->num++;
112                 } else {
113                         err = -ENOSPC;
114                         NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
115                 }
116         } else {
117                 entry->num--;
118         }
119
120         return err;
121 }
122
123 static int nsim_fib_rule_event(struct fib_notifier_info *info, bool add)
124 {
125         struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
126         struct netlink_ext_ack *extack = info->extack;
127         int err = 0;
128
129         switch (info->family) {
130         case AF_INET:
131                 err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
132                 break;
133         case AF_INET6:
134                 err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
135                 break;
136         }
137
138         return err;
139 }
140
141 static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
142                             struct netlink_ext_ack *extack)
143 {
144         int err = 0;
145
146         if (add) {
147                 if (entry->num < entry->max) {
148                         entry->num++;
149                 } else {
150                         err = -ENOSPC;
151                         NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
152                 }
153         } else {
154                 entry->num--;
155         }
156
157         return err;
158 }
159
160 static int nsim_fib_event(struct fib_notifier_info *info, bool add)
161 {
162         struct nsim_fib_data *data = net_generic(info->net, nsim_fib_net_id);
163         struct netlink_ext_ack *extack = info->extack;
164         int err = 0;
165
166         switch (info->family) {
167         case AF_INET:
168                 err = nsim_fib_account(&data->ipv4.fib, add, extack);
169                 break;
170         case AF_INET6:
171                 err = nsim_fib_account(&data->ipv6.fib, add, extack);
172                 break;
173         }
174
175         return err;
176 }
177
178 static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
179                              void *ptr)
180 {
181         struct fib_notifier_info *info = ptr;
182         int err = 0;
183
184         switch (event) {
185         case FIB_EVENT_RULE_ADD: /* fall through */
186         case FIB_EVENT_RULE_DEL:
187                 err = nsim_fib_rule_event(info, event == FIB_EVENT_RULE_ADD);
188                 break;
189
190         case FIB_EVENT_ENTRY_ADD:  /* fall through */
191         case FIB_EVENT_ENTRY_DEL:
192                 err = nsim_fib_event(info, event == FIB_EVENT_ENTRY_ADD);
193                 break;
194         }
195
196         return notifier_from_errno(err);
197 }
198
199 /* inconsistent dump, trying again */
200 static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
201 {
202         struct nsim_fib_data *data;
203         struct net *net;
204
205         rcu_read_lock();
206         for_each_net_rcu(net) {
207                 data = net_generic(net, nsim_fib_net_id);
208
209                 data->ipv4.fib.num = 0ULL;
210                 data->ipv4.rules.num = 0ULL;
211
212                 data->ipv6.fib.num = 0ULL;
213                 data->ipv6.rules.num = 0ULL;
214         }
215         rcu_read_unlock();
216 }
217
218 static struct notifier_block nsim_fib_nb = {
219         .notifier_call = nsim_fib_event_nb,
220 };
221
222 /* Initialize per network namespace state */
223 static int __net_init nsim_fib_netns_init(struct net *net)
224 {
225         struct nsim_fib_data *data = net_generic(net, nsim_fib_net_id);
226
227         data->ipv4.fib.max = (u64)-1;
228         data->ipv4.rules.max = (u64)-1;
229
230         data->ipv6.fib.max = (u64)-1;
231         data->ipv6.rules.max = (u64)-1;
232
233         return 0;
234 }
235
236 static struct pernet_operations nsim_fib_net_ops = {
237         .init = nsim_fib_netns_init,
238         .id   = &nsim_fib_net_id,
239         .size = sizeof(struct nsim_fib_data),
240 };
241
242 void nsim_fib_exit(void)
243 {
244         unregister_fib_notifier(&nsim_fib_nb);
245         unregister_pernet_subsys(&nsim_fib_net_ops);
246 }
247
248 int nsim_fib_init(void)
249 {
250         int err;
251
252         err = register_pernet_subsys(&nsim_fib_net_ops);
253         if (err < 0) {
254                 pr_err("Failed to register pernet subsystem\n");
255                 goto err_out;
256         }
257
258         err = register_fib_notifier(&nsim_fib_nb, nsim_fib_dump_inconsistent);
259         if (err < 0) {
260                 pr_err("Failed to register fib notifier\n");
261                 unregister_pernet_subsys(&nsim_fib_net_ops);
262                 goto err_out;
263         }
264
265 err_out:
266         return err;
267 }