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(net: seq_file_single_net(seq: 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(src: p, srclen: end - p, dst: (u8 *)&pref->ipv4_addr, delim: -1, end: &stop)) {
100 pref->family = AF_INET;
101 mask = 32;
102 } else if (in6_pton(src: p, srclen: end - p, dst: (u8 *)&pref->ipv6_addr, delim: -1, end: &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(p: argv[1], pref: &pref);
269 if (ret < 0)
270 return ret;
271
272 ret = kstrtou16(s: argv[2], base: 10, res: &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(a: &pref, b: &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: &pref, index: 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: &pref, index: 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(p: argv[1], pref: &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(a: &pref, b: &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, index: 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(inode: file_inode(f: 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(pbuf: &buf, strv: 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: &preflist, argc: argc - 1, argv: argv + 1);
423 else if (strcmp(argv[0], "del") == 0)
424 ret = afs_del_address_pref(net, preflist: &preflist, argc: argc - 1, argv: 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(objp: preflist);
441 inode_unlock(inode: file_inode(f: 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(peer: 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(a: &test, b: &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(peer: 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(a: &test, b: &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

source code of linux/fs/afs/addr_prefs.c