532 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* Address preferences management3 *4 * Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.5 * Written by David Howells (dhowells@redhat.com)6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": addr_prefs: " fmt9#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 16static 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. The23 * source string will be modified to have NUL-terminations inserted.24 */25static 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 */72static 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 085 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#endif98 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 142enum 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 */152static 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 */205static 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_prefs249 */250static 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 */306static 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_prefs329 */330static 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_prefs378 */379int 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 else397 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 else411 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 else426 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 439done:440 kfree(preflist);441 inode_unlock(file_inode(file));442 _leave(" = %d", ret);443 return ret;444 445inval:446 pr_warn("Invalid Command\n");447 ret = -EINVAL;448 goto done;449}450 451/*452 * Mark the priorities on an address list if the address preferences table has453 * changed. The caller must hold the RCU read lock.454 */455void 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 has519 * changed. Avoid taking the RCU read lock if we can.520 */521void 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}532