1 /* $OpenBSD: dhcrelay.c,v 1.42 2016/09/15 16:16:03 jca 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 65 void usage(void); 66 int rdaemon(int); 67 void relay(struct interface_info *, struct dhcp_packet *, int, 68 unsigned int, struct iaddr, struct hardware *); 69 char *print_hw_addr(int, int, unsigned char *); 70 void got_response(struct protocol *); 71 int get_rdomain(char *); 72 73 ssize_t relay_agentinfo(struct interface_info *, struct dhcp_packet *, 74 size_t, struct in_addr *, struct in_addr *); 75 76 time_t cur_time; 77 78 int log_perror = 1; 79 80 u_int16_t server_port; 81 u_int16_t client_port; 82 int log_priority; 83 struct interface_info *interfaces = NULL; 84 int server_fd; 85 int oflag; 86 87 struct server_list { 88 struct server_list *next; 89 struct sockaddr_in to; 90 int fd; 91 } *servers; 92 93 int 94 main(int argc, char *argv[]) 95 { 96 int ch, devnull = -1, daemonize, opt, rdomain; 97 extern char *__progname; 98 struct server_list *sp = NULL; 99 struct passwd *pw; 100 struct sockaddr_in laddr; 101 102 daemonize = 1; 103 104 /* Initially, log errors to stderr as well as to syslogd. */ 105 openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY); 106 setlogmask(LOG_UPTO(LOG_INFO)); 107 108 while ((ch = getopt(argc, argv, "adi:o")) != -1) { 109 switch (ch) { 110 case 'd': 111 daemonize = 0; 112 break; 113 case 'i': 114 if (interfaces != NULL) 115 usage(); 116 if ((interfaces = calloc(1, 117 sizeof(struct interface_info))) == NULL) 118 error("calloc"); 119 strlcpy(interfaces->name, optarg, 120 sizeof(interfaces->name)); 121 break; 122 case 'o': 123 /* add the relay agent information option */ 124 oflag++; 125 break; 126 127 default: 128 usage(); 129 /* not reached */ 130 } 131 } 132 133 argc -= optind; 134 argv += optind; 135 136 if (argc < 1) 137 usage(); 138 139 while (argc > 0) { 140 struct hostent *he; 141 struct in_addr ia, *iap = NULL; 142 143 if (inet_aton(argv[0], &ia)) 144 iap = &ia; 145 else { 146 he = gethostbyname(argv[0]); 147 if (!he) 148 warning("%s: host unknown", argv[0]); 149 else 150 iap = ((struct in_addr *)he->h_addr_list[0]); 151 } 152 if (iap) { 153 if ((sp = calloc(1, sizeof *sp)) == NULL) 154 error("calloc"); 155 sp->next = servers; 156 servers = sp; 157 memcpy(&sp->to.sin_addr, iap, sizeof *iap); 158 } 159 argc--; 160 argv++; 161 } 162 163 if (daemonize) { 164 devnull = open(_PATH_DEVNULL, O_RDWR, 0); 165 if (devnull == -1) 166 error("open(%s): %m", _PATH_DEVNULL); 167 } 168 169 if (interfaces == NULL) 170 error("no interface given"); 171 172 /* Default DHCP/BOOTP ports. */ 173 server_port = htons(SERVER_PORT); 174 client_port = htons(CLIENT_PORT); 175 176 /* We need at least one server. */ 177 if (!sp) 178 usage(); 179 180 discover_interfaces(interfaces); 181 182 rdomain = get_rdomain(interfaces->name); 183 184 /* Enable the relay agent option by default for enc0 */ 185 if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) 186 oflag++; 187 188 bzero(&laddr, sizeof laddr); 189 laddr.sin_len = sizeof laddr; 190 laddr.sin_family = AF_INET; 191 laddr.sin_port = server_port; 192 laddr.sin_addr.s_addr = interfaces->primary_address.s_addr; 193 /* Set up the server sockaddrs. */ 194 for (sp = servers; sp; sp = sp->next) { 195 sp->to.sin_port = server_port; 196 sp->to.sin_family = AF_INET; 197 sp->to.sin_len = sizeof sp->to; 198 sp->fd = socket(AF_INET, SOCK_DGRAM, 0); 199 if (sp->fd == -1) 200 error("socket: %m"); 201 opt = 1; 202 if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT, 203 &opt, sizeof(opt)) == -1) 204 error("setsockopt: %m"); 205 if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain, 206 sizeof(rdomain)) == -1) 207 error("setsockopt: %m"); 208 if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == -1) 209 error("bind: %m"); 210 if (connect(sp->fd, (struct sockaddr *)&sp->to, 211 sizeof sp->to) == -1) 212 error("connect: %m"); 213 add_protocol("server", sp->fd, got_response, sp); 214 } 215 216 /* Socket used to forward packets to the DHCP client */ 217 if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) { 218 laddr.sin_addr.s_addr = INADDR_ANY; 219 server_fd = socket(AF_INET, SOCK_DGRAM, 0); 220 if (server_fd == -1) 221 error("socket: %m"); 222 opt = 1; 223 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, 224 &opt, sizeof(opt)) == -1) 225 error("setsockopt: %m"); 226 if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain, 227 sizeof(rdomain)) == -1) 228 error("setsockopt: %m"); 229 if (bind(server_fd, (struct sockaddr *)&laddr, 230 sizeof(laddr)) == -1) 231 error("bind: %m"); 232 } 233 234 tzset(); 235 236 time(&cur_time); 237 bootp_packet_handler = relay; 238 239 if ((pw = getpwnam("_dhcp")) == NULL) 240 error("user \"_dhcp\" not found"); 241 if (chroot(_PATH_VAREMPTY) == -1) 242 error("chroot: %m"); 243 if (chdir("/") == -1) 244 error("chdir(\"/\"): %m"); 245 if (setgroups(1, &pw->pw_gid) || 246 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 247 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 248 error("can't drop privileges: %m"); 249 250 if (daemonize) { 251 if (rdaemon(devnull) == -1) 252 error("rdaemon: %m"); 253 log_perror = 0; 254 } 255 256 dispatch(); 257 /* not reached */ 258 259 exit(0); 260 } 261 262 void 263 relay(struct interface_info *ip, struct dhcp_packet *packet, int length, 264 unsigned int from_port, struct iaddr from, struct hardware *hfrom) 265 { 266 struct server_list *sp; 267 struct sockaddr_in to; 268 struct hardware hto; 269 270 if (packet->hlen > sizeof packet->chaddr) { 271 note("Discarding packet with invalid hlen."); 272 return; 273 } 274 275 /* If it's a bootreply, forward it to the client. */ 276 if (packet->op == BOOTREPLY) { 277 bzero(&to, sizeof(to)); 278 if (!(packet->flags & htons(BOOTP_BROADCAST))) { 279 to.sin_addr = packet->yiaddr; 280 to.sin_port = client_port; 281 } else { 282 to.sin_addr.s_addr = htonl(INADDR_BROADCAST); 283 to.sin_port = client_port; 284 } 285 to.sin_family = AF_INET; 286 to.sin_len = sizeof to; 287 288 /* Set up the hardware destination address. */ 289 hto.hlen = packet->hlen; 290 if (hto.hlen > sizeof hto.haddr) 291 hto.hlen = sizeof hto.haddr; 292 memcpy(hto.haddr, packet->chaddr, hto.hlen); 293 hto.htype = packet->htype; 294 295 if ((length = relay_agentinfo(interfaces, 296 packet, length, NULL, &to.sin_addr)) == -1) { 297 note("ignoring BOOTREPLY with invalid " 298 "relay agent information"); 299 return; 300 } 301 302 /* 303 * VMware PXE "ROMs" confuse the DHCP gateway address 304 * with the IP gateway address. This is a problem if your 305 * DHCP relay is running on something that's not your 306 * network gateway. 307 * 308 * It is purely informational from the relay to the client 309 * so we can safely clear it. 310 */ 311 packet->giaddr.s_addr = 0x0; 312 313 if (send_packet(interfaces, packet, length, 314 interfaces->primary_address, &to, &hto) != -1) 315 debug("forwarded BOOTREPLY for %s to %s", 316 print_hw_addr(packet->htype, packet->hlen, 317 packet->chaddr), inet_ntoa(to.sin_addr)); 318 return; 319 } 320 321 if (ip == NULL) { 322 note("ignoring non BOOTREPLY from server"); 323 return; 324 } 325 326 /* If giaddr is set on a BOOTREQUEST, ignore it - it's already 327 been gatewayed. */ 328 if (packet->giaddr.s_addr) { 329 note("ignoring BOOTREQUEST with giaddr of %s", 330 inet_ntoa(packet->giaddr)); 331 return; 332 } 333 334 /* Set the giaddr so the server can figure out what net it's 335 from and so that we can later forward the response to the 336 correct net. */ 337 packet->giaddr = ip->primary_address; 338 339 if ((length = relay_agentinfo(ip, packet, length, 340 (struct in_addr *)from.iabuf, NULL)) == -1) { 341 note("ignoring BOOTREQUEST with invalid " 342 "relay agent information"); 343 return; 344 } 345 346 /* Otherwise, it's a BOOTREQUEST, so forward it to all the 347 servers. */ 348 for (sp = servers; sp; sp = sp->next) { 349 if (send(sp->fd, packet, length, 0) != -1) { 350 debug("forwarded BOOTREQUEST for %s to %s", 351 print_hw_addr(packet->htype, packet->hlen, 352 packet->chaddr), inet_ntoa(sp->to.sin_addr)); 353 } 354 } 355 356 } 357 358 void 359 usage(void) 360 { 361 extern char *__progname; 362 363 fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n", 364 __progname); 365 exit(1); 366 } 367 368 int 369 rdaemon(int devnull) 370 { 371 372 switch (fork()) { 373 case -1: 374 return (-1); 375 case 0: 376 break; 377 default: 378 _exit(0); 379 } 380 381 if (setsid() == -1) 382 return (-1); 383 384 (void)dup2(devnull, STDIN_FILENO); 385 (void)dup2(devnull, STDOUT_FILENO); 386 (void)dup2(devnull, STDERR_FILENO); 387 if (devnull > 2) 388 (void)close(devnull); 389 390 return (0); 391 } 392 393 char * 394 print_hw_addr(int htype, int hlen, unsigned char *data) 395 { 396 static char habuf[49]; 397 char *s = habuf; 398 int i, j, slen = sizeof(habuf); 399 400 if (htype == 0 || hlen == 0) { 401 bad: 402 strlcpy(habuf, "<null>", sizeof habuf); 403 return habuf; 404 } 405 406 for (i = 0; i < hlen; i++) { 407 j = snprintf(s, slen, "%02x", data[i]); 408 if (j <= 0 || j >= slen) 409 goto bad; 410 j = strlen (s); 411 s += j; 412 slen -= (j + 1); 413 *s++ = ':'; 414 } 415 *--s = '\0'; 416 return habuf; 417 } 418 419 void 420 got_response(struct protocol *l) 421 { 422 ssize_t result; 423 struct iaddr ifrom; 424 union { 425 /* 426 * Packet input buffer. Must be as large as largest 427 * possible MTU. 428 */ 429 unsigned char packbuf[4095]; 430 struct dhcp_packet packet; 431 } u; 432 struct server_list *sp = l->local; 433 434 memset(&u, DHO_END, sizeof(u)); 435 if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 && 436 errno != ECONNREFUSED) { 437 /* 438 * Ignore ECONNREFUSED as too many dhcp servers send a bogus 439 * icmp unreach for every request. 440 */ 441 warning("recv failed for %s: %m", 442 inet_ntoa(sp->to.sin_addr)); 443 return; 444 } 445 if (result == -1 && errno == ECONNREFUSED) 446 return; 447 448 if (result == 0) 449 return; 450 451 if (result < BOOTP_MIN_LEN) { 452 note("Discarding packet with invalid size."); 453 return; 454 } 455 456 if (bootp_packet_handler) { 457 ifrom.len = 4; 458 memcpy(ifrom.iabuf, &sp->to.sin_addr, ifrom.len); 459 460 (*bootp_packet_handler)(NULL, &u.packet, result, 461 sp->to.sin_port, ifrom, NULL); 462 } 463 } 464 465 ssize_t 466 relay_agentinfo(struct interface_info *info, struct dhcp_packet *packet, 467 size_t length, struct in_addr *from, struct in_addr *to) 468 { 469 u_int8_t *p; 470 u_int i, j, railen; 471 ssize_t optlen, maxlen, grow; 472 473 if (!oflag) 474 return (length); 475 476 /* Buffer length vs. received packet length */ 477 maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1; 478 optlen = length - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN; 479 if (maxlen < 1 || optlen < 1) 480 return (length); 481 482 if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 483 DHCP_OPTIONS_COOKIE_LEN) != 0) 484 return (length); 485 p = packet->options + DHCP_OPTIONS_COOKIE_LEN; 486 487 for (i = 0; i < (u_int)optlen && *p != DHO_END;) { 488 if (*p == DHO_PAD) 489 j = 1; 490 else 491 j = p[1] + 2; 492 493 if ((i + j) > (u_int)optlen) { 494 warning("truncated dhcp options"); 495 break; 496 } 497 498 /* Revert any other relay agent information */ 499 if (*p == DHO_RELAY_AGENT_INFORMATION) { 500 if (to != NULL) { 501 /* Check the relay agent information */ 502 railen = 8 + sizeof(struct in_addr); 503 if (j >= railen && 504 p[1] == (railen - 2) && 505 p[2] == RAI_CIRCUIT_ID && 506 p[3] == 2 && 507 p[4] == (u_int8_t)(info->index << 8) && 508 p[5] == (info->index & 0xff) && 509 p[6] == RAI_REMOTE_ID && 510 p[7] == sizeof(*to)) 511 memcpy(to, p + 8, sizeof(*to)); 512 513 /* It should be the last option */ 514 memset(p, 0, j); 515 *p = DHO_END; 516 } else { 517 /* Discard invalid option from a client */ 518 if (!packet->giaddr.s_addr) 519 return (-1); 520 } 521 return (length); 522 } 523 524 p += j; 525 i += j; 526 527 if (from != NULL && (*p == DHO_END || (i >= optlen))) { 528 j = 8 + sizeof(*from); 529 if ((i + j) > (u_int)maxlen) { 530 warning("skipping agent information"); 531 break; 532 } 533 534 /* Append the relay agent information if it fits */ 535 p[0] = DHO_RELAY_AGENT_INFORMATION; 536 p[1] = j - 2; 537 p[2] = RAI_CIRCUIT_ID; 538 p[3] = 2; 539 p[4] = info->index << 8; 540 p[5] = info->index & 0xff; 541 p[6] = RAI_REMOTE_ID; 542 p[7] = sizeof(*from); 543 memcpy(p + 8, from, sizeof(*from)); 544 545 /* Do we need to increase the packet length? */ 546 grow = j + 1 - (optlen - i); 547 if (grow > 0) 548 length += grow; 549 p += j; 550 551 *p = DHO_END; 552 break; 553 } 554 } 555 556 return (length); 557 } 558 559 int 560 get_rdomain(char *name) 561 { 562 int rv = 0, s; 563 struct ifreq ifr; 564 565 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 566 error("get_rdomain socket: %m"); 567 568 bzero(&ifr, sizeof(ifr)); 569 strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); 570 if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1) 571 rv = ifr.ifr_rdomainid; 572 573 close(s); 574 return rv; 575 } 576