Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/fs/afs/addr_prefs.c
26285 views
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) && *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
}
534
535