xref: /openbmc/linux/drivers/net/netdevsim/fib.c (revision ba61bb17)
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_pernet_subsys(&nsim_fib_net_ops);
245 	unregister_fib_notifier(&nsim_fib_nb);
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 		goto err_out;
262 	}
263 
264 err_out:
265 	return err;
266 }
267