1 /* $OpenBSD: dhcrelay.c,v 1.67 2024/08/21 10:35:12 florian Exp $ */ 2 3 /* 4 * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org> 5 * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of The Internet Software Consortium nor the names 18 * of its contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 22 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 24 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 26 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 29 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 30 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * This software has been written for the Internet Software Consortium 36 * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 37 * Enterprises. To learn more about the Internet Software Consortium, 38 * see ``http://www.vix.com/isc''. To learn more about Vixie 39 * Enterprises, see ``http://www.vix.com''. 40 */ 41 42 #include <sys/types.h> 43 #include <sys/ioctl.h> 44 #include <sys/socket.h> 45 46 #include <arpa/inet.h> 47 48 #include <net/if.h> 49 50 #include <errno.h> 51 #include <fcntl.h> 52 #include <netdb.h> 53 #include <paths.h> 54 #include <pwd.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <syslog.h> 59 #include <time.h> 60 #include <unistd.h> 61 62 #include "dhcp.h" 63 #include "dhcpd.h" 64 #include "log.h" 65 66 void usage(void); 67 int rdaemon(int); 68 void relay(struct interface_info *, struct dhcp_packet *, int, 69 struct packet_ctx *); 70 void l2relay(struct interface_info *, struct dhcp_packet *, int, 71 struct packet_ctx *); 72 char *print_hw_addr(int, int, unsigned char *); 73 void got_response(struct protocol *); 74 int get_rdomain(char *); 75 76 void relay_agentinfo(struct packet_ctx *, struct interface_info *, int); 77 78 int relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *, int); 79 ssize_t relay_agentinfo_append(struct packet_ctx *, struct dhcp_packet *, 80 size_t); 81 ssize_t relay_agentinfo_remove(struct packet_ctx *, struct dhcp_packet *, 82 size_t); 83 84 time_t cur_time; 85 86 struct interface_info *interfaces = NULL; 87 struct server_list *servers; 88 struct iflist intflist; 89 int server_fd; 90 int oflag; 91 92 enum dhcp_relay_mode drm = DRM_UNKNOWN; 93 const char *rai_circuit = NULL; 94 const char *rai_remote = NULL; 95 int rai_replace = 0; 96 97 int 98 main(int argc, char *argv[]) 99 { 100 int ch, devnull = -1, daemonize, opt, rdomain; 101 struct server_list *sp = NULL; 102 struct passwd *pw; 103 struct sockaddr_in laddr; 104 int optslen; 105 106 daemonize = 1; 107 108 log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ 109 110 setup_iflist(); 111 112 while ((ch = getopt(argc, argv, "aC:di:oR:r")) != -1) { 113 switch (ch) { 114 case 'C': 115 rai_circuit = optarg; 116 break; 117 case 'd': 118 daemonize = 0; 119 break; 120 case 'i': 121 if (interfaces != NULL) 122 usage(); 123 124 interfaces = iflist_getbyname(optarg); 125 if (interfaces == NULL) 126 fatalx("interface '%s' not found", optarg); 127 break; 128 case 'o': 129 /* add the relay agent information option */ 130 oflag++; 131 break; 132 case 'R': 133 rai_remote = optarg; 134 break; 135 case 'r': 136 rai_replace = 1; 137 break; 138 139 default: 140 usage(); 141 /* not reached */ 142 } 143 } 144 145 argc -= optind; 146 argv += optind; 147 148 if (argc < 1) 149 usage(); 150 151 if (rai_remote != NULL && rai_circuit == NULL) 152 fatalx("you must specify a circuit-id with a remote-id"); 153 154 /* Validate that we have space for all suboptions. */ 155 if (rai_circuit != NULL) { 156 optslen = 2 + strlen(rai_circuit); 157 if (rai_remote != NULL) 158 optslen += 2 + strlen(rai_remote); 159 160 if (optslen > DHCP_OPTION_MAXLEN) 161 fatalx("relay agent information is too long"); 162 } 163 164 while (argc > 0) { 165 struct addrinfo hints, *res; 166 struct in_addr ia, *iap = NULL; 167 168 if ((sp = calloc(1, sizeof(*sp))) == NULL) 169 fatalx("calloc"); 170 171 memset(&hints, 0, sizeof(hints)); 172 hints.ai_family = AF_INET; 173 174 if ((sp->intf = register_interface(argv[0], got_one, 175 1)) != NULL) { 176 if (drm == DRM_LAYER3) 177 fatalx("don't mix interfaces with hosts"); 178 179 if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL) 180 fatalx("can't use IPsec with layer 2"); 181 182 sp->next = servers; 183 servers = sp; 184 185 drm = DRM_LAYER2; 186 argc--; 187 argv++; 188 continue; 189 } 190 191 if (getaddrinfo(argv[0], NULL, &hints, &res) != 0) 192 log_warnx("%s: host unknown", argv[0]); 193 else { 194 ia = ((struct sockaddr_in *)res->ai_addr)->sin_addr; 195 iap = &ia; 196 freeaddrinfo(res); 197 } 198 199 if (iap) { 200 if (drm == DRM_LAYER2) 201 fatalx("don't mix interfaces with hosts"); 202 203 drm = DRM_LAYER3; 204 sp->next = servers; 205 servers = sp; 206 memcpy(&ss2sin(&sp->to)->sin_addr, iap, sizeof(*iap)); 207 } else 208 free(sp); 209 210 argc--; 211 argv++; 212 } 213 214 if (daemonize) { 215 devnull = open(_PATH_DEVNULL, O_RDWR); 216 if (devnull == -1) 217 fatal("open(%s)", _PATH_DEVNULL); 218 } 219 220 if (interfaces == NULL || 221 register_interface(interfaces->name, got_one, 0) == NULL) 222 fatalx("no interface given"); 223 224 /* We need an address for running layer 3 mode. */ 225 if (drm == DRM_LAYER3 && 226 (interfaces->hw_address.htype != HTYPE_IPSEC_TUNNEL && 227 interfaces->primary_address.s_addr == 0)) 228 fatalx("interface '%s' does not have an address", 229 interfaces->name); 230 231 /* We need at least one server. */ 232 if (!sp) 233 usage(); 234 235 rdomain = get_rdomain(interfaces->name); 236 237 /* Enable the relay agent option by default for enc0 */ 238 if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) 239 oflag++; 240 241 bzero(&laddr, sizeof laddr); 242 laddr.sin_len = sizeof laddr; 243 laddr.sin_family = AF_INET; 244 laddr.sin_port = htons(SERVER_PORT); 245 laddr.sin_addr.s_addr = interfaces->primary_address.s_addr; 246 /* Set up the server sockaddrs. */ 247 for (sp = servers; sp; sp = sp->next) { 248 if (sp->intf != NULL) 249 break; 250 251 ss2sin(&sp->to)->sin_port = htons(SERVER_PORT); 252 ss2sin(&sp->to)->sin_family = AF_INET; 253 ss2sin(&sp->to)->sin_len = sizeof(struct sockaddr_in); 254 sp->fd = socket(AF_INET, SOCK_DGRAM, 0); 255 if (sp->fd == -1) 256 fatal("socket"); 257 opt = 1; 258 if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT, 259 &opt, sizeof(opt)) == -1) 260 fatal("setsockopt"); 261 if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain, 262 sizeof(rdomain)) == -1) 263 fatal("setsockopt"); 264 if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == 265 -1) 266 fatal("bind"); 267 if (connect(sp->fd, (struct sockaddr *)&sp->to, 268 sp->to.ss_len) == -1) 269 fatal("connect"); 270 add_protocol("server", sp->fd, got_response, sp); 271 } 272 273 /* Socket used to forward packets to the DHCP client */ 274 if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) { 275 laddr.sin_addr.s_addr = INADDR_ANY; 276 server_fd = socket(AF_INET, SOCK_DGRAM, 0); 277 if (server_fd == -1) 278 fatal("socket"); 279 opt = 1; 280 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, 281 &opt, sizeof(opt)) == -1) 282 fatal("setsockopt"); 283 if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain, 284 sizeof(rdomain)) == -1) 285 fatal("setsockopt"); 286 if (bind(server_fd, (struct sockaddr *)&laddr, 287 sizeof(laddr)) == -1) 288 fatal("bind"); 289 } 290 291 tzset(); 292 293 time(&cur_time); 294 if (drm == DRM_LAYER3) 295 bootp_packet_handler = relay; 296 else 297 bootp_packet_handler = l2relay; 298 299 if ((pw = getpwnam("_dhcp")) == NULL) 300 fatalx("user \"_dhcp\" not found"); 301 if (chroot(pw->pw_dir) == -1) 302 fatal("chroot"); 303 if (chdir("/") == -1) 304 fatal("chdir(\"/\")"); 305 if (setgroups(1, &pw->pw_gid) || 306 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 307 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 308 fatal("can't drop privileges"); 309 310 if (daemonize) { 311 if (rdaemon(devnull) == -1) 312 fatal("rdaemon"); 313 314 log_init(0, LOG_DAEMON); /* stop logging to stderr */ 315 } 316 317 if (pledge("stdio route", NULL) == -1) 318 fatalx("pledge"); 319 320 dispatch(); 321 /* not reached */ 322 323 exit(0); 324 } 325 326 void 327 relay(struct interface_info *ip, struct dhcp_packet *packet, int length, 328 struct packet_ctx *pc) 329 { 330 struct server_list *sp; 331 struct sockaddr_in to; 332 333 if (packet->hlen > sizeof packet->chaddr) { 334 log_info("Discarding packet with invalid hlen."); 335 return; 336 } 337 338 /* If it's a bootreply, forward it to the client. */ 339 if (packet->op == BOOTREPLY) { 340 /* Filter packet that were not meant for us. */ 341 if (packet->giaddr.s_addr != 342 interfaces->primary_address.s_addr) 343 return; 344 345 bzero(&to, sizeof(to)); 346 if (!(packet->flags & htons(BOOTP_BROADCAST))) { 347 to.sin_addr = packet->yiaddr; 348 to.sin_port = htons(CLIENT_PORT); 349 } else { 350 to.sin_addr.s_addr = htonl(INADDR_BROADCAST); 351 to.sin_port = htons(CLIENT_PORT); 352 } 353 to.sin_family = AF_INET; 354 to.sin_len = sizeof to; 355 *ss2sin(&pc->pc_dst) = to; 356 357 /* 358 * Set up the hardware destination address. If it's a reply 359 * with the BROADCAST flag set, we should send an L2 broad- 360 * cast as well. 361 */ 362 if (!(packet->flags & htons(BOOTP_BROADCAST))) { 363 pc->pc_hlen = packet->hlen; 364 if (pc->pc_hlen > CHADDR_SIZE) 365 pc->pc_hlen = CHADDR_SIZE; 366 memcpy(pc->pc_dmac, packet->chaddr, pc->pc_hlen); 367 pc->pc_htype = packet->htype; 368 } else { 369 memset(pc->pc_dmac, 0xff, sizeof(pc->pc_dmac)); 370 } 371 372 relay_agentinfo(pc, interfaces, packet->op); 373 if ((length = relay_agentinfo_remove(pc, packet, 374 length)) == -1) { 375 log_info("ignoring BOOTREPLY with invalid " 376 "relay agent information"); 377 return; 378 } 379 380 /* 381 * VMware PXE "ROMs" confuse the DHCP gateway address 382 * with the IP gateway address. This is a problem if your 383 * DHCP relay is running on something that's not your 384 * network gateway. 385 * 386 * It is purely informational from the relay to the client 387 * so we can safely clear it. 388 */ 389 packet->giaddr.s_addr = 0x0; 390 391 ss2sin(&pc->pc_src)->sin_addr = interfaces->primary_address; 392 if (send_packet(interfaces, packet, length, pc) != -1) 393 log_debug("forwarded BOOTREPLY for %s to %s", 394 print_hw_addr(packet->htype, packet->hlen, 395 packet->chaddr), inet_ntoa(to.sin_addr)); 396 return; 397 } 398 399 if (ip == NULL) { 400 log_info("ignoring non BOOTREPLY from server"); 401 return; 402 } 403 404 if (packet->hops > 16) { 405 log_info("ignoring BOOTREQUEST with hop count of %d", 406 packet->hops); 407 return; 408 } 409 packet->hops++; 410 411 /* 412 * Set the giaddr so the server can figure out what net it's 413 * from and so that we can later forward the response to the 414 * correct net. The RFC specifies that we have to keep the 415 * initial giaddr (in case we relay over multiple hops). 416 */ 417 if (!packet->giaddr.s_addr) 418 packet->giaddr = ip->primary_address; 419 420 relay_agentinfo(pc, interfaces, packet->op); 421 if ((length = relay_agentinfo_append(pc, packet, length)) == -1) { 422 log_info("ignoring BOOTREQUEST with invalid " 423 "relay agent information"); 424 return; 425 } 426 427 /* Otherwise, it's a BOOTREQUEST, so forward it to all the 428 servers. */ 429 for (sp = servers; sp; sp = sp->next) { 430 if (send(sp->fd, packet, length, 0) != -1) { 431 log_debug("forwarded BOOTREQUEST for %s to %s", 432 print_hw_addr(packet->htype, packet->hlen, 433 packet->chaddr), 434 inet_ntoa(ss2sin(&sp->to)->sin_addr)); 435 } 436 } 437 438 } 439 440 void 441 usage(void) 442 { 443 extern char *__progname; 444 445 fprintf(stderr, "usage: %s [-dor] [-C circuit-id] [-R remote-id] " 446 "-i interface\n\tdestination ...\n", 447 __progname); 448 exit(1); 449 } 450 451 int 452 rdaemon(int devnull) 453 { 454 if (devnull == -1) { 455 errno = EBADF; 456 return (-1); 457 } 458 if (fcntl(devnull, F_GETFL) == -1) 459 return (-1); 460 461 switch (fork()) { 462 case -1: 463 return (-1); 464 case 0: 465 break; 466 default: 467 _exit(0); 468 } 469 470 if (setsid() == -1) 471 return (-1); 472 473 (void)dup2(devnull, STDIN_FILENO); 474 (void)dup2(devnull, STDOUT_FILENO); 475 (void)dup2(devnull, STDERR_FILENO); 476 if (devnull > 2) 477 (void)close(devnull); 478 479 return (0); 480 } 481 482 char * 483 print_hw_addr(int htype, int hlen, unsigned char *data) 484 { 485 static char habuf[49]; 486 char *s = habuf; 487 int i, j, slen = sizeof(habuf); 488 489 if (htype == 0 || hlen == 0) { 490 bad: 491 strlcpy(habuf, "<null>", sizeof habuf); 492 return habuf; 493 } 494 495 for (i = 0; i < hlen; i++) { 496 j = snprintf(s, slen, "%02x", data[i]); 497 if (j <= 0 || j >= slen) 498 goto bad; 499 j = strlen (s); 500 s += j; 501 slen -= (j + 1); 502 *s++ = ':'; 503 } 504 *--s = '\0'; 505 return habuf; 506 } 507 508 void 509 got_response(struct protocol *l) 510 { 511 struct packet_ctx pc; 512 ssize_t result; 513 union { 514 /* 515 * Packet input buffer. Must be as large as largest 516 * possible MTU. 517 */ 518 unsigned char packbuf[4095]; 519 struct dhcp_packet packet; 520 } u; 521 struct server_list *sp = l->local; 522 523 memset(&u, DHO_END, sizeof(u)); 524 if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 && 525 errno != ECONNREFUSED) { 526 /* 527 * Ignore ECONNREFUSED as too many dhcp servers send a bogus 528 * icmp unreach for every request. 529 */ 530 log_warn("recv failed for %s", 531 inet_ntoa(ss2sin(&sp->to)->sin_addr)); 532 return; 533 } 534 if (result == -1 && errno == ECONNREFUSED) 535 return; 536 537 if (result == 0) 538 return; 539 540 if (result < BOOTP_MIN_LEN) { 541 log_info("Discarding packet with invalid size."); 542 return; 543 } 544 545 memset(&pc, 0, sizeof(pc)); 546 pc.pc_src.ss_family = AF_INET; 547 pc.pc_src.ss_len = sizeof(struct sockaddr_in); 548 memcpy(&ss2sin(&pc.pc_src)->sin_addr, &ss2sin(&sp->to)->sin_addr, 549 sizeof(ss2sin(&pc.pc_src)->sin_addr)); 550 ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT); 551 552 pc.pc_dst.ss_family = AF_INET; 553 pc.pc_dst.ss_len = sizeof(struct sockaddr_in); 554 ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT); 555 556 if (bootp_packet_handler) 557 (*bootp_packet_handler)(NULL, &u.packet, result, &pc); 558 } 559 560 void 561 relay_agentinfo(struct packet_ctx *pc, struct interface_info *intf, 562 int bootop) 563 { 564 static u_int8_t buf[8]; 565 struct sockaddr_in *sin; 566 567 if (oflag == 0) 568 return; 569 570 if (rai_remote != NULL) { 571 pc->pc_remote = rai_remote; 572 pc->pc_remotelen = strlen(rai_remote); 573 } else 574 pc->pc_remotelen = 0; 575 576 if (rai_circuit == NULL) { 577 buf[0] = (uint8_t)(intf->index << 8); 578 buf[1] = intf->index & 0xff; 579 pc->pc_circuit = buf; 580 pc->pc_circuitlen = 2; 581 582 if (rai_remote == NULL) { 583 if (bootop == BOOTREPLY) 584 sin = ss2sin(&pc->pc_dst); 585 else 586 sin = ss2sin(&pc->pc_src); 587 588 pc->pc_remote = 589 (uint8_t *)&sin->sin_addr; 590 pc->pc_remotelen = 591 sizeof(sin->sin_addr); 592 } 593 } else { 594 pc->pc_circuit = rai_circuit; 595 pc->pc_circuitlen = strlen(rai_circuit); 596 } 597 } 598 599 int 600 relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *p, int plen) 601 { 602 int len; 603 char buf[256]; 604 605 if (oflag == 0) 606 return (-1); 607 608 len = *(p + 1); 609 if (len > plen) 610 return (-1); 611 612 switch (*p) { 613 case RAI_CIRCUIT_ID: 614 if (pc->pc_circuit == NULL) 615 return (-1); 616 if (pc->pc_circuitlen != len) 617 return (-1); 618 619 memcpy(buf, p + DHCP_OPTION_HDR_LEN, len); 620 return (memcmp(pc->pc_circuit, buf, len)); 621 622 case RAI_REMOTE_ID: 623 if (pc->pc_remote == NULL) 624 return (-1); 625 if (pc->pc_remotelen != len) 626 return (-1); 627 628 memcpy(buf, p + DHCP_OPTION_HDR_LEN, len); 629 return (memcmp(pc->pc_remote, buf, len)); 630 631 default: 632 /* Unmatched type */ 633 log_info("unmatched relay info %d", *p); 634 return (0); 635 } 636 } 637 638 ssize_t 639 relay_agentinfo_append(struct packet_ctx *pc, struct dhcp_packet *dp, 640 size_t dplen) 641 { 642 uint8_t *p, *startp; 643 ssize_t newtotal = dplen; 644 int opttotal, optlen, i, hasinfo = 0; 645 int maxlen, neededlen; 646 647 /* Only append when enabled. */ 648 if (oflag == 0) 649 return (dplen); 650 651 startp = (uint8_t *)dp; 652 p = (uint8_t *)&dp->options; 653 if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) { 654 log_info("invalid dhcp options cookie"); 655 return (-1); 656 } 657 658 p += DHCP_OPTIONS_COOKIE_LEN; 659 opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN; 660 maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1; 661 if (maxlen < 1 || opttotal < 1) 662 return (dplen); 663 664 for (i = 0; i < opttotal && *p != DHO_END;) { 665 if (*p == DHO_PAD) 666 optlen = 1; 667 else 668 optlen = p[1] + DHCP_OPTION_HDR_LEN; 669 670 if ((i + optlen) > opttotal) { 671 log_info("truncated dhcp options"); 672 return (-1); 673 } 674 675 if (*p == DHO_RELAY_AGENT_INFORMATION) { 676 if (rai_replace) { 677 memmove(p, p + optlen, opttotal - i); 678 opttotal -= optlen; 679 optlen = 0; 680 } else 681 hasinfo = 1; 682 } 683 684 p += optlen; 685 i += optlen; 686 687 /* We reached the end, append the relay agent info. */ 688 if (i < opttotal && *p == DHO_END) { 689 /* We already have the Relay Agent Info, skip it. */ 690 if (hasinfo) 691 continue; 692 693 /* Calculate needed length to append new data. */ 694 neededlen = newtotal + DHCP_OPTION_HDR_LEN; 695 if (pc->pc_circuitlen > 0) 696 neededlen += DHCP_OPTION_HDR_LEN + 697 pc->pc_circuitlen; 698 if (pc->pc_remotelen > 0) 699 neededlen += DHCP_OPTION_HDR_LEN + 700 pc->pc_remotelen; 701 702 /* Save one byte for DHO_END. */ 703 neededlen += 1; 704 705 /* Check if we have space for the new options. */ 706 if (neededlen > maxlen) { 707 log_warnx("no space for relay agent info"); 708 return (newtotal); 709 } 710 711 /* New option header: 2 bytes. */ 712 newtotal += DHCP_OPTION_HDR_LEN; 713 714 *p++ = DHO_RELAY_AGENT_INFORMATION; 715 *p = 0; 716 if (pc->pc_circuitlen > 0) { 717 newtotal += DHCP_OPTION_HDR_LEN + 718 pc->pc_circuitlen; 719 *p = (*p) + DHCP_OPTION_HDR_LEN + 720 pc->pc_circuitlen; 721 } 722 723 if (pc->pc_remotelen > 0) { 724 newtotal += DHCP_OPTION_HDR_LEN + 725 pc->pc_remotelen; 726 *p = (*p) + DHCP_OPTION_HDR_LEN + 727 pc->pc_remotelen; 728 } 729 730 p++; 731 732 /* Sub-option circuit-id header plus value. */ 733 if (pc->pc_circuitlen > 0) { 734 *p++ = RAI_CIRCUIT_ID; 735 *p++ = pc->pc_circuitlen; 736 memcpy(p, pc->pc_circuit, pc->pc_circuitlen); 737 738 p += pc->pc_circuitlen; 739 } 740 741 /* Sub-option remote-id header plus value. */ 742 if (pc->pc_remotelen > 0) { 743 *p++ = RAI_REMOTE_ID; 744 *p++ = pc->pc_remotelen; 745 memcpy(p, pc->pc_remote, pc->pc_remotelen); 746 747 p += pc->pc_remotelen; 748 } 749 750 *p = DHO_END; 751 } 752 } 753 754 /* Zero the padding so we don't leak anything. */ 755 p++; 756 if (p < (startp + maxlen)) 757 memset(p, 0, (startp + maxlen) - p); 758 759 return (newtotal); 760 } 761 762 ssize_t 763 relay_agentinfo_remove(struct packet_ctx *pc, struct dhcp_packet *dp, 764 size_t dplen) 765 { 766 uint8_t *p, *np, *startp, *endp; 767 int opttotal, optleft; 768 int suboptlen, optlen, i; 769 int maxlen, remaining, matched = 0; 770 771 startp = (uint8_t *)dp; 772 p = (uint8_t *)&dp->options; 773 if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) { 774 log_info("invalid dhcp options cookie"); 775 return (-1); 776 } 777 778 maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1; 779 opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN; 780 optleft = opttotal; 781 782 p += DHCP_OPTIONS_COOKIE_LEN; 783 endp = p + opttotal; 784 785 for (i = 0; i < opttotal && *p != DHO_END;) { 786 if (*p == DHO_PAD) 787 optlen = 1; 788 else 789 optlen = p[1] + DHCP_OPTION_HDR_LEN; 790 791 if ((i + optlen) > opttotal) { 792 log_info("truncated dhcp options"); 793 return (-1); 794 } 795 796 if (*p == DHO_RELAY_AGENT_INFORMATION) { 797 /* Fast case: there is no next option. */ 798 np = p + optlen; 799 if (*np == DHO_END) { 800 *p = *np; 801 endp = p + 1; 802 /* Zero the padding so we don't leak data. */ 803 if (endp < (startp + maxlen)) 804 memset(endp, 0, 805 (startp + maxlen) - endp); 806 807 return (dplen); 808 } 809 810 remaining = optlen; 811 while (remaining > 0) { 812 suboptlen = *(p + 1); 813 remaining -= DHCP_OPTION_HDR_LEN + suboptlen; 814 815 matched = 1; 816 if (relay_agentinfo_cmp(pc, p, suboptlen) == 0) 817 continue; 818 819 matched = 0; 820 break; 821 } 822 /* It is not ours Relay Agent Info, don't remove it. */ 823 if (matched == 0) 824 break; 825 826 /* Move the other options on top of this one. */ 827 optleft -= optlen; 828 endp -= optlen; 829 830 /* Replace the old agent relay info. */ 831 memmove(p, dp, optleft); 832 833 endp++; 834 /* Zero the padding so we don't leak data. */ 835 if (endp < (startp + maxlen)) 836 memset(endp, 0, 837 (startp + maxlen) - endp); 838 839 return (endp - startp); 840 } 841 842 p += optlen; 843 i += optlen; 844 optleft -= optlen; 845 } 846 847 return (endp - startp); 848 } 849 850 int 851 get_rdomain(char *name) 852 { 853 int rv = 0, s; 854 struct ifreq ifr; 855 856 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 857 fatal("get_rdomain socket"); 858 859 bzero(&ifr, sizeof(ifr)); 860 strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); 861 if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1) 862 rv = ifr.ifr_rdomainid; 863 864 close(s); 865 return rv; 866 } 867 868 void 869 l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length, 870 struct packet_ctx *pc) 871 { 872 struct server_list *sp; 873 ssize_t dplen; 874 875 if (dp->hlen > sizeof(dp->chaddr)) { 876 log_info("Discarding packet with invalid hlen."); 877 return; 878 } 879 880 relay_agentinfo(pc, ip, dp->op); 881 882 switch (dp->op) { 883 case BOOTREQUEST: 884 /* Add the relay agent info asked by the user. */ 885 if ((dplen = relay_agentinfo_append(pc, dp, length)) == -1) 886 return; 887 888 /* 889 * Re-send the packet to every interface except the one 890 * it came in. 891 */ 892 for (sp = servers; sp != NULL; sp = sp->next) { 893 if (sp->intf == ip) 894 continue; 895 896 log_debug("forwarded BOOTREQUEST for %s to %s", 897 print_hw_addr(pc->pc_htype, pc->pc_hlen, 898 pc->pc_smac), sp->intf->name); 899 900 send_packet(sp->intf, dp, dplen, pc); 901 } 902 if (ip != interfaces) { 903 log_debug("forwarded BOOTREQUEST for %s to %s", 904 print_hw_addr(pc->pc_htype, pc->pc_hlen, 905 pc->pc_smac), interfaces->name); 906 907 send_packet(interfaces, dp, dplen, pc); 908 } 909 break; 910 911 case BOOTREPLY: 912 /* Remove relay agent info on offer. */ 913 if ((dplen = relay_agentinfo_remove(pc, dp, length)) == -1) 914 return; 915 916 if (ip != interfaces) { 917 log_debug("forwarded BOOTREPLY for %s to %s", 918 print_hw_addr(pc->pc_htype, pc->pc_hlen, 919 pc->pc_dmac), interfaces->name); 920 send_packet(interfaces, dp, dplen, pc); 921 } 922 break; 923 924 default: 925 log_debug("invalid operation type '%d'", dp->op); 926 return; 927 } 928 } 929