1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Address preferences management
4 * Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
5 * Written by David Howells (dhowells@redhat.com)
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>
16 static inline struct afs_net
*afs_seq2net_single(struct seq_file
*m
)
18 return afs_net(seq_file_single_net(m
));
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.
25 static int afs_split_string(char **pbuf
, char *strv
[], unsigned int maxstrv
)
27 unsigned int count
= 0;
30 maxstrv
--; /* Allow for terminal NULL */
32 /* Skip over spaces */
43 /* Mark start of word */
44 if (count
>= maxstrv
) {
45 pr_warn("Too many elements in string\n");
56 /* Mark end of word */
70 * Parse an address with an optional subnet mask.
72 static int afs_parse_address(char *p
, struct afs_addr_preference
*pref
)
75 unsigned long mask
, tmp
;
76 char *end
= p
+ strlen(p
);
87 q
= memchr(p
, ']', end
- p
);
89 pr_warn("Can't find closing ']'\n");
93 for (q
= p
; q
< end
; q
++)
99 if (in4_pton(p
, end
- p
, (u8
*)&pref
->ipv4_addr
, -1, &stop
)) {
100 pref
->family
= AF_INET
;
102 } else if (in6_pton(p
, end
- p
, (u8
*)&pref
->ipv6_addr
, -1, &stop
)) {
103 pref
->family
= AF_INET6
;
106 pr_warn("Can't determine address family\n");
113 pr_warn("Can't find closing ']'\n");
121 tmp
= simple_strtoul(p
, &p
, 10);
123 pr_warn("Subnet mask too large\n");
127 pr_warn("Subnet mask too small\n");
134 pr_warn("Invalid address\n");
138 pref
->subnet_mask
= mask
;
150 * See if a candidate address matches a listed address.
152 static enum cmp_ret
afs_cmp_address_pref(const struct afs_addr_preference
*a
,
153 const struct afs_addr_preference
*b
)
155 int subnet
= min(a
->subnet_mask
, b
->subnet_mask
);
156 const __be32
*pa
, *pb
;
160 if (a
->family
!= b
->family
)
165 pa
= a
->ipv6_addr
.s6_addr32
;
166 pb
= b
->ipv6_addr
.s6_addr32
;
169 pa
= &a
->ipv4_addr
.s_addr
;
170 pb
= &b
->ipv4_addr
.s_addr
;
174 while (subnet
> 32) {
175 diff
= ntohl(*pa
++) - ntohl(*pb
++);
177 return INSERT_HERE
; /* a<b */
179 return CONTINUE_SEARCH
; /* a>b */
186 mask
= 0xffffffffU
<< (32 - subnet
);
189 diff
= (na
& mask
) - (nb
& mask
);
190 //kdebug("diff %08x %08x %08x %d", na, nb, mask, diff);
192 return INSERT_HERE
; /* a<b */
194 return CONTINUE_SEARCH
; /* a>b */
195 if (a
->subnet_mask
== b
->subnet_mask
)
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 */
203 * Insert an address preference.
205 static int afs_insert_address_pref(struct afs_addr_preference_list
**_preflist
,
206 struct afs_addr_preference
*pref
,
209 struct afs_addr_preference_list
*preflist
= *_preflist
, *old
= preflist
;
210 size_t size
, max_prefs
;
212 _enter("{%u/%u/%u},%u", preflist
->ipv6_off
, preflist
->nr
, preflist
->max_prefs
, index
);
214 if (preflist
->nr
== 255)
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
);
224 *preflist
= **_preflist
;
225 preflist
->max_prefs
= max_prefs
;
226 *_preflist
= preflist
;
228 if (index
< preflist
->nr
)
229 memcpy(preflist
->prefs
+ index
+ 1, old
->prefs
+ index
,
230 sizeof(*pref
) * (preflist
->nr
- index
));
232 memcpy(preflist
->prefs
, old
->prefs
, sizeof(*pref
) * index
);
234 if (index
< preflist
->nr
)
235 memmove(preflist
->prefs
+ index
+ 1, preflist
->prefs
+ index
,
236 sizeof(*pref
) * (preflist
->nr
- index
));
239 preflist
->prefs
[index
] = *pref
;
241 if (pref
->family
== AF_INET
)
242 preflist
->ipv6_off
++;
247 * Add an address preference.
248 * echo "add <proto> <IP>[/<mask>] <prior>" >/proc/fs/afs/addr_prefs
250 static int afs_add_address_pref(struct afs_net
*net
, struct afs_addr_preference_list
**_preflist
,
251 int argc
, char **argv
)
253 struct afs_addr_preference_list
*preflist
= *_preflist
;
254 struct afs_addr_preference pref
;
259 pr_warn("Wrong number of params\n");
263 if (strcmp(argv
[0], "udp") != 0) {
264 pr_warn("Unsupported protocol\n");
268 ret
= afs_parse_address(argv
[1], &pref
);
272 ret
= kstrtou16(argv
[2], 10, &pref
.prio
);
274 pr_warn("Invalid priority\n");
278 if (pref
.family
== AF_INET
) {
280 stop
= preflist
->ipv6_off
;
282 i
= preflist
->ipv6_off
;
286 for (; i
< stop
; i
++) {
287 cmp
= afs_cmp_address_pref(&pref
, &preflist
->prefs
[i
]);
289 case CONTINUE_SEARCH
:
293 return afs_insert_address_pref(_preflist
, &pref
, i
);
295 preflist
->prefs
[i
].prio
= pref
.prio
;
300 return afs_insert_address_pref(_preflist
, &pref
, i
);
304 * Delete an address preference.
306 static int afs_delete_address_pref(struct afs_addr_preference_list
**_preflist
,
309 struct afs_addr_preference_list
*preflist
= *_preflist
;
311 _enter("{%u/%u/%u},%u", preflist
->ipv6_off
, preflist
->nr
, preflist
->max_prefs
, index
);
313 if (preflist
->nr
== 0)
316 if (index
< preflist
->nr
- 1)
317 memmove(preflist
->prefs
+ index
, preflist
->prefs
+ index
+ 1,
318 sizeof(preflist
->prefs
[0]) * (preflist
->nr
- index
- 1));
320 if (index
< preflist
->ipv6_off
)
321 preflist
->ipv6_off
--;
327 * Delete an address preference.
328 * echo "del <proto> <IP>[/<mask>]" >/proc/fs/afs/addr_prefs
330 static int afs_del_address_pref(struct afs_net
*net
, struct afs_addr_preference_list
**_preflist
,
331 int argc
, char **argv
)
333 struct afs_addr_preference_list
*preflist
= *_preflist
;
334 struct afs_addr_preference pref
;
339 pr_warn("Wrong number of params\n");
343 if (strcmp(argv
[0], "udp") != 0) {
344 pr_warn("Unsupported protocol\n");
348 ret
= afs_parse_address(argv
[1], &pref
);
352 if (pref
.family
== AF_INET
) {
354 stop
= preflist
->ipv6_off
;
356 i
= preflist
->ipv6_off
;
360 for (; i
< stop
; i
++) {
361 cmp
= afs_cmp_address_pref(&pref
, &preflist
->prefs
[i
]);
363 case CONTINUE_SEARCH
:
369 return afs_delete_address_pref(_preflist
, i
);
377 * Handle writes to /proc/fs/afs/addr_prefs
379 int afs_proc_addr_prefs_write(struct file
*file
, char *buf
, size_t size
)
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
);
386 int ret
, argc
, max_prefs
;
388 inode_lock(file_inode(file
));
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
));
395 max_prefs
= old
->nr
+ 1;
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);
404 preflist
= kmalloc(struct_size(preflist
, prefs
, max_prefs
), GFP_KERNEL
);
409 memcpy(preflist
, old
, struct_size(preflist
, prefs
, old
->nr
));
411 memset(preflist
, 0, sizeof(*preflist
));
412 preflist
->max_prefs
= max_prefs
;
415 argc
= afs_split_string(&buf
, argv
, ARRAY_SIZE(argv
));
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);
432 rcu_assign_pointer(net
->address_prefs
, preflist
);
433 /* Store prefs before version */
434 smp_store_release(&net
->address_pref_version
, preflist
->version
);
441 inode_unlock(file_inode(file
));
442 _leave(" = %d", ret
);
446 pr_warn("Invalid Command\n");
452 * Mark the priorities on an address list if the address preferences table has
453 * changed. The caller must hold the RCU read lock.
455 void afs_get_address_preferences_rcu(struct afs_net
*net
, struct afs_addr_list
*alist
)
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
;
466 if (!preflist
|| !preflist
->nr
|| !alist
->nr_addrs
||
467 smp_load_acquire(&alist
->addr_pref_version
) == preflist
->version
)
470 test
.family
= AF_INET
;
471 test
.subnet_mask
= 32;
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
]);
480 case CONTINUE_SEARCH
:
486 WRITE_ONCE(alist
->addrs
[i
].prio
, preflist
->prefs
[j
].prio
);
492 test
.family
= AF_INET6
;
493 test
.subnet_mask
= 128;
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
]);
502 case CONTINUE_SEARCH
:
508 WRITE_ONCE(alist
->addrs
[i
].prio
, preflist
->prefs
[j
].prio
);
514 smp_store_release(&alist
->addr_pref_version
, preflist
->version
);
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.
521 void afs_get_address_preferences(struct afs_net
*net
, struct afs_addr_list
*alist
)
523 if (!net
->address_prefs
||
524 /* Load version before prefs */
525 smp_load_acquire(&net
->address_pref_version
) == alist
->addr_pref_version
)
529 afs_get_address_preferences_rcu(net
, alist
);