brintos

brintos / linux-shallow public Read only

0
0
Text · 11.9 KiB · a189ff8 Raw
532 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* Address preferences management3 *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: " fmt9#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.  The23 * 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 085	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#endif98 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_prefs249 */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_prefs329 */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_prefs378 */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	else397		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	else411		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		else426			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 has453 * 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 has519 * 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