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