xref: /openbmc/linux/drivers/net/netdevsim/fib.c (revision 4eb7ae7a)
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/net_namespace.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 notifier_block fib_nb;
37 	struct nsim_per_fib_data ipv4;
38 	struct nsim_per_fib_data ipv6;
39 };
40 
41 u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
42 		     enum nsim_resource_id res_id, bool max)
43 {
44 	struct nsim_fib_entry *entry;
45 
46 	switch (res_id) {
47 	case NSIM_RESOURCE_IPV4_FIB:
48 		entry = &fib_data->ipv4.fib;
49 		break;
50 	case NSIM_RESOURCE_IPV4_FIB_RULES:
51 		entry = &fib_data->ipv4.rules;
52 		break;
53 	case NSIM_RESOURCE_IPV6_FIB:
54 		entry = &fib_data->ipv6.fib;
55 		break;
56 	case NSIM_RESOURCE_IPV6_FIB_RULES:
57 		entry = &fib_data->ipv6.rules;
58 		break;
59 	default:
60 		return 0;
61 	}
62 
63 	return max ? entry->max : entry->num;
64 }
65 
66 static void nsim_fib_set_max(struct nsim_fib_data *fib_data,
67 			     enum nsim_resource_id res_id, u64 val)
68 {
69 	struct nsim_fib_entry *entry;
70 
71 	switch (res_id) {
72 	case NSIM_RESOURCE_IPV4_FIB:
73 		entry = &fib_data->ipv4.fib;
74 		break;
75 	case NSIM_RESOURCE_IPV4_FIB_RULES:
76 		entry = &fib_data->ipv4.rules;
77 		break;
78 	case NSIM_RESOURCE_IPV6_FIB:
79 		entry = &fib_data->ipv6.fib;
80 		break;
81 	case NSIM_RESOURCE_IPV6_FIB_RULES:
82 		entry = &fib_data->ipv6.rules;
83 		break;
84 	default:
85 		WARN_ON(1);
86 		return;
87 	}
88 	entry->max = val;
89 }
90 
91 static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
92 				 struct netlink_ext_ack *extack)
93 {
94 	int err = 0;
95 
96 	if (add) {
97 		if (entry->num < entry->max) {
98 			entry->num++;
99 		} else {
100 			err = -ENOSPC;
101 			NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
102 		}
103 	} else {
104 		entry->num--;
105 	}
106 
107 	return err;
108 }
109 
110 static int nsim_fib_rule_event(struct nsim_fib_data *data,
111 			       struct fib_notifier_info *info, bool add)
112 {
113 	struct netlink_ext_ack *extack = info->extack;
114 	int err = 0;
115 
116 	switch (info->family) {
117 	case AF_INET:
118 		err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
119 		break;
120 	case AF_INET6:
121 		err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
122 		break;
123 	}
124 
125 	return err;
126 }
127 
128 static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
129 			    struct netlink_ext_ack *extack)
130 {
131 	int err = 0;
132 
133 	if (add) {
134 		if (entry->num < entry->max) {
135 			entry->num++;
136 		} else {
137 			err = -ENOSPC;
138 			NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
139 		}
140 	} else {
141 		entry->num--;
142 	}
143 
144 	return err;
145 }
146 
147 static int nsim_fib_event(struct nsim_fib_data *data,
148 			  struct fib_notifier_info *info, bool add)
149 {
150 	struct netlink_ext_ack *extack = info->extack;
151 	int err = 0;
152 
153 	switch (info->family) {
154 	case AF_INET:
155 		err = nsim_fib_account(&data->ipv4.fib, add, extack);
156 		break;
157 	case AF_INET6:
158 		err = nsim_fib_account(&data->ipv6.fib, add, extack);
159 		break;
160 	}
161 
162 	return err;
163 }
164 
165 static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
166 			     void *ptr)
167 {
168 	struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
169 						  fib_nb);
170 	struct fib_notifier_info *info = ptr;
171 	int err = 0;
172 
173 	switch (event) {
174 	case FIB_EVENT_RULE_ADD: /* fall through */
175 	case FIB_EVENT_RULE_DEL:
176 		err = nsim_fib_rule_event(data, info,
177 					  event == FIB_EVENT_RULE_ADD);
178 		break;
179 
180 	case FIB_EVENT_ENTRY_REPLACE:  /* fall through */
181 	case FIB_EVENT_ENTRY_ADD:  /* fall through */
182 	case FIB_EVENT_ENTRY_DEL:
183 		err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL);
184 		break;
185 	}
186 
187 	return notifier_from_errno(err);
188 }
189 
190 /* inconsistent dump, trying again */
191 static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
192 {
193 	struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
194 						  fib_nb);
195 
196 	data->ipv4.fib.num = 0ULL;
197 	data->ipv4.rules.num = 0ULL;
198 	data->ipv6.fib.num = 0ULL;
199 	data->ipv6.rules.num = 0ULL;
200 }
201 
202 static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
203 {
204 	struct nsim_fib_data *data = priv;
205 
206 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
207 }
208 
209 static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
210 {
211 	struct nsim_fib_data *data = priv;
212 
213 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
214 }
215 
216 static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
217 {
218 	struct nsim_fib_data *data = priv;
219 
220 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
221 }
222 
223 static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
224 {
225 	struct nsim_fib_data *data = priv;
226 
227 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
228 }
229 
230 static void nsim_fib_set_max_all(struct nsim_fib_data *data,
231 				 struct devlink *devlink)
232 {
233 	enum nsim_resource_id res_ids[] = {
234 		NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
235 		NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
236 	};
237 	int i;
238 
239 	for (i = 0; i < ARRAY_SIZE(res_ids); i++) {
240 		int err;
241 		u64 val;
242 
243 		err = devlink_resource_size_get(devlink, res_ids[i], &val);
244 		if (err)
245 			val = (u64) -1;
246 		nsim_fib_set_max(data, res_ids[i], val);
247 	}
248 }
249 
250 struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
251 				      struct netlink_ext_ack *extack)
252 {
253 	struct nsim_fib_data *data;
254 	int err;
255 
256 	data = kzalloc(sizeof(*data), GFP_KERNEL);
257 	if (!data)
258 		return ERR_PTR(-ENOMEM);
259 
260 	nsim_fib_set_max_all(data, devlink);
261 
262 	data->fib_nb.notifier_call = nsim_fib_event_nb;
263 	err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
264 				    nsim_fib_dump_inconsistent, extack);
265 	if (err) {
266 		pr_err("Failed to register fib notifier\n");
267 		goto err_out;
268 	}
269 
270 	devlink_resource_occ_get_register(devlink,
271 					  NSIM_RESOURCE_IPV4_FIB,
272 					  nsim_fib_ipv4_resource_occ_get,
273 					  data);
274 	devlink_resource_occ_get_register(devlink,
275 					  NSIM_RESOURCE_IPV4_FIB_RULES,
276 					  nsim_fib_ipv4_rules_res_occ_get,
277 					  data);
278 	devlink_resource_occ_get_register(devlink,
279 					  NSIM_RESOURCE_IPV6_FIB,
280 					  nsim_fib_ipv6_resource_occ_get,
281 					  data);
282 	devlink_resource_occ_get_register(devlink,
283 					  NSIM_RESOURCE_IPV6_FIB_RULES,
284 					  nsim_fib_ipv6_rules_res_occ_get,
285 					  data);
286 	return data;
287 
288 err_out:
289 	kfree(data);
290 	return ERR_PTR(err);
291 }
292 
293 void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data)
294 {
295 	devlink_resource_occ_get_unregister(devlink,
296 					    NSIM_RESOURCE_IPV6_FIB_RULES);
297 	devlink_resource_occ_get_unregister(devlink,
298 					    NSIM_RESOURCE_IPV6_FIB);
299 	devlink_resource_occ_get_unregister(devlink,
300 					    NSIM_RESOURCE_IPV4_FIB_RULES);
301 	devlink_resource_occ_get_unregister(devlink,
302 					    NSIM_RESOURCE_IPV4_FIB);
303 	unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
304 	kfree(data);
305 }
306