]> Git Repo - linux.git/blob - fs/afs/addr_prefs.c
Linux 6.14-rc3
[linux.git] / fs / afs / addr_prefs.c
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                         ret = argc;
418                         goto done;
419                 }
420                 if (argc < 2)
421                         goto inval;
422
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);
427                 else
428                         goto inval;
429                 if (ret < 0)
430                         goto done;
431         } while (*buf);
432
433         preflist->version++;
434         rcu_assign_pointer(net->address_prefs, preflist);
435         /* Store prefs before version */
436         smp_store_release(&net->address_pref_version, preflist->version);
437         kfree_rcu(old, rcu);
438         preflist = NULL;
439         ret = 0;
440
441 done:
442         kfree(preflist);
443         inode_unlock(file_inode(file));
444         _leave(" = %d", ret);
445         return ret;
446
447 inval:
448         pr_warn("Invalid Command\n");
449         ret = -EINVAL;
450         goto done;
451 }
452
453 /*
454  * Mark the priorities on an address list if the address preferences table has
455  * changed.  The caller must hold the RCU read lock.
456  */
457 void afs_get_address_preferences_rcu(struct afs_net *net, struct afs_addr_list *alist)
458 {
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;
465         enum cmp_ret cmp;
466         int i, j;
467
468         if (!preflist || !preflist->nr || !alist->nr_addrs ||
469             smp_load_acquire(&alist->addr_pref_version) == preflist->version)
470                 return;
471
472         test.family = AF_INET;
473         test.subnet_mask = 32;
474         test.prio = 0;
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]);
481                         switch (cmp) {
482                         case CONTINUE_SEARCH:
483                                 continue;
484                         case INSERT_HERE:
485                                 break;
486                         case EXACT_MATCH:
487                         case SUBNET_MATCH:
488                                 WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
489                                 break;
490                         }
491                 }
492         }
493
494         test.family = AF_INET6;
495         test.subnet_mask = 128;
496         test.prio = 0;
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]);
503                         switch (cmp) {
504                         case CONTINUE_SEARCH:
505                                 continue;
506                         case INSERT_HERE:
507                                 break;
508                         case EXACT_MATCH:
509                         case SUBNET_MATCH:
510                                 WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
511                                 break;
512                         }
513                 }
514         }
515
516         smp_store_release(&alist->addr_pref_version, preflist->version);
517 }
518
519 /*
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.
522  */
523 void afs_get_address_preferences(struct afs_net *net, struct afs_addr_list *alist)
524 {
525         if (!net->address_prefs ||
526             /* Load version before prefs */
527             smp_load_acquire(&net->address_pref_version) == alist->addr_pref_version)
528                 return;
529
530         rcu_read_lock();
531         afs_get_address_preferences_rcu(net, alist);
532         rcu_read_unlock();
533 }
This page took 0.060688 seconds and 4 git commands to generate.