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))
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			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
439done:
440	kfree(preflist);
441	inode_unlock(file_inode(file));
442	_leave(" = %d", ret);
443	return ret;
444
445inval:
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 */
455void 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 */
521void 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