// SPDX-License-Identifier: GPL-2.0-or-later
/* Address preferences management
*
* Copyright ( C ) 2023 Red Hat , Inc . All Rights Reserved .
* Written by David Howells ( dhowells @ redhat . com )
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": addr_prefs: " fmt
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/inet.h>
#include <linux/seq_file.h>
#include <keys/rxrpc-type.h>
#include "internal.h"
static inline struct afs_net *afs_seq2net_single(struct seq_file *m)
{
return afs_net(seq_file_single_net(m));
}
/*
* Split a NUL - terminated string up to the first newline around spaces . The
* source string will be modified to have NUL - terminations inserted .
*/
static int afs_split_string(char **pbuf, char *strv[], unsigned int maxstrv)
{
unsigned int count = 0 ;
char *p = *pbuf;
maxstrv--; /* Allow for terminal NULL */
for (;;) {
/* Skip over spaces */
while (isspace(*p)) {
if (*p == '\n' ) {
p++;
break ;
}
p++;
}
if (!*p)
break ;
/* Mark start of word */
if (count >= maxstrv) {
pr_warn("Too many elements in string\n" );
return -EINVAL;
}
strv[count++] = p;
/* Skip over word */
while (!isspace(*p) && *p)
p++;
if (!*p)
break ;
/* Mark end of word */
if (*p == '\n' ) {
*p++ = 0 ;
break ;
}
*p++ = 0 ;
}
*pbuf = p;
strv[count] = NULL;
return count;
}
/*
* Parse an address with an optional subnet mask .
*/
static int afs_parse_address(char *p, struct afs_addr_preference *pref)
{
const char *stop;
unsigned long mask, tmp;
char *end = p + strlen(p);
bool bracket = false ;
if (*p == '[' ) {
p++;
bracket = true ;
}
#if 0
if (*p == '[' ) {
p++;
q = memchr(p, ']' , end - p);
if (!q) {
pr_warn("Can't find closing ']'\n" );
return -EINVAL;
}
} else {
for (q = p; q < end; q++)
if (*q == '/' )
break ;
}
#endif
if (in4_pton(p, end - p, (u8 *)&pref->ipv4_addr, -1 , &stop)) {
pref->family = AF_INET;
mask = 32 ;
} else if (in6_pton(p, end - p, (u8 *)&pref->ipv6_addr, -1 , &stop)) {
pref->family = AF_INET6;
mask = 128 ;
} else {
pr_warn("Can't determine address family\n" );
return -EINVAL;
}
p = (char *)stop;
if (bracket) {
if (*p != ']' ) {
pr_warn("Can't find closing ']'\n" );
return -EINVAL;
}
p++;
}
if (*p == '/' ) {
p++;
tmp = simple_strtoul(p, &p, 10 );
if (tmp > mask) {
pr_warn("Subnet mask too large\n" );
return -EINVAL;
}
if (tmp == 0 ) {
pr_warn("Subnet mask too small\n" );
return -EINVAL;
}
mask = tmp;
}
if (*p) {
pr_warn("Invalid address\n" );
return -EINVAL;
}
pref->subnet_mask = mask;
return 0 ;
}
enum cmp_ret {
CONTINUE_SEARCH,
INSERT_HERE,
EXACT_MATCH,
SUBNET_MATCH,
};
/*
* See if a candidate address matches a listed address .
*/
static enum cmp_ret afs_cmp_address_pref(const struct afs_addr_preference *a,
const struct afs_addr_preference *b)
{
int subnet = min(a->subnet_mask, b->subnet_mask);
const __be32 *pa, *pb;
u32 mask, na, nb;
int diff;
if (a->family != b->family)
return INSERT_HERE;
switch (a->family) {
case AF_INET6:
pa = a->ipv6_addr.s6_addr32;
pb = b->ipv6_addr.s6_addr32;
break ;
case AF_INET:
pa = &a->ipv4_addr.s_addr;
pb = &b->ipv4_addr.s_addr;
break ;
}
while (subnet > 32 ) {
diff = ntohl(*pa++) - ntohl(*pb++);
if (diff < 0 )
return INSERT_HERE; /* a<b */
if (diff > 0 )
return CONTINUE_SEARCH; /* a>b */
subnet -= 32 ;
}
if (subnet == 0 )
return EXACT_MATCH;
mask = 0 xffffffffU << (32 - subnet);
na = ntohl(*pa);
nb = ntohl(*pb);
diff = (na & mask) - (nb & mask);
//kdebug("diff %08x %08x %08x %d", na, nb, mask, diff);
if (diff < 0 )
return INSERT_HERE; /* a<b */
if (diff > 0 )
return CONTINUE_SEARCH; /* a>b */
if (a->subnet_mask == b->subnet_mask)
return EXACT_MATCH;
if (a->subnet_mask > b->subnet_mask)
return SUBNET_MATCH; /* a binds tighter than b */
return CONTINUE_SEARCH; /* b binds tighter than a */
}
/*
* Insert an address preference .
*/
static int afs_insert_address_pref(struct afs_addr_preference_list **_preflist,
struct afs_addr_preference *pref,
int index)
{
struct afs_addr_preference_list *preflist = *_preflist, *old = preflist;
size_t size, max_prefs;
_enter("{%u/%u/%u},%u" , preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
if (preflist->nr == 255 )
return -ENOSPC;
if (preflist->nr >= preflist->max_prefs) {
max_prefs = preflist->max_prefs + 1 ;
size = struct_size(preflist, prefs, max_prefs);
size = roundup_pow_of_two(size);
max_prefs = min_t(size_t, (size - sizeof (*preflist)) / sizeof (*pref), 255 );
preflist = kmalloc(size, GFP_KERNEL);
if (!preflist)
return -ENOMEM;
*preflist = **_preflist;
preflist->max_prefs = max_prefs;
*_preflist = preflist;
if (index < preflist->nr)
memcpy(preflist->prefs + index + 1 , old->prefs + index,
sizeof (*pref) * (preflist->nr - index));
if (index > 0 )
memcpy(preflist->prefs, old->prefs, sizeof (*pref) * index);
} else {
if (index < preflist->nr)
memmove(preflist->prefs + index + 1 , preflist->prefs + index,
sizeof (*pref) * (preflist->nr - index));
}
preflist->prefs[index] = *pref;
preflist->nr++;
if (pref->family == AF_INET)
preflist->ipv6_off++;
return 0 ;
}
/*
* Add an address preference .
* echo " add < proto > < IP > [ / < mask > ] < prior > " > / proc / fs / afs / addr_prefs
*/
static int afs_add_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
int argc, char **argv)
{
struct afs_addr_preference_list *preflist = *_preflist;
struct afs_addr_preference pref;
enum cmp_ret cmp;
int ret, i, stop;
if (argc != 3 ) {
pr_warn("Wrong number of params\n" );
return -EINVAL;
}
if (strcmp(argv[0 ], "udp" ) != 0 ) {
pr_warn("Unsupported protocol\n" );
return -EINVAL;
}
ret = afs_parse_address(argv[1 ], &pref);
if (ret < 0 )
return ret;
ret = kstrtou16(argv[2 ], 10 , &pref.prio);
if (ret < 0 ) {
pr_warn("Invalid priority\n" );
return ret;
}
if (pref.family == AF_INET) {
i = 0 ;
stop = preflist->ipv6_off;
} else {
i = preflist->ipv6_off;
stop = preflist->nr;
}
for (; i < stop; i++) {
cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
switch (cmp) {
case CONTINUE_SEARCH:
continue ;
case INSERT_HERE:
case SUBNET_MATCH:
return afs_insert_address_pref(_preflist, &pref, i);
case EXACT_MATCH:
preflist->prefs[i].prio = pref.prio;
return 0 ;
}
}
return afs_insert_address_pref(_preflist, &pref, i);
}
/*
* Delete an address preference .
*/
static int afs_delete_address_pref(struct afs_addr_preference_list **_preflist,
int index)
{
struct afs_addr_preference_list *preflist = *_preflist;
_enter("{%u/%u/%u},%u" , preflist->ipv6_off, preflist->nr, preflist->max_prefs, index);
if (preflist->nr == 0 )
return -ENOENT;
if (index < preflist->nr - 1 )
memmove(preflist->prefs + index, preflist->prefs + index + 1 ,
sizeof (preflist->prefs[0 ]) * (preflist->nr - index - 1 ));
if (index < preflist->ipv6_off)
preflist->ipv6_off--;
preflist->nr--;
return 0 ;
}
/*
* Delete an address preference .
* echo " del < proto > < IP > [ / < mask > ] " > / proc / fs / afs / addr_prefs
*/
static int afs_del_address_pref(struct afs_net *net, struct afs_addr_preference_list **_preflist,
int argc, char **argv)
{
struct afs_addr_preference_list *preflist = *_preflist;
struct afs_addr_preference pref;
enum cmp_ret cmp;
int ret, i, stop;
if (argc != 2 ) {
pr_warn("Wrong number of params\n" );
return -EINVAL;
}
if (strcmp(argv[0 ], "udp" ) != 0 ) {
pr_warn("Unsupported protocol\n" );
return -EINVAL;
}
ret = afs_parse_address(argv[1 ], &pref);
if (ret < 0 )
return ret;
if (pref.family == AF_INET) {
i = 0 ;
stop = preflist->ipv6_off;
} else {
i = preflist->ipv6_off;
stop = preflist->nr;
}
for (; i < stop; i++) {
cmp = afs_cmp_address_pref(&pref, &preflist->prefs[i]);
switch (cmp) {
case CONTINUE_SEARCH:
continue ;
case INSERT_HERE:
case SUBNET_MATCH:
return 0 ;
case EXACT_MATCH:
return afs_delete_address_pref(_preflist, i);
}
}
return -ENOANO;
}
/*
* Handle writes to / proc / fs / afs / addr_prefs
*/
int afs_proc_addr_prefs_write(struct file *file, char *buf, size_t size)
{
struct afs_addr_preference_list *preflist, *old;
struct seq_file *m = file->private_data;
struct afs_net *net = afs_seq2net_single(m);
size_t psize;
char *argv[5 ];
int ret, argc, max_prefs;
inode_lock(file_inode(file));
/* Allocate a candidate new list and initialise it from the old. */
old = rcu_dereference_protected(net->address_prefs,
lockdep_is_held(&file_inode(file)->i_rwsem));
if (old)
max_prefs = old->nr + 1 ;
else
max_prefs = 1 ;
psize = struct_size(old, prefs, max_prefs);
psize = roundup_pow_of_two(psize);
max_prefs = min_t(size_t, (psize - sizeof (*old)) / sizeof (old->prefs[0 ]), 255 );
ret = -ENOMEM;
preflist = kmalloc(struct_size(preflist, prefs, max_prefs), GFP_KERNEL);
if (!preflist)
goto done;
if (old)
memcpy(preflist, old, struct_size(preflist, prefs, old->nr));
else
memset(preflist, 0 , sizeof (*preflist));
preflist->max_prefs = max_prefs;
do {
argc = afs_split_string(&buf, argv, ARRAY_SIZE(argv));
if (argc < 0 ) {
ret = argc;
goto done;
}
if (argc < 2 )
goto inval;
if (strcmp(argv[0 ], "add" ) == 0 )
ret = afs_add_address_pref(net, &preflist, argc - 1 , argv + 1 );
else if (strcmp(argv[0 ], "del" ) == 0 )
ret = afs_del_address_pref(net, &preflist, argc - 1 , argv + 1 );
else
goto inval;
if (ret < 0 )
goto done;
} while (*buf);
preflist->version++;
rcu_assign_pointer(net->address_prefs, preflist);
/* Store prefs before version */
smp_store_release(&net->address_pref_version, preflist->version);
kfree_rcu(old, rcu);
preflist = NULL;
ret = 0 ;
done:
kfree(preflist);
inode_unlock(file_inode(file));
_leave(" = %d" , ret);
return ret;
inval:
pr_warn("Invalid Command\n" );
ret = -EINVAL;
goto done;
}
/*
* Mark the priorities on an address list if the address preferences table has
* changed . The caller must hold the RCU read lock .
*/
void afs_get_address_preferences_rcu(struct afs_net *net, struct afs_addr_list *alist)
{
const struct afs_addr_preference_list *preflist =
rcu_dereference(net->address_prefs);
const struct sockaddr_in6 *sin6;
const struct sockaddr_in *sin;
const struct sockaddr *sa;
struct afs_addr_preference test;
enum cmp_ret cmp;
int i, j;
if (!preflist || !preflist->nr || !alist->nr_addrs ||
smp_load_acquire(&alist->addr_pref_version) == preflist->version)
return ;
test.family = AF_INET;
test.subnet_mask = 32 ;
test.prio = 0 ;
for (i = 0 ; i < alist->nr_ipv4; i++) {
sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
sin = (const struct sockaddr_in *)sa;
test.ipv4_addr = sin->sin_addr;
for (j = 0 ; j < preflist->ipv6_off; j++) {
cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
switch (cmp) {
case CONTINUE_SEARCH:
continue ;
case INSERT_HERE:
break ;
case EXACT_MATCH:
case SUBNET_MATCH:
WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
break ;
}
}
}
test.family = AF_INET6;
test.subnet_mask = 128 ;
test.prio = 0 ;
for (; i < alist->nr_addrs; i++) {
sa = rxrpc_kernel_remote_addr(alist->addrs[i].peer);
sin6 = (const struct sockaddr_in6 *)sa;
test.ipv6_addr = sin6->sin6_addr;
for (j = preflist->ipv6_off; j < preflist->nr; j++) {
cmp = afs_cmp_address_pref(&test, &preflist->prefs[j]);
switch (cmp) {
case CONTINUE_SEARCH:
continue ;
case INSERT_HERE:
break ;
case EXACT_MATCH:
case SUBNET_MATCH:
WRITE_ONCE(alist->addrs[i].prio, preflist->prefs[j].prio);
break ;
}
}
}
smp_store_release(&alist->addr_pref_version, preflist->version);
}
/*
* Mark the priorities on an address list if the address preferences table has
* changed . Avoid taking the RCU read lock if we can .
*/
void afs_get_address_preferences(struct afs_net *net, struct afs_addr_list *alist)
{
if (!net->address_prefs ||
/* Load version before prefs */
smp_load_acquire(&net->address_pref_version) == alist->addr_pref_version)
return ;
rcu_read_lock();
afs_get_address_preferences_rcu(net, alist);
rcu_read_unlock();
}
Messung V0.5 in Prozent C=96 H=86 G=90
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland