1 /* 2 * SPDX-FileCopyrightText: 2013 Steven Barth <steven@midlink.org> 3 * SPDX-FileCopyrightText: 2013 Hans Dedecker <dedeckeh@gmail.com> 4 * SPDX-FileCopyrightText: 2022 Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk> 5 * SPDX-FileCopyrightText: 2024 Paul Donald <newtwen@gmail.com> 6 * SPDX-FileCopyrightText: 2024 David Härdeman <david@hardeman.nu> 7 * SPDX-FileCopyrightText: 2025 Álvaro Fernández Rojas <noltari@gmail.com> 8 * 9 * SPDX-License-Identifier: GPL2.0-only 10 */ 11 12 #include <stdio.h> 13 #include <stdint.h> 14 #include <stddef.h> 15 #include <string.h> 16 #include <stdlib.h> 17 #include <libgen.h> 18 #include <unistd.h> 19 #include <fcntl.h> 20 #include <time.h> 21 #include <netinet/in.h> 22 #include <netinet/ether.h> 23 #include <arpa/nameser.h> 24 #include <arpa/inet.h> 25 #include <resolv.h> 26 #include <spawn.h> 27 28 #include <libubox/md5.h> 29 #include <json-c/json.h> 30 31 #include "odhcpd.h" 32 #include "dhcpv6-ia.h" 33 #include "statefiles.h" 34 35 static uint8_t statemd5[16]; 36 37 struct write_ctxt { 38 FILE *fp; 39 md5_ctx_t md5; 40 struct interface *iface; 41 time_t now; // CLOCK_MONOTONIC 42 time_t wall_time; 43 }; 44 45 static FILE *statefiles_open_tmp_file(int dirfd) 46 { 47 int fd; 48 FILE *fp; 49 50 if (dirfd < 0) 51 return NULL; 52 53 fd = openat(dirfd, ODHCPD_TMP_FILE, O_CREAT | O_WRONLY | O_CLOEXEC, 0644); 54 if (fd < 0) 55 goto err; 56 57 if (lockf(fd, F_LOCK, 0) < 0) 58 goto err; 59 60 if (ftruncate(fd, 0) < 0) 61 goto err_del; 62 63 fp = fdopen(fd, "w"); 64 if (!fp) 65 goto err_del; 66 67 return fp; 68 69 err_del: 70 unlinkat(dirfd, ODHCPD_TMP_FILE, 0); 71 err: 72 close(fd); 73 error("Failed to create temporary file: %m"); 74 return NULL; 75 } 76 77 static void statefiles_finish_tmp_file(int dirfd, FILE **fpp, const char *prefix, const char *suffix) 78 { 79 char *filename; 80 81 if (dirfd < 0 || !fpp || !*fpp) 82 return; 83 84 if (!prefix) { 85 unlinkat(dirfd, ODHCPD_TMP_FILE, 0); 86 fclose(*fpp); 87 *fpp = NULL; 88 return; 89 } 90 91 if (fflush(*fpp)) 92 error("Error flushing tmpfile: %m"); 93 94 if (fsync(fileno(*fpp)) < 0) 95 error("Error synching tmpfile: %m"); 96 97 fclose(*fpp); 98 *fpp = NULL; 99 100 if (suffix) { 101 filename = alloca(strlen(prefix) + strlen(".") + strlen(suffix) + 1); 102 sprintf(filename, "%s.%s", prefix, suffix); 103 } else { 104 filename = alloca(strlen(prefix) + 1); 105 sprintf(filename, "%s", prefix); 106 } 107 108 renameat(dirfd, ODHCPD_TMP_FILE, dirfd, filename); 109 } 110 111 #define JSON_LENGTH "length" 112 #define JSON_PREFIX "prefix" 113 #define JSON_SLAAC "slaac" 114 #define JSON_TIME "time" 115 116 static inline time_t statefiles_time_from_json(time_t json_time) 117 { 118 time_t ref, now; 119 120 ref = time(NULL); 121 now = odhcpd_time(); 122 123 if (now > json_time || ref > json_time) 124 return 0; 125 126 return json_time + (now - ref); 127 } 128 129 static inline time_t statefiles_time_to_json(time_t config_time) 130 { 131 time_t ref, now; 132 133 ref = time(NULL); 134 now = odhcpd_time(); 135 136 return config_time + (ref - now); 137 } 138 139 static inline bool statefiles_ra_pio_enabled(struct interface *iface) 140 { 141 return config.ra_piodir_fd >= 0 && iface->ra == MODE_SERVER && !iface->master; 142 } 143 144 static bool statefiles_ra_pio_time(json_object *slaac_json, time_t *slaac_time) 145 { 146 time_t pio_json_time, pio_time; 147 json_object *time_json; 148 149 time_json = json_object_object_get(slaac_json, JSON_TIME); 150 if (!time_json) 151 return true; 152 153 pio_json_time = (time_t) json_object_get_int64(time_json); 154 if (!pio_json_time) 155 return true; 156 157 pio_time = statefiles_time_from_json(pio_json_time); 158 if (!pio_time) 159 return false; 160 161 *slaac_time = pio_time; 162 163 return true; 164 } 165 166 static json_object *statefiles_load_ra_pio_json(struct interface *iface) 167 { 168 json_object *json; 169 char filename[strlen(ODHCPD_PIO_FILE_PREFIX) + strlen(".") + strlen(iface->ifname) + 1]; 170 int fd; 171 172 sprintf(filename, "%s.%s", ODHCPD_PIO_FILE_PREFIX, iface->ifname); 173 fd = openat(config.ra_piodir_fd, filename, O_RDONLY | O_CLOEXEC); 174 if (fd < 0) 175 return NULL; 176 177 json = json_object_from_fd(fd); 178 179 close(fd); 180 181 if (!json) 182 error("rfc9096: %s: json read error %s", 183 iface->ifname, 184 json_util_get_last_err()); 185 186 return json; 187 } 188 189 void statefiles_read_prefix_information(struct interface *iface) 190 { 191 json_object *json, *slaac_json; 192 struct ra_pio *new_pios; 193 size_t pio_cnt; 194 time_t now; 195 196 if (!statefiles_ra_pio_enabled(iface)) 197 return; 198 199 json = statefiles_load_ra_pio_json(iface); 200 if (!json) 201 return; 202 203 slaac_json = json_object_object_get(json, JSON_SLAAC); 204 if (!slaac_json) { 205 json_object_put(json); 206 return; 207 } 208 209 now = odhcpd_time(); 210 211 pio_cnt = json_object_array_length(slaac_json); 212 new_pios = realloc(iface->pios, sizeof(struct ra_pio) * pio_cnt); 213 if (!new_pios) { 214 json_object_put(json); 215 return; 216 } 217 218 iface->pios = new_pios; 219 iface->pio_cnt = 0; 220 for (size_t i = 0; i < pio_cnt; i++) { 221 json_object *cur_pio_json, *length_json, *prefix_json; 222 const char *pio_str; 223 time_t pio_lt = 0; 224 struct ra_pio *pio; 225 uint8_t pio_len; 226 227 cur_pio_json = json_object_array_get_idx(slaac_json, i); 228 if (!cur_pio_json) 229 continue; 230 231 if (!statefiles_ra_pio_time(cur_pio_json, &pio_lt)) 232 continue; 233 234 length_json = json_object_object_get(cur_pio_json, JSON_LENGTH); 235 if (!length_json) 236 continue; 237 238 prefix_json = json_object_object_get(cur_pio_json, JSON_PREFIX); 239 if (!prefix_json) 240 continue; 241 242 pio_len = (uint8_t) json_object_get_uint64(length_json); 243 pio_str = json_object_get_string(prefix_json); 244 pio = &iface->pios[iface->pio_cnt]; 245 246 inet_pton(AF_INET6, pio_str, &pio->prefix); 247 pio->length = pio_len; 248 pio->lifetime = pio_lt; 249 info("rfc9096: %s: load %s/%u (%u)", 250 iface->ifname, 251 pio_str, 252 pio_len, 253 ra_pio_lifetime(pio, now)); 254 255 iface->pio_cnt++; 256 } 257 258 json_object_put(json); 259 260 if (!iface->pio_cnt) { 261 free(iface->pios); 262 iface->pios = NULL; 263 } else if (iface->pio_cnt != pio_cnt) { 264 struct ra_pio *tmp; 265 266 tmp = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt); 267 if (tmp) 268 iface->pios = tmp; 269 } 270 } 271 272 void statefiles_write_prefix_information(struct interface *iface) 273 { 274 struct json_object *json, *slaac_json; 275 char ipv6_str[INET6_ADDRSTRLEN]; 276 time_t now; 277 FILE *fp; 278 279 if (!statefiles_ra_pio_enabled(iface)) 280 return; 281 282 if (!iface->pio_update) 283 return; 284 285 fp = statefiles_open_tmp_file(config.ra_piodir_fd); 286 if (!fp) 287 return; 288 289 now = odhcpd_time(); 290 291 json = json_object_new_object(); 292 if (!json) 293 goto out; 294 295 slaac_json = json_object_new_array_ext(iface->pio_cnt); 296 if (!slaac_json) 297 goto out; 298 299 json_object_object_add(json, JSON_SLAAC, slaac_json); 300 301 for (size_t i = 0; i < iface->pio_cnt; i++) { 302 struct json_object *cur_pio_json, *len_json, *pfx_json; 303 const struct ra_pio *cur_pio = &iface->pios[i]; 304 305 if (ra_pio_expired(cur_pio, now)) 306 continue; 307 308 cur_pio_json = json_object_new_object(); 309 if (!cur_pio_json) 310 continue; 311 312 inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str)); 313 314 pfx_json = json_object_new_string(ipv6_str); 315 if (!pfx_json) { 316 json_object_put(cur_pio_json); 317 continue; 318 } 319 320 len_json = json_object_new_uint64(cur_pio->length); 321 if (!len_json) { 322 json_object_put(cur_pio_json); 323 json_object_put(pfx_json); 324 continue; 325 } 326 327 json_object_object_add(cur_pio_json, JSON_PREFIX, pfx_json); 328 json_object_object_add(cur_pio_json, JSON_LENGTH, len_json); 329 330 if (cur_pio->lifetime) { 331 struct json_object *time_json; 332 time_t pio_lt; 333 334 pio_lt = statefiles_time_to_json(cur_pio->lifetime); 335 336 time_json = json_object_new_int64(pio_lt); 337 if (time_json) 338 json_object_object_add(cur_pio_json, JSON_TIME, time_json); 339 } 340 341 json_object_array_add(slaac_json, cur_pio_json); 342 } 343 344 if (json_object_to_fd(fileno(fp), json, JSON_C_TO_STRING_PLAIN)) { 345 error("rfc9096: %s: json write error %s", 346 iface->ifname, 347 json_util_get_last_err()); 348 goto out; 349 } 350 351 statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, ODHCPD_PIO_FILE_PREFIX, iface->ifname); 352 iface->pio_update = false; 353 warn("rfc9096: %s: piofile updated", iface->ifname); 354 355 out: 356 json_object_put(json); 357 statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, NULL, NULL); 358 } 359 360 static void statefiles_write_host(const char *ipbuf, const char *hostname, struct write_ctxt *ctxt) 361 { 362 char exp_dn[DNS_MAX_NAME_LEN]; 363 364 if (dn_expand(ctxt->iface->dns_search, ctxt->iface->dns_search + ctxt->iface->dns_search_len, 365 ctxt->iface->dns_search, exp_dn, sizeof(exp_dn)) > 0) 366 fprintf(ctxt->fp, "%s\t%s.%s\t%s\n", ipbuf, hostname, exp_dn, hostname); 367 else 368 fprintf(ctxt->fp, "%s\t%s\n", ipbuf, hostname); 369 } 370 371 static bool statefiles_write_host6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease, 372 struct in6_addr *addr) 373 { 374 char ipbuf[INET6_ADDRSTRLEN]; 375 376 if (!lease->hostname || !lease->hostname_valid || !(lease->flags & OAF_DHCPV6_NA)) 377 return false; 378 379 if (ctxt->fp) { 380 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 381 statefiles_write_host(ipbuf, lease->hostname, ctxt); 382 } 383 384 return true; 385 } 386 387 static void statefiles_write_host6_cb(struct dhcpv6_lease *lease, struct in6_addr *addr, _o_unused uint8_t prefix_len, 388 _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg) 389 { 390 struct write_ctxt *ctxt = (struct write_ctxt *)arg; 391 392 statefiles_write_host6(ctxt, lease, addr); 393 } 394 395 static bool statefiles_write_host4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease) 396 { 397 char ipbuf[INET_ADDRSTRLEN]; 398 399 if (!lease->hostname || !lease->hostname_valid) 400 return false; 401 402 if (ctxt->fp) { 403 inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf)); 404 statefiles_write_host(ipbuf, lease->hostname, ctxt); 405 } 406 407 return true; 408 } 409 410 static void statefiles_write_hosts(time_t now) 411 { 412 struct write_ctxt ctxt; 413 414 if (config.dhcp_hostsdir_fd < 0) 415 return; 416 417 avl_for_each_element(&interfaces, ctxt.iface, avl) { 418 if (ctxt.iface->ignore) 419 continue; 420 421 if (ctxt.iface->dhcpv4 != MODE_SERVER && ctxt.iface->dhcpv6 != MODE_SERVER) 422 continue; 423 424 ctxt.fp = statefiles_open_tmp_file(config.dhcp_hostsdir_fd); 425 if (!ctxt.fp) 426 continue; 427 428 if (ctxt.iface->dhcpv6 == MODE_SERVER) { 429 struct dhcpv6_lease *lease; 430 431 list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) { 432 if (!lease->bound) 433 continue; 434 435 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 436 continue; 437 438 odhcpd_enum_addr6(ctxt.iface, lease, now, 439 statefiles_write_host6_cb, &ctxt); 440 } 441 } 442 443 if (ctxt.iface->dhcpv4 == MODE_SERVER) { 444 struct dhcpv4_lease *lease; 445 446 avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) { 447 if (!lease->bound) 448 continue; 449 450 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 451 continue; 452 453 statefiles_write_host4(&ctxt, lease); 454 } 455 } 456 457 statefiles_finish_tmp_file(config.dhcp_hostsdir_fd, &ctxt.fp, 458 ODHCPD_HOSTS_FILE_PREFIX, ctxt.iface->name); 459 } 460 } 461 462 static void statefiles_write_state6_addr(struct dhcpv6_lease *lease, struct in6_addr *addr, uint8_t prefix_len, 463 _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg) 464 { 465 struct write_ctxt *ctxt = (struct write_ctxt *)arg; 466 char ipbuf[INET6_ADDRSTRLEN]; 467 468 if (lease->hostname && lease->hostname_valid && lease->flags & OAF_DHCPV6_NA) { 469 md5_hash(addr, sizeof(*addr), &ctxt->md5); 470 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5); 471 } 472 473 if (!ctxt->fp) 474 return; 475 476 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf)); 477 fprintf(ctxt->fp, " %s/%" PRIu8, ipbuf, prefix_len); 478 } 479 480 static void statefiles_write_state6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease) 481 { 482 char duidbuf[DUID_HEXSTRLEN]; 483 484 if (ctxt->fp) { 485 odhcpd_hexlify(duidbuf, lease->duid, lease->duid_len); 486 487 /* # <iface> <hexduid> <hexiaid> <hostname> <valid_until> <assigned_[host|subnet]_id> <pfx_length> [<addrs> ...] */ 488 fprintf(ctxt->fp, 489 "# %s %s %x %s%s %" PRId64 " %" PRIx64 " %" PRIu8, 490 ctxt->iface->ifname, duidbuf, ntohl(lease->iaid), 491 lease->hostname && !lease->hostname_valid ? "broken\\x20": "", 492 lease->hostname ? lease->hostname : "-", 493 (lease->valid_until > ctxt->now ? 494 (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) : 495 (INFINITE_VALID(lease->valid_until) ? -1 : 0)), 496 (lease->flags & OAF_DHCPV6_NA ? 497 lease->assigned_host_id : 498 (uint64_t)lease->assigned_subnet_id), 499 lease->length); 500 } 501 502 odhcpd_enum_addr6(ctxt->iface, lease, ctxt->now, statefiles_write_state6_addr, ctxt); 503 504 if (ctxt->fp) 505 putc('\n', ctxt->fp); 506 } 507 508 static void statefiles_write_state4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease) 509 { 510 char ipbuf[INET6_ADDRSTRLEN]; 511 512 if (lease->hostname && lease->hostname_valid) { 513 md5_hash(&lease->ipv4, sizeof(lease->ipv4), &ctxt->md5); 514 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5); 515 } 516 517 if (!ctxt->fp) 518 return; 519 520 inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf)); 521 522 /* # <iface> <hexhwaddr> "ipv4" <hostname> <valid_until> <hexaddr> "32" <addrstr>"/32" */ 523 fprintf(ctxt->fp, 524 "# %s %s ipv4 %s%s %" PRId64 " %x 32 %s/32\n", 525 ctxt->iface->ifname, 526 ether_ntoa((struct ether_addr *)lease->hwaddr), 527 lease->hostname && !lease->hostname_valid ? "broken\\x20" : "", 528 lease->hostname ? lease->hostname : "-", 529 (lease->valid_until > ctxt->now ? 530 (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) : 531 (INFINITE_VALID(lease->valid_until) ? -1 : 0)), 532 ntohl(lease->ipv4.s_addr), ipbuf); 533 } 534 535 /* Returns true if there are changes to be written to the hosts file(s) */ 536 static bool statefiles_write_state(time_t now) 537 { 538 struct write_ctxt ctxt = { 539 .fp = NULL, 540 .now = now, 541 .wall_time = time(NULL), 542 }; 543 uint8_t newmd5[16]; 544 545 /* Return value unchecked, continue in order to get the md5 */ 546 ctxt.fp = statefiles_open_tmp_file(config.dhcp_statedir_fd); 547 548 md5_begin(&ctxt.md5); 549 550 avl_for_each_element(&interfaces, ctxt.iface, avl) { 551 if (ctxt.iface->dhcpv6 == MODE_SERVER) { 552 struct dhcpv6_lease *lease; 553 554 list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) { 555 if (!lease->bound) 556 continue; 557 558 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 559 continue; 560 561 statefiles_write_state6(&ctxt, lease); 562 } 563 } 564 565 if (ctxt.iface->dhcpv4 == MODE_SERVER) { 566 struct dhcpv4_lease *lease; 567 568 avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) { 569 if (!lease->bound) 570 continue; 571 572 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now) 573 continue; 574 575 statefiles_write_state4(&ctxt, lease); 576 } 577 } 578 } 579 580 statefiles_finish_tmp_file(config.dhcp_statedir_fd, &ctxt.fp, config.dhcp_statefile, NULL); 581 582 md5_end(newmd5, &ctxt.md5); 583 if (!memcmp(newmd5, statemd5, sizeof(newmd5))) 584 return false; 585 586 memcpy(statemd5, newmd5, sizeof(statemd5)); 587 return true; 588 } 589 590 bool statefiles_write() 591 { 592 time_t now = odhcpd_time(); 593 594 if (!statefiles_write_state(now)) 595 return false; 596 597 statefiles_write_hosts(now); 598 599 if (config.dhcp_cb) { 600 char *argv[2] = { config.dhcp_cb, NULL }; 601 pid_t pid; 602 603 posix_spawn(&pid, argv[0], NULL, NULL, argv, environ); 604 } 605 606 return true; 607 } 608 609 void statefiles_setup_dirfd(const char *path, int *dirfd) 610 { 611 if (!dirfd) 612 return; 613 614 if (*dirfd >= 0) { 615 close(*dirfd); 616 *dirfd = -1; 617 } 618 619 if (!path) 620 return; 621 622 mkdir_p(strdupa(path), 0755); 623 624 *dirfd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC); 625 if (*dirfd < 0) 626 error("Unable to open directory '%s': %m", path); 627 } 628
This page was automatically generated by LXR 0.3.1. • OpenWrt