1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Address preferences management
4 * Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
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>
16 static inline struct afs_net *afs_seq2net_single(struct seq_file *m)
18 return afs_net(seq_file_single_net(m));
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.
25 static int afs_split_string(char **pbuf, char *strv[], unsigned int maxstrv)
27 unsigned int count = 0;
30 maxstrv--; /* Allow for terminal NULL */
32 /* Skip over spaces */
43 /* Mark start of word */
44 if (count >= maxstrv) {
45 pr_warn("Too many elements in string\n");
56 /* Mark end of word */
70 * Parse an address with an optional subnet mask.
72 static int afs_parse_address(char *p, struct afs_addr_preference *pref)
75 unsigned long mask, tmp;
76 char *end = p + strlen(p);
87 q = memchr(p, ']', end - p);
89 pr_warn("Can't find closing ']'\n");
93 for (q = p; q < end; q++)
99 if (in4_pton(p, end - p, (u8 *)&pref->ipv4_addr, -1, &stop)) {
100 pref->family = AF_INET;
102 } else if (in6_pton(p, end - p, (u8 *)&pref->ipv6_addr, -1, &stop)) {
103 pref->family = AF_INET6;
106 pr_warn("Can't determine address family\n");
113 pr_warn("Can't find closing ']'\n");
121 tmp = simple_strtoul(p, &p, 10);
123 pr_warn("Subnet mask too large\n");
127 pr_warn("Subnet mask too small\n");
134 pr_warn("Invalid address\n");
138 pref->subnet_mask = mask;
150 * See if a candidate address matches a listed address.
152 static enum cmp_ret afs_cmp_address_pref(const struct afs_addr_preference *a,
153 const struct afs_addr_preference *b)
155 int subnet = min(a->subnet_mask, b->subnet_mask);
156 const __be32 *pa, *pb;
160 if (a->family != b->family)
165 pa = a->ipv6_addr.s6_addr32;
166 pb = b->ipv6_addr.s6_addr32;
169 pa = &a->ipv4_addr.s_addr;
170 pb = &b->ipv4_addr.s_addr;
174 while (subnet > 32) {
175 diff = ntohl(*pa++) - ntohl(*pb++);
177 return INSERT_HERE; /* a<b */
179 return CONTINUE_SEARCH; /* a>b */
186 mask = 0xffffffffU << (32 - subnet);
189 diff = (na & mask) - (nb & mask);
190 //kdebug("diff %08x %08x %08x %d", na, nb, mask, diff);
192 return INSERT_HERE; /* a<b */
194 return CONTINUE_SEARCH; /* a>b */
195 if (a->subnet_mask == b->subnet_mask)
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 */
203 * Insert an address preference.
205 static int afs_insert_address_pref(struct afs_addr_preference_list **_preflist,
206 struct afs_addr_preference *pref,
209 struct afs_addr_preference_list *preflist = *_preflist, *old = preflist;
210 size_t size, max_prefs;
212 _enter("{%u/%u/%u},%u", preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
214 if (preflist->nr == 255)
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);
224 *preflist = **_preflist;
225 preflist->max_prefs = max_prefs;
226 *_preflist = preflist;
228 if (index < preflist->nr)
229 memcpy(preflist->prefs + index + 1, old->prefs + index,
230 sizeof(*pref) * (preflist->nr - index));
232 memcpy(preflist->prefs, old->prefs, sizeof(*pref) * index);
234 if (index < preflist->nr)
235 memmove(preflist->prefs + index + 1, preflist->prefs + index,
236 sizeof(*pref) * (preflist->nr - index));
239 preflist->prefs[index] = *pref;
241 if (pref->family == AF_INET)
242 preflist->ipv6_off++;
247 * Add an address preference.
248 * echo "add <proto> <IP>[/<mask>] <prior>" >/proc/fs/afs/addr_prefs
250 static int afs_add_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
251 int argc, char **argv)
253 struct afs_addr_preference_list *preflist = *_preflist;
254 struct afs_addr_preference pref;
259 pr_warn("Wrong number of params\n");
263 if (strcmp(argv[0], "udp") != 0) {
264 pr_warn("Unsupported protocol\n");
268 ret = afs_parse_address(argv[1], &pref);
272 ret = kstrtou16(argv[2], 10, &pref.prio);
274 pr_warn("Invalid priority\n");
278 if (pref.family == AF_INET) {
280 stop = preflist->ipv6_off;
282 i = preflist->ipv6_off;
286 for (; i < stop; i++) {
287 cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
289 case CONTINUE_SEARCH:
293 return afs_insert_address_pref(_preflist, &pref, i);
295 preflist->prefs[i].prio = pref.prio;
300 return afs_insert_address_pref(_preflist, &pref, i);
304 * Delete an address preference.
306 static int afs_delete_address_pref(struct afs_addr_preference_list **_preflist,
309 struct afs_addr_preference_list *preflist = *_preflist;
311 _enter("{%u/%u/%u},%u", preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
313 if (preflist->nr == 0)
316 if (index < preflist->nr - 1)
317 memmove(preflist->prefs + index, preflist->prefs + index + 1,
318 sizeof(preflist->prefs[0]) * (preflist->nr - index - 1));
320 if (index < preflist->ipv6_off)
321 preflist->ipv6_off--;
327 * Delete an address preference.
328 * echo "del <proto> <IP>[/<mask>]" >/proc/fs/afs/addr_prefs
330 static int afs_del_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
331 int argc, char **argv)
333 struct afs_addr_preference_list *preflist = *_preflist;
334 struct afs_addr_preference pref;
339 pr_warn("Wrong number of params\n");
343 if (strcmp(argv[0], "udp") != 0) {
344 pr_warn("Unsupported protocol\n");
348 ret = afs_parse_address(argv[1], &pref);
352 if (pref.family == AF_INET) {
354 stop = preflist->ipv6_off;
356 i = preflist->ipv6_off;
360 for (; i < stop; i++) {
361 cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
363 case CONTINUE_SEARCH:
369 return afs_delete_address_pref(_preflist, i);
377 * Handle writes to /proc/fs/afs/addr_prefs
379 int afs_proc_addr_prefs_write(struct file *file, char *buf, size_t size)
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);
386 int ret, argc, max_prefs;
388 inode_lock(file_inode(file));
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));
395 max_prefs = old->nr + 1;
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);
404 preflist = kmalloc(struct_size(preflist, prefs, max_prefs), GFP_KERNEL);
409 memcpy(preflist, old, struct_size(preflist, prefs, old->nr));
411 memset(preflist, 0, sizeof(*preflist));
412 preflist->max_prefs = max_prefs;
415 argc = afs_split_string(&buf, argv, ARRAY_SIZE(argv));
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);
434 rcu_assign_pointer(net->address_prefs, preflist);
435 /* Store prefs before version */
436 smp_store_release(&net->address_pref_version, preflist->version);
443 inode_unlock(file_inode(file));
444 _leave(" = %d", ret);
448 pr_warn("Invalid Command\n");
454 * Mark the priorities on an address list if the address preferences table has
455 * changed. The caller must hold the RCU read lock.
457 void afs_get_address_preferences_rcu(struct afs_net *net, struct afs_addr_list *alist)
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;
468 if (!preflist || !preflist->nr || !alist->nr_addrs ||
469 smp_load_acquire(&alist->addr_pref_version) == preflist->version)
472 test.family = AF_INET;
473 test.subnet_mask = 32;
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]);
482 case CONTINUE_SEARCH:
488 WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
494 test.family = AF_INET6;
495 test.subnet_mask = 128;
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]);
504 case CONTINUE_SEARCH:
510 WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
516 smp_store_release(&alist->addr_pref_version, preflist->version);
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.
523 void afs_get_address_preferences(struct afs_net *net, struct afs_addr_list *alist)
525 if (!net->address_prefs ||
526 /* Load version before prefs */
527 smp_load_acquire(&net->address_pref_version) == alist->addr_pref_version)
531 afs_get_address_preferences_rcu(net, alist);