File Index
Files and directories
Sign in
FILE

addr_prefs.c

File preview with syntax highlighting.

linux-6.19.14/fs/afs/addr_prefs.c C
  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
 16static 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 */
 25static 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) && *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 */
 72static 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
142enum 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 */
152static 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 */
205static 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 */
250static 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 */
306static 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 */
330static 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 */
379int 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			ret = argc;
418			goto done;
419		}
420		if (argc < 2)
421			goto inval;
422
423		if (strcmp(argv[0], "add") == 0)
424			ret = afs_add_address_pref(net, &preflist, argc - 1, argv + 1);
425		else if (strcmp(argv[0], "del") == 0)
426			ret = afs_del_address_pref(net, &preflist, argc - 1, argv + 1);
427		else
428			goto inval;
429		if (ret < 0)
430			goto done;
431	} while (*buf);
432
433	preflist->version++;
434	rcu_assign_pointer(net->address_prefs, preflist);
435	/* Store prefs before version */
436	smp_store_release(&net->address_pref_version, preflist->version);
437	kfree_rcu(old, rcu);
438	preflist = NULL;
439	ret = 0;
440
441done:
442	kfree(preflist);
443	inode_unlock(file_inode(file));
444	_leave(" = %d", ret);
445	return ret;
446
447inval:
448	pr_warn("Invalid Command\n");
449	ret = -EINVAL;
450	goto done;
451}
452
453/*
454 * Mark the priorities on an address list if the address preferences table has
455 * changed.  The caller must hold the RCU read lock.
456 */
457void afs_get_address_preferences_rcu(struct afs_net *net, struct afs_addr_list *alist)
458{
459	const struct afs_addr_preference_list *preflist =
460		rcu_dereference(net->address_prefs);
461	const struct sockaddr_in6 *sin6;
462	const struct sockaddr_in *sin;
463	const struct sockaddr *sa;
464	struct afs_addr_preference test;
465	enum cmp_ret cmp;
466	int i, j;
467
468	if (!preflist || !preflist->nr || !alist->nr_addrs ||
469	    smp_load_acquire(&alist->addr_pref_version) == preflist->version)
470		return;
471
472	test.family = AF_INET;
473	test.subnet_mask = 32;
474	test.prio = 0;
475	for (i = 0; i < alist->nr_ipv4; i++) {
476		sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
477		sin = (const struct sockaddr_in *)sa;
478		test.ipv4_addr = sin->sin_addr;
479		for (j = 0; j < preflist->ipv6_off; j++) {
480			cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
481			switch (cmp) {
482			case CONTINUE_SEARCH:
483				continue;
484			case INSERT_HERE:
485				break;
486			case EXACT_MATCH:
487			case SUBNET_MATCH:
488				WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
489				break;
490			}
491		}
492	}
493
494	test.family = AF_INET6;
495	test.subnet_mask = 128;
496	test.prio = 0;
497	for (; i < alist->nr_addrs; i++) {
498		sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
499		sin6 = (const struct sockaddr_in6 *)sa;
500		test.ipv6_addr = sin6->sin6_addr;
501		for (j = preflist->ipv6_off; j < preflist->nr; j++) {
502			cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
503			switch (cmp) {
504			case CONTINUE_SEARCH:
505				continue;
506			case INSERT_HERE:
507				break;
508			case EXACT_MATCH:
509			case SUBNET_MATCH:
510				WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
511				break;
512			}
513		}
514	}
515
516	smp_store_release(&alist->addr_pref_version, preflist->version);
517}
518
519/*
520 * Mark the priorities on an address list if the address preferences table has
521 * changed.  Avoid taking the RCU read lock if we can.
522 */
523void afs_get_address_preferences(struct afs_net *net, struct afs_addr_list *alist)
524{
525	if (!net->address_prefs ||
526	    /* Load version before prefs */
527	    smp_load_acquire(&net->address_pref_version) == alist->addr_pref_version)
528		return;
529
530	rcu_read_lock();
531	afs_get_address_preferences_rcu(net, alist);
532	rcu_read_unlock();
533}
~/ Read-only file preview.