1 /* SPDX-License-Identifier: BSD-2-Clause */ 2 /* 3 * Privilege Separation for dhcpcd, network proxy 4 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name> 5 * All rights reserved 6 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/socket.h> 30 #include <sys/types.h> 31 32 #include <netinet/in.h> 33 #include <netinet/icmp6.h> 34 35 #include <assert.h> 36 #include <errno.h> 37 #include <signal.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include "arp.h" 43 #include "bpf.h" 44 #include "dhcp.h" 45 #include "dhcp6.h" 46 #include "eloop.h" 47 #include "ipv6nd.h" 48 #include "logerr.h" 49 #include "privsep.h" 50 51 #ifdef HAVE_CAPSICUM 52 #include <sys/capsicum.h> 53 #endif 54 55 #ifdef INET 56 static void 57 ps_inet_recvbootp(void *arg) 58 { 59 struct dhcpcd_ctx *ctx = arg; 60 61 if (ps_recvmsg(ctx, ctx->udp_rfd, PS_BOOTP, ctx->ps_inet_fd) == -1) 62 logerr(__func__); 63 } 64 #endif 65 66 #ifdef INET6 67 static void 68 ps_inet_recvra(void *arg) 69 { 70 #ifdef __sun 71 struct interface *ifp = arg; 72 struct rs_state *state = RS_STATE(ifp); 73 struct dhcpcd_ctx *ctx = ifp->ctx; 74 75 if (ps_recvmsg(ctx, state->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) 76 logerr(__func__); 77 #else 78 struct dhcpcd_ctx *ctx = arg; 79 80 if (ps_recvmsg(ctx, ctx->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) 81 logerr(__func__); 82 #endif 83 } 84 #endif 85 86 #ifdef DHCP6 87 static void 88 ps_inet_recvdhcp6(void *arg) 89 { 90 struct dhcpcd_ctx *ctx = arg; 91 92 if (ps_recvmsg(ctx, ctx->dhcp6_rfd, PS_DHCP6, ctx->ps_inet_fd) == -1) 93 logerr(__func__); 94 } 95 #endif 96 97 static int 98 ps_inet_startcb(void *arg) 99 { 100 struct dhcpcd_ctx *ctx = arg; 101 int ret = 0; 102 103 if (ctx->options & DHCPCD_MASTER) 104 setproctitle("[network proxy]"); 105 else 106 setproctitle("[network proxy] %s%s%s", 107 ctx->ifv[0], 108 ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", 109 ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); 110 111 /* This end is the main engine, so it's useless for us. */ 112 close(ctx->ps_data_fd); 113 ctx->ps_data_fd = -1; 114 115 errno = 0; 116 117 #ifdef INET 118 if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MASTER)) == 119 (DHCPCD_IPV4 | DHCPCD_MASTER)) 120 { 121 ctx->udp_rfd = dhcp_openudp(NULL); 122 if (ctx->udp_rfd == -1) 123 logerr("%s: dhcp_open", __func__); 124 #ifdef PRIVSEP_RIGHTS 125 else if (ps_rights_limit_fd_rdonly(ctx->udp_rfd) == -1) { 126 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 127 close(ctx->udp_rfd); 128 ctx->udp_rfd = -1; 129 } 130 #endif 131 else if (eloop_event_add(ctx->eloop, ctx->udp_rfd, 132 ps_inet_recvbootp, ctx) == -1) 133 { 134 logerr("%s: eloop_event_add DHCP", __func__); 135 close(ctx->udp_rfd); 136 ctx->udp_rfd = -1; 137 } else 138 ret++; 139 } 140 #endif 141 #if defined(INET6) && !defined(__sun) 142 if (ctx->options & DHCPCD_IPV6) { 143 ctx->nd_fd = ipv6nd_open(true); 144 if (ctx->nd_fd == -1) 145 logerr("%s: ipv6nd_open", __func__); 146 #ifdef PRIVSEP_RIGHTS 147 else if (ps_rights_limit_fd_rdonly(ctx->nd_fd) == -1) { 148 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 149 close(ctx->nd_fd); 150 ctx->nd_fd = -1; 151 } 152 #endif 153 else if (eloop_event_add(ctx->eloop, ctx->nd_fd, 154 ps_inet_recvra, ctx) == -1) 155 { 156 logerr("%s: eloop_event_add RA", __func__); 157 close(ctx->nd_fd); 158 ctx->nd_fd = -1; 159 } else 160 ret++; 161 } 162 #endif 163 #ifdef DHCP6 164 if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MASTER)) == 165 (DHCPCD_IPV6 | DHCPCD_MASTER)) 166 { 167 ctx->dhcp6_rfd = dhcp6_openudp(0, NULL); 168 if (ctx->dhcp6_rfd == -1) 169 logerr("%s: dhcp6_open", __func__); 170 #ifdef PRIVSEP_RIGHTS 171 else if (ps_rights_limit_fd_rdonly(ctx->dhcp6_rfd) == -1) { 172 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 173 close(ctx->dhcp6_rfd); 174 ctx->dhcp6_rfd = -1; 175 } 176 #endif 177 else if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, 178 ps_inet_recvdhcp6, ctx) == -1) 179 { 180 logerr("%s: eloop_event_add DHCP6", __func__); 181 close(ctx->dhcp6_rfd); 182 ctx->dhcp6_rfd = -1; 183 } else 184 ret++; 185 } 186 #endif 187 188 if (ret == 0 && errno == 0) { 189 errno = ENXIO; 190 return -1; 191 } 192 return ret; 193 } 194 195 static bool 196 ps_inet_validudp(struct msghdr *msg, uint16_t sport, uint16_t dport) 197 { 198 struct udphdr udp; 199 struct iovec *iov = msg->msg_iov; 200 201 if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(udp)) { 202 errno = EINVAL; 203 return false; 204 } 205 206 memcpy(&udp, iov->iov_base, sizeof(udp)); 207 if (udp.uh_sport != htons(sport) || udp.uh_dport != htons(dport)) { 208 errno = EPERM; 209 return false; 210 } 211 return true; 212 } 213 214 #ifdef INET6 215 static bool 216 ps_inet_validnd(struct msghdr *msg) 217 { 218 struct icmp6_hdr icmp6; 219 struct iovec *iov = msg->msg_iov; 220 221 if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(icmp6)) { 222 errno = EINVAL; 223 return false; 224 } 225 226 memcpy(&icmp6, iov->iov_base, sizeof(icmp6)); 227 switch(icmp6.icmp6_type) { 228 case ND_ROUTER_SOLICIT: 229 case ND_NEIGHBOR_ADVERT: 230 break; 231 default: 232 errno = EPERM; 233 return false; 234 } 235 236 return true; 237 } 238 #endif 239 240 static ssize_t 241 ps_inet_sendmsg(struct dhcpcd_ctx *ctx, 242 struct ps_msghdr *psm, struct msghdr *msg) 243 { 244 struct ps_process *psp; 245 int s; 246 247 psp = ps_findprocess(ctx, &psm->ps_id); 248 if (psp != NULL) { 249 s = psp->psp_work_fd; 250 goto dosend; 251 } 252 253 switch (psm->ps_cmd) { 254 #ifdef INET 255 case PS_BOOTP: 256 if (!ps_inet_validudp(msg, BOOTPC, BOOTPS)) 257 return -1; 258 s = ctx->udp_wfd; 259 break; 260 #endif 261 #if defined(INET6) && !defined(__sun) 262 case PS_ND: 263 if (!ps_inet_validnd(msg)) 264 return -1; 265 s = ctx->nd_fd; 266 break; 267 #endif 268 #ifdef DHCP6 269 case PS_DHCP6: 270 if (!ps_inet_validudp(msg, DHCP6_CLIENT_PORT,DHCP6_SERVER_PORT)) 271 return -1; 272 s = ctx->dhcp6_wfd; 273 break; 274 #endif 275 default: 276 errno = EINVAL; 277 return -1; 278 } 279 280 dosend: 281 return sendmsg(s, msg, 0); 282 } 283 284 static void 285 ps_inet_recvmsg(void *arg) 286 { 287 struct dhcpcd_ctx *ctx = arg; 288 289 /* Receive shutdown */ 290 if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, NULL, NULL) == -1) 291 logerr(__func__); 292 } 293 294 static void 295 ps_inet_signalcb(int sig, void *arg) 296 { 297 struct dhcpcd_ctx *ctx = arg; 298 299 if (sig != SIGTERM) 300 return; 301 302 shutdown(ctx->ps_inet_fd, SHUT_RDWR); 303 eloop_exit(ctx->eloop, EXIT_SUCCESS); 304 } 305 306 ssize_t 307 ps_inet_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) 308 { 309 struct dhcpcd_ctx *ctx = arg; 310 311 switch (psm->ps_cmd) { 312 #ifdef INET 313 case PS_BOOTP: 314 dhcp_recvmsg(ctx, msg); 315 break; 316 #endif 317 #ifdef INET6 318 case PS_ND: 319 ipv6nd_recvmsg(ctx, msg); 320 break; 321 #endif 322 #ifdef DHCP6 323 case PS_DHCP6: 324 dhcp6_recvmsg(ctx, msg, NULL); 325 break; 326 #endif 327 default: 328 errno = ENOTSUP; 329 return -1; 330 } 331 return 1; 332 } 333 334 static void 335 ps_inet_dodispatch(void *arg) 336 { 337 struct dhcpcd_ctx *ctx = arg; 338 339 if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, ps_inet_dispatch, ctx) == -1) 340 logerr(__func__); 341 } 342 343 pid_t 344 ps_inet_start(struct dhcpcd_ctx *ctx) 345 { 346 pid_t pid; 347 348 pid = ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd, 349 ps_inet_recvmsg, ps_inet_dodispatch, ctx, 350 ps_inet_startcb, ps_inet_signalcb, 351 PSF_DROPPRIVS); 352 353 #ifdef HAVE_CAPSICUM 354 if (pid == 0 && cap_enter() == -1 && errno != ENOSYS) 355 logerr("%s: cap_enter", __func__); 356 #endif 357 #ifdef HAVE_PLEDGE 358 if (pid == 0 && pledge("stdio", NULL) == -1) 359 logerr("%s: pledge", __func__); 360 #endif 361 362 return pid; 363 } 364 365 int 366 ps_inet_stop(struct dhcpcd_ctx *ctx) 367 { 368 369 return ps_dostop(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd); 370 } 371 372 #ifdef INET 373 static void 374 ps_inet_recvinbootp(void *arg) 375 { 376 struct ps_process *psp = arg; 377 378 if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, 379 PS_BOOTP, psp->psp_ctx->ps_data_fd) == -1) 380 logerr(__func__); 381 } 382 383 static int 384 ps_inet_listenin(void *arg) 385 { 386 struct ps_process *psp = arg; 387 struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; 388 char buf[INET_ADDRSTRLEN]; 389 390 inet_ntop(AF_INET, ia, buf, sizeof(buf)); 391 setproctitle("[network proxy] %s", buf); 392 393 psp->psp_work_fd = dhcp_openudp(ia); 394 if (psp->psp_work_fd == -1) { 395 logerr(__func__); 396 return -1; 397 } 398 399 #ifdef PRIVSEP_RIGHTS 400 if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { 401 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 402 return -1; 403 } 404 #endif 405 406 if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, 407 ps_inet_recvinbootp, psp) == -1) 408 { 409 logerr("%s: eloop_event_add DHCP", __func__); 410 return -1; 411 } 412 413 logdebugx("spawned listener %s on PID %d", buf, getpid()); 414 return 0; 415 } 416 #endif 417 418 #if defined(INET6) && defined(__sun) 419 static void 420 ps_inet_recvin6nd(void *arg) 421 { 422 struct ps_process *psp = arg; 423 424 if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, 425 PS_ND, psp->psp_ctx->ps_data_fd) == -1) 426 logerr(__func__); 427 } 428 429 static int 430 ps_inet_listennd(void *arg) 431 { 432 struct ps_process *psp = arg; 433 434 setproctitle("[ND network proxy]"); 435 436 psp->psp_work_fd = ipv6nd_open(&psp->psp_ifp); 437 if (psp->psp_work_fd == -1) { 438 logerr(__func__); 439 return -1; 440 } 441 442 #ifdef PRIVSEP_RIGHTS 443 if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { 444 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 445 return -1; 446 } 447 #endif 448 449 if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, 450 ps_inet_recvin6nd, psp) == -1) 451 { 452 logerr(__func__); 453 return -1; 454 } 455 456 logdebugx("spawned ND listener on PID %d", getpid()); 457 return 0; 458 } 459 #endif 460 461 #ifdef DHCP6 462 static void 463 ps_inet_recvin6dhcp6(void *arg) 464 { 465 struct ps_process *psp = arg; 466 467 if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, 468 PS_DHCP6, psp->psp_ctx->ps_data_fd) == -1) 469 logerr(__func__); 470 } 471 472 static int 473 ps_inet_listenin6(void *arg) 474 { 475 struct ps_process *psp = arg; 476 struct in6_addr *ia = &psp->psp_id.psi_addr.psa_in6_addr; 477 char buf[INET6_ADDRSTRLEN]; 478 479 inet_ntop(AF_INET6, ia, buf, sizeof(buf)); 480 setproctitle("[network proxy] %s", buf); 481 482 psp->psp_work_fd = dhcp6_openudp(psp->psp_id.psi_ifindex, ia); 483 if (psp->psp_work_fd == -1) { 484 logerr(__func__); 485 return -1; 486 } 487 488 #ifdef PRIVSEP_RIGHTS 489 if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { 490 logerr("%s: ps_rights_limit_fd_rdonly", __func__); 491 return -1; 492 } 493 #endif 494 495 if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, 496 ps_inet_recvin6dhcp6, psp) == -1) 497 { 498 logerr("%s: eloop_event_add DHCP", __func__); 499 return -1; 500 } 501 502 logdebugx("spawned listener %s on PID %d", buf, getpid()); 503 return 0; 504 } 505 #endif 506 507 static void 508 ps_inet_recvmsgpsp(void *arg) 509 { 510 struct ps_process *psp = arg; 511 512 /* Receive shutdown. */ 513 if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, NULL, NULL) == -1) 514 logerr(__func__); 515 } 516 517 ssize_t 518 ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) 519 { 520 uint16_t cmd; 521 struct ps_process *psp; 522 int (*start_func)(void *); 523 pid_t start; 524 525 cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); 526 if (cmd == psm->ps_cmd) 527 return ps_inet_sendmsg(ctx, psm, msg); 528 529 psp = ps_findprocess(ctx, &psm->ps_id); 530 531 #ifdef PRIVSEP_DEBUG 532 logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); 533 #endif 534 535 if (psm->ps_cmd & PS_STOP) { 536 assert(psp == NULL); 537 return 0; 538 } 539 540 switch (cmd) { 541 #ifdef INET 542 case PS_BOOTP: 543 start_func = ps_inet_listenin; 544 break; 545 #endif 546 #ifdef INET6 547 #ifdef __sun 548 case PS_ND: 549 start_func = ps_inet_listennd; 550 break; 551 #endif 552 #ifdef DHCP6 553 case PS_DHCP6: 554 start_func = ps_inet_listenin6; 555 break; 556 #endif 557 #endif 558 default: 559 logerrx("%s: unknown command %x", __func__, psm->ps_cmd); 560 errno = ENOTSUP; 561 return -1; 562 } 563 564 if (!(psm->ps_cmd & PS_START)) { 565 errno = EINVAL; 566 return -1; 567 } 568 569 if (psp != NULL) 570 return 1; 571 572 psp = ps_newprocess(ctx, &psm->ps_id); 573 if (psp == NULL) 574 return -1; 575 576 start = ps_dostart(ctx, 577 &psp->psp_pid, &psp->psp_fd, 578 ps_inet_recvmsgpsp, NULL, psp, 579 start_func, ps_inet_signalcb, 580 PSF_DROPPRIVS); 581 switch (start) { 582 case -1: 583 ps_freeprocess(psp); 584 return -1; 585 case 0: 586 #ifdef HAVE_CAPSICUM 587 if (cap_enter() == -1 && errno != ENOSYS) 588 logerr("%s: cap_enter", __func__); 589 #endif 590 #ifdef HAVE_PLEDGE 591 if (pledge("stdio", NULL) == -1) 592 logerr("%s: pledge", __func__); 593 #endif 594 break; 595 default: 596 break; 597 } 598 return start; 599 } 600 601 #ifdef INET 602 static ssize_t 603 ps_inet_in_docmd(struct ipv4_addr *ia, uint16_t cmd, const struct msghdr *msg) 604 { 605 assert(ia != NULL); 606 struct dhcpcd_ctx *ctx = ia->iface->ctx; 607 struct ps_msghdr psm = { 608 .ps_cmd = cmd, 609 .ps_id = { 610 .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), 611 .psi_ifindex = ia->iface->index, 612 .psi_addr.psa_in_addr = ia->addr, 613 }, 614 }; 615 616 return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); 617 } 618 619 ssize_t 620 ps_inet_openbootp(struct ipv4_addr *ia) 621 { 622 623 return ps_inet_in_docmd(ia, PS_START | PS_BOOTP, NULL); 624 } 625 626 ssize_t 627 ps_inet_closebootp(struct ipv4_addr *ia) 628 { 629 630 return ps_inet_in_docmd(ia, PS_STOP | PS_BOOTP, NULL); 631 } 632 633 ssize_t 634 ps_inet_sendbootp(struct interface *ifp, const struct msghdr *msg) 635 { 636 637 return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_BOOTP, 0, msg); 638 } 639 #endif /* INET */ 640 641 #ifdef INET6 642 #ifdef __sun 643 static ssize_t 644 ps_inet_ifp_docmd(struct interface *ifp, uint16_t cmd, const struct msghdr *msg) 645 { 646 struct dhcpcd_ctx *ctx = ifp->ctx; 647 struct ps_msghdr psm = { 648 .ps_cmd = cmd, 649 .ps_id = { 650 .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), 651 .psi_ifindex = ifp->index, 652 }, 653 }; 654 655 return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); 656 } 657 658 ssize_t 659 ps_inet_opennd(struct interface *ifp) 660 { 661 662 return ps_inet_ifp_docmd(ifp, PS_ND | PS_START, NULL); 663 } 664 665 ssize_t 666 ps_inet_closend(struct interface *ifp) 667 { 668 669 return ps_inet_ifp_docmd(ifp, PS_ND | PS_STOP, NULL); 670 } 671 672 ssize_t 673 ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) 674 { 675 676 return ps_inet_ifp_docmd(ifp, PS_ND, msg); 677 } 678 #else 679 ssize_t 680 ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) 681 { 682 683 return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_ND, 0, msg); 684 } 685 #endif 686 687 #ifdef DHCP6 688 static ssize_t 689 ps_inet_in6_docmd(struct ipv6_addr *ia, uint16_t cmd, const struct msghdr *msg) 690 { 691 struct dhcpcd_ctx *ctx = ia->iface->ctx; 692 struct ps_msghdr psm = { 693 .ps_cmd = cmd, 694 .ps_id = { 695 .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), 696 .psi_ifindex = ia->iface->index, 697 .psi_addr.psa_in6_addr = ia->addr, 698 }, 699 }; 700 701 return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); 702 } 703 704 ssize_t 705 ps_inet_opendhcp6(struct ipv6_addr *ia) 706 { 707 708 return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_START, NULL); 709 } 710 711 ssize_t 712 ps_inet_closedhcp6(struct ipv6_addr *ia) 713 { 714 715 return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_STOP, NULL); 716 } 717 718 ssize_t 719 ps_inet_senddhcp6(struct interface *ifp, const struct msghdr *msg) 720 { 721 722 return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_DHCP6, 0, msg); 723 } 724 #endif /* DHCP6 */ 725 #endif /* INET6 */ 726