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 | |
16 | static 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 | */ |
25 | static 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 | */ |
72 | static 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 | |
142 | enum 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 | */ |
152 | static 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 | */ |
205 | static 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 | */ |
250 | static 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 | */ |
306 | static 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 | */ |
330 | static 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 | */ |
379 | int 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 | |
439 | done: |
440 | kfree(objp: preflist); |
441 | inode_unlock(inode: file_inode(f: file)); |
442 | _leave(" = %d" , ret); |
443 | return ret; |
444 | |
445 | inval: |
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 | */ |
455 | void 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 | */ |
521 | void 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 | |