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 (dhowells@redhat.com) 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 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 else 426 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 439 done: 440 kfree(preflist); 441 inode_unlock(file_inode(file)); 442 _leave(" = %d", ret); 443 return ret; 444 445 inval: 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 has 453 * changed. The caller must hold the RCU read lock. 454 */ 455 void 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 has 519 * changed. Avoid taking the RCU read lock if we can. 520 */ 521 void 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