xref: /linux/drivers/net/netdevsim/fib.c (revision 521fe8bb5874963d5f6fd58d5c5ad80fbc9c6b1c)
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_DEL:
182 		err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL);
183 		break;
184 	}
185 
186 	return notifier_from_errno(err);
187 }
188 
189 /* inconsistent dump, trying again */
190 static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
191 {
192 	struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
193 						  fib_nb);
194 
195 	data->ipv4.fib.num = 0ULL;
196 	data->ipv4.rules.num = 0ULL;
197 	data->ipv6.fib.num = 0ULL;
198 	data->ipv6.rules.num = 0ULL;
199 }
200 
201 static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
202 {
203 	struct nsim_fib_data *data = priv;
204 
205 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
206 }
207 
208 static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
209 {
210 	struct nsim_fib_data *data = priv;
211 
212 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
213 }
214 
215 static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
216 {
217 	struct nsim_fib_data *data = priv;
218 
219 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
220 }
221 
222 static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
223 {
224 	struct nsim_fib_data *data = priv;
225 
226 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
227 }
228 
229 static void nsim_fib_set_max_all(struct nsim_fib_data *data,
230 				 struct devlink *devlink)
231 {
232 	enum nsim_resource_id res_ids[] = {
233 		NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
234 		NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
235 	};
236 	int i;
237 
238 	for (i = 0; i < ARRAY_SIZE(res_ids); i++) {
239 		int err;
240 		u64 val;
241 
242 		err = devlink_resource_size_get(devlink, res_ids[i], &val);
243 		if (err)
244 			val = (u64) -1;
245 		nsim_fib_set_max(data, res_ids[i], val);
246 	}
247 }
248 
249 struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
250 				      struct netlink_ext_ack *extack)
251 {
252 	struct nsim_fib_data *data;
253 	int err;
254 
255 	data = kzalloc(sizeof(*data), GFP_KERNEL);
256 	if (!data)
257 		return ERR_PTR(-ENOMEM);
258 
259 	nsim_fib_set_max_all(data, devlink);
260 
261 	data->fib_nb.notifier_call = nsim_fib_event_nb;
262 	err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
263 				    nsim_fib_dump_inconsistent, extack);
264 	if (err) {
265 		pr_err("Failed to register fib notifier\n");
266 		goto err_out;
267 	}
268 
269 	devlink_resource_occ_get_register(devlink,
270 					  NSIM_RESOURCE_IPV4_FIB,
271 					  nsim_fib_ipv4_resource_occ_get,
272 					  data);
273 	devlink_resource_occ_get_register(devlink,
274 					  NSIM_RESOURCE_IPV4_FIB_RULES,
275 					  nsim_fib_ipv4_rules_res_occ_get,
276 					  data);
277 	devlink_resource_occ_get_register(devlink,
278 					  NSIM_RESOURCE_IPV6_FIB,
279 					  nsim_fib_ipv6_resource_occ_get,
280 					  data);
281 	devlink_resource_occ_get_register(devlink,
282 					  NSIM_RESOURCE_IPV6_FIB_RULES,
283 					  nsim_fib_ipv6_rules_res_occ_get,
284 					  data);
285 	return data;
286 
287 err_out:
288 	kfree(data);
289 	return ERR_PTR(err);
290 }
291 
292 void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data)
293 {
294 	devlink_resource_occ_get_unregister(devlink,
295 					    NSIM_RESOURCE_IPV6_FIB_RULES);
296 	devlink_resource_occ_get_unregister(devlink,
297 					    NSIM_RESOURCE_IPV6_FIB);
298 	devlink_resource_occ_get_unregister(devlink,
299 					    NSIM_RESOURCE_IPV4_FIB_RULES);
300 	devlink_resource_occ_get_unregister(devlink,
301 					    NSIM_RESOURCE_IPV4_FIB);
302 	unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
303 	kfree(data);
304 }
305