]>
Commit | Line | Data |
---|---|---|
f94f70d3 DH |
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 ([email protected]) | |
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(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 | */ | |
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(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 | ||
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(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 | */ | |
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(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 | */ | |
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(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 | ||
439 | done: | |
440 | kfree(preflist); | |
441 | inode_unlock(file_inode(file)); | |
442 | _leave(" = %d", ret); | |
443 | return ret; | |
444 | ||
445 | inval: | |
446 | pr_warn("Invalid Command\n"); | |
447 | ret = -EINVAL; | |
448 | goto done; | |
449 | } | |
d14cf8ed DH |
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(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 | */ | |
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 | } |