xref: /linux/fs/afs/addr_prefs.c (revision 06d07429858317ded2db7986113a9e0129cd599b)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Address preferences management
3  *
4  * Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
5  * Written by David Howells (dhowells@redhat.com)
6  */
7 
8 #define pr_fmt(fmt) KBUILD_MODNAME ": addr_prefs: " fmt
9 #include <linux/slab.h>
10 #include <linux/ctype.h>
11 #include <linux/inet.h>
12 #include <linux/seq_file.h>
13 #include <keys/rxrpc-type.h>
14 #include "internal.h"
15 
afs_seq2net_single(struct seq_file * m)16 static inline struct afs_net *afs_seq2net_single(struct seq_file *m)
17 {
18 	return afs_net(seq_file_single_net(m));
19 }
20 
21 /*
22  * Split a NUL-terminated string up to the first newline around spaces.  The
23  * source string will be modified to have NUL-terminations inserted.
24  */
afs_split_string(char ** pbuf,char * strv[],unsigned int maxstrv)25 static int afs_split_string(char **pbuf, char *strv[], unsigned int maxstrv)
26 {
27 	unsigned int count = 0;
28 	char *p = *pbuf;
29 
30 	maxstrv--; /* Allow for terminal NULL */
31 	for (;;) {
32 		/* Skip over spaces */
33 		while (isspace(*p)) {
34 			if (*p == '\n') {
35 				p++;
36 				break;
37 			}
38 			p++;
39 		}
40 		if (!*p)
41 			break;
42 
43 		/* Mark start of word */
44 		if (count >= maxstrv) {
45 			pr_warn("Too many elements in string\n");
46 			return -EINVAL;
47 		}
48 		strv[count++] = p;
49 
50 		/* Skip over word */
51 		while (!isspace(*p))
52 			p++;
53 		if (!*p)
54 			break;
55 
56 		/* Mark end of word */
57 		if (*p == '\n') {
58 			*p++ = 0;
59 			break;
60 		}
61 		*p++ = 0;
62 	}
63 
64 	*pbuf = p;
65 	strv[count] = NULL;
66 	return count;
67 }
68 
69 /*
70  * Parse an address with an optional subnet mask.
71  */
afs_parse_address(char * p,struct afs_addr_preference * pref)72 static int afs_parse_address(char *p, struct afs_addr_preference *pref)
73 {
74 	const char *stop;
75 	unsigned long mask, tmp;
76 	char *end = p + strlen(p);
77 	bool bracket = false;
78 
79 	if (*p == '[') {
80 		p++;
81 		bracket = true;
82 	}
83 
84 #if 0
85 	if (*p == '[') {
86 		p++;
87 		q = memchr(p, ']', end - p);
88 		if (!q) {
89 			pr_warn("Can't find closing ']'\n");
90 			return -EINVAL;
91 		}
92 	} else {
93 		for (q = p; q < end; q++)
94 			if (*q == '/')
95 				break;
96 	}
97 #endif
98 
99 	if (in4_pton(p, end - p, (u8 *)&pref->ipv4_addr, -1, &stop)) {
100 		pref->family = AF_INET;
101 		mask = 32;
102 	} else if (in6_pton(p, end - p, (u8 *)&pref->ipv6_addr, -1, &stop)) {
103 		pref->family = AF_INET6;
104 		mask = 128;
105 	} else {
106 		pr_warn("Can't determine address family\n");
107 		return -EINVAL;
108 	}
109 
110 	p = (char *)stop;
111 	if (bracket) {
112 		if (*p != ']') {
113 			pr_warn("Can't find closing ']'\n");
114 			return -EINVAL;
115 		}
116 		p++;
117 	}
118 
119 	if (*p == '/') {
120 		p++;
121 		tmp = simple_strtoul(p, &p, 10);
122 		if (tmp > mask) {
123 			pr_warn("Subnet mask too large\n");
124 			return -EINVAL;
125 		}
126 		if (tmp == 0) {
127 			pr_warn("Subnet mask too small\n");
128 			return -EINVAL;
129 		}
130 		mask = tmp;
131 	}
132 
133 	if (*p) {
134 		pr_warn("Invalid address\n");
135 		return -EINVAL;
136 	}
137 
138 	pref->subnet_mask = mask;
139 	return 0;
140 }
141 
142 enum cmp_ret {
143 	CONTINUE_SEARCH,
144 	INSERT_HERE,
145 	EXACT_MATCH,
146 	SUBNET_MATCH,
147 };
148 
149 /*
150  * See if a candidate address matches a listed address.
151  */
afs_cmp_address_pref(const struct afs_addr_preference * a,const struct afs_addr_preference * b)152 static enum cmp_ret afs_cmp_address_pref(const struct afs_addr_preference *a,
153 					 const struct afs_addr_preference *b)
154 {
155 	int subnet = min(a->subnet_mask, b->subnet_mask);
156 	const __be32 *pa, *pb;
157 	u32 mask, na, nb;
158 	int diff;
159 
160 	if (a->family != b->family)
161 		return INSERT_HERE;
162 
163 	switch (a->family) {
164 	case AF_INET6:
165 		pa = a->ipv6_addr.s6_addr32;
166 		pb = b->ipv6_addr.s6_addr32;
167 		break;
168 	case AF_INET:
169 		pa = &a->ipv4_addr.s_addr;
170 		pb = &b->ipv4_addr.s_addr;
171 		break;
172 	}
173 
174 	while (subnet > 32) {
175 		diff = ntohl(*pa++) - ntohl(*pb++);
176 		if (diff < 0)
177 			return INSERT_HERE; /* a<b */
178 		if (diff > 0)
179 			return CONTINUE_SEARCH; /* a>b */
180 		subnet -= 32;
181 	}
182 
183 	if (subnet == 0)
184 		return EXACT_MATCH;
185 
186 	mask = 0xffffffffU << (32 - subnet);
187 	na = ntohl(*pa);
188 	nb = ntohl(*pb);
189 	diff = (na & mask) - (nb & mask);
190 	//kdebug("diff %08x %08x %08x %d", na, nb, mask, diff);
191 	if (diff < 0)
192 		return INSERT_HERE; /* a<b */
193 	if (diff > 0)
194 		return CONTINUE_SEARCH; /* a>b */
195 	if (a->subnet_mask == b->subnet_mask)
196 		return EXACT_MATCH;
197 	if (a->subnet_mask > b->subnet_mask)
198 		return SUBNET_MATCH; /* a binds tighter than b */
199 	return CONTINUE_SEARCH; /* b binds tighter than a */
200 }
201 
202 /*
203  * Insert an address preference.
204  */
afs_insert_address_pref(struct afs_addr_preference_list ** _preflist,struct afs_addr_preference * pref,int index)205 static int afs_insert_address_pref(struct afs_addr_preference_list **_preflist,
206 				   struct afs_addr_preference *pref,
207 				   int index)
208 {
209 	struct afs_addr_preference_list *preflist = *_preflist, *old = preflist;
210 	size_t size, max_prefs;
211 
212 	_enter("{%u/%u/%u},%u", preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
213 
214 	if (preflist->nr == 255)
215 		return -ENOSPC;
216 	if (preflist->nr >= preflist->max_prefs) {
217 		max_prefs = preflist->max_prefs + 1;
218 		size = struct_size(preflist, prefs, max_prefs);
219 		size = roundup_pow_of_two(size);
220 		max_prefs = min_t(size_t, (size - sizeof(*preflist)) / sizeof(*pref), 255);
221 		preflist = kmalloc(size, GFP_KERNEL);
222 		if (!preflist)
223 			return -ENOMEM;
224 		*preflist = **_preflist;
225 		preflist->max_prefs = max_prefs;
226 		*_preflist = preflist;
227 
228 		if (index < preflist->nr)
229 			memcpy(preflist->prefs + index + 1, old->prefs + index,
230 			       sizeof(*pref) * (preflist->nr - index));
231 		if (index > 0)
232 			memcpy(preflist->prefs, old->prefs, sizeof(*pref) * index);
233 	} else {
234 		if (index < preflist->nr)
235 			memmove(preflist->prefs + index + 1, preflist->prefs + index,
236 			       sizeof(*pref) * (preflist->nr - index));
237 	}
238 
239 	preflist->prefs[index] = *pref;
240 	preflist->nr++;
241 	if (pref->family == AF_INET)
242 		preflist->ipv6_off++;
243 	return 0;
244 }
245 
246 /*
247  * Add an address preference.
248  *	echo "add <proto> <IP>[/<mask>] <prior>" >/proc/fs/afs/addr_prefs
249  */
afs_add_address_pref(struct afs_net * net,struct afs_addr_preference_list ** _preflist,int argc,char ** argv)250 static int afs_add_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
251 				int argc, char **argv)
252 {
253 	struct afs_addr_preference_list *preflist = *_preflist;
254 	struct afs_addr_preference pref;
255 	enum cmp_ret cmp;
256 	int ret, i, stop;
257 
258 	if (argc != 3) {
259 		pr_warn("Wrong number of params\n");
260 		return -EINVAL;
261 	}
262 
263 	if (strcmp(argv[0], "udp") != 0) {
264 		pr_warn("Unsupported protocol\n");
265 		return -EINVAL;
266 	}
267 
268 	ret = afs_parse_address(argv[1], &pref);
269 	if (ret < 0)
270 		return ret;
271 
272 	ret = kstrtou16(argv[2], 10, &pref.prio);
273 	if (ret < 0) {
274 		pr_warn("Invalid priority\n");
275 		return ret;
276 	}
277 
278 	if (pref.family == AF_INET) {
279 		i = 0;
280 		stop = preflist->ipv6_off;
281 	} else {
282 		i = preflist->ipv6_off;
283 		stop = preflist->nr;
284 	}
285 
286 	for (; i < stop; i++) {
287 		cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
288 		switch (cmp) {
289 		case CONTINUE_SEARCH:
290 			continue;
291 		case INSERT_HERE:
292 		case SUBNET_MATCH:
293 			return afs_insert_address_pref(_preflist, &pref, i);
294 		case EXACT_MATCH:
295 			preflist->prefs[i].prio = pref.prio;
296 			return 0;
297 		}
298 	}
299 
300 	return afs_insert_address_pref(_preflist, &pref, i);
301 }
302 
303 /*
304  * Delete an address preference.
305  */
afs_delete_address_pref(struct afs_addr_preference_list ** _preflist,int index)306 static int afs_delete_address_pref(struct afs_addr_preference_list **_preflist,
307 				   int index)
308 {
309 	struct afs_addr_preference_list *preflist = *_preflist;
310 
311 	_enter("{%u/%u/%u},%u", preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
312 
313 	if (preflist->nr == 0)
314 		return -ENOENT;
315 
316 	if (index < preflist->nr - 1)
317 		memmove(preflist->prefs + index, preflist->prefs + index + 1,
318 			sizeof(preflist->prefs[0]) * (preflist->nr - index - 1));
319 
320 	if (index < preflist->ipv6_off)
321 		preflist->ipv6_off--;
322 	preflist->nr--;
323 	return 0;
324 }
325 
326 /*
327  * Delete an address preference.
328  *	echo "del <proto> <IP>[/<mask>]" >/proc/fs/afs/addr_prefs
329  */
afs_del_address_pref(struct afs_net * net,struct afs_addr_preference_list ** _preflist,int argc,char ** argv)330 static int afs_del_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
331 				int argc, char **argv)
332 {
333 	struct afs_addr_preference_list *preflist = *_preflist;
334 	struct afs_addr_preference pref;
335 	enum cmp_ret cmp;
336 	int ret, i, stop;
337 
338 	if (argc != 2) {
339 		pr_warn("Wrong number of params\n");
340 		return -EINVAL;
341 	}
342 
343 	if (strcmp(argv[0], "udp") != 0) {
344 		pr_warn("Unsupported protocol\n");
345 		return -EINVAL;
346 	}
347 
348 	ret = afs_parse_address(argv[1], &pref);
349 	if (ret < 0)
350 		return ret;
351 
352 	if (pref.family == AF_INET) {
353 		i = 0;
354 		stop = preflist->ipv6_off;
355 	} else {
356 		i = preflist->ipv6_off;
357 		stop = preflist->nr;
358 	}
359 
360 	for (; i < stop; i++) {
361 		cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
362 		switch (cmp) {
363 		case CONTINUE_SEARCH:
364 			continue;
365 		case INSERT_HERE:
366 		case SUBNET_MATCH:
367 			return 0;
368 		case EXACT_MATCH:
369 			return afs_delete_address_pref(_preflist, i);
370 		}
371 	}
372 
373 	return -ENOANO;
374 }
375 
376 /*
377  * Handle writes to /proc/fs/afs/addr_prefs
378  */
afs_proc_addr_prefs_write(struct file * file,char * buf,size_t size)379 int afs_proc_addr_prefs_write(struct file *file, char *buf, size_t size)
380 {
381 	struct afs_addr_preference_list *preflist, *old;
382 	struct seq_file *m = file->private_data;
383 	struct afs_net *net = afs_seq2net_single(m);
384 	size_t psize;
385 	char *argv[5];
386 	int ret, argc, max_prefs;
387 
388 	inode_lock(file_inode(file));
389 
390 	/* Allocate a candidate new list and initialise it from the old. */
391 	old = rcu_dereference_protected(net->address_prefs,
392 					lockdep_is_held(&file_inode(file)->i_rwsem));
393 
394 	if (old)
395 		max_prefs = old->nr + 1;
396 	else
397 		max_prefs = 1;
398 
399 	psize = struct_size(old, prefs, max_prefs);
400 	psize = roundup_pow_of_two(psize);
401 	max_prefs = min_t(size_t, (psize - sizeof(*old)) / sizeof(old->prefs[0]), 255);
402 
403 	ret = -ENOMEM;
404 	preflist = kmalloc(struct_size(preflist, prefs, max_prefs), GFP_KERNEL);
405 	if (!preflist)
406 		goto done;
407 
408 	if (old)
409 		memcpy(preflist, old, struct_size(preflist, prefs, old->nr));
410 	else
411 		memset(preflist, 0, sizeof(*preflist));
412 	preflist->max_prefs = max_prefs;
413 
414 	do {
415 		argc = afs_split_string(&buf, argv, ARRAY_SIZE(argv));
416 		if (argc < 0)
417 			return argc;
418 		if (argc < 2)
419 			goto inval;
420 
421 		if (strcmp(argv[0], "add") == 0)
422 			ret = afs_add_address_pref(net, &preflist, argc - 1, argv + 1);
423 		else if (strcmp(argv[0], "del") == 0)
424 			ret = afs_del_address_pref(net, &preflist, argc - 1, argv + 1);
425 		else
426 			goto inval;
427 		if (ret < 0)
428 			goto done;
429 	} while (*buf);
430 
431 	preflist->version++;
432 	rcu_assign_pointer(net->address_prefs, preflist);
433 	/* Store prefs before version */
434 	smp_store_release(&net->address_pref_version, preflist->version);
435 	kfree_rcu(old, rcu);
436 	preflist = NULL;
437 	ret = 0;
438 
439 done:
440 	kfree(preflist);
441 	inode_unlock(file_inode(file));
442 	_leave(" = %d", ret);
443 	return ret;
444 
445 inval:
446 	pr_warn("Invalid Command\n");
447 	ret = -EINVAL;
448 	goto done;
449 }
450 
451 /*
452  * Mark the priorities on an address list if the address preferences table has
453  * changed.  The caller must hold the RCU read lock.
454  */
afs_get_address_preferences_rcu(struct afs_net * net,struct afs_addr_list * alist)455 void afs_get_address_preferences_rcu(struct afs_net *net, struct afs_addr_list *alist)
456 {
457 	const struct afs_addr_preference_list *preflist =
458 		rcu_dereference(net->address_prefs);
459 	const struct sockaddr_in6 *sin6;
460 	const struct sockaddr_in *sin;
461 	const struct sockaddr *sa;
462 	struct afs_addr_preference test;
463 	enum cmp_ret cmp;
464 	int i, j;
465 
466 	if (!preflist || !preflist->nr || !alist->nr_addrs ||
467 	    smp_load_acquire(&alist->addr_pref_version) == preflist->version)
468 		return;
469 
470 	test.family = AF_INET;
471 	test.subnet_mask = 32;
472 	test.prio = 0;
473 	for (i = 0; i < alist->nr_ipv4; i++) {
474 		sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
475 		sin = (const struct sockaddr_in *)sa;
476 		test.ipv4_addr = sin->sin_addr;
477 		for (j = 0; j < preflist->ipv6_off; j++) {
478 			cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
479 			switch (cmp) {
480 			case CONTINUE_SEARCH:
481 				continue;
482 			case INSERT_HERE:
483 				break;
484 			case EXACT_MATCH:
485 			case SUBNET_MATCH:
486 				WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
487 				break;
488 			}
489 		}
490 	}
491 
492 	test.family = AF_INET6;
493 	test.subnet_mask = 128;
494 	test.prio = 0;
495 	for (; i < alist->nr_addrs; i++) {
496 		sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
497 		sin6 = (const struct sockaddr_in6 *)sa;
498 		test.ipv6_addr = sin6->sin6_addr;
499 		for (j = preflist->ipv6_off; j < preflist->nr; j++) {
500 			cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
501 			switch (cmp) {
502 			case CONTINUE_SEARCH:
503 				continue;
504 			case INSERT_HERE:
505 				break;
506 			case EXACT_MATCH:
507 			case SUBNET_MATCH:
508 				WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
509 				break;
510 			}
511 		}
512 	}
513 
514 	smp_store_release(&alist->addr_pref_version, preflist->version);
515 }
516 
517 /*
518  * Mark the priorities on an address list if the address preferences table has
519  * changed.  Avoid taking the RCU read lock if we can.
520  */
afs_get_address_preferences(struct afs_net * net,struct afs_addr_list * alist)521 void afs_get_address_preferences(struct afs_net *net, struct afs_addr_list *alist)
522 {
523 	if (!net->address_prefs ||
524 	    /* Load version before prefs */
525 	    smp_load_acquire(&net->address_pref_version) == alist->addr_pref_version)
526 		return;
527 
528 	rcu_read_lock();
529 	afs_get_address_preferences_rcu(net, alist);
530 	rcu_read_unlock();
531 }
532