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
main(int argc,char * argv[])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
relay(struct interface_info * ip,struct dhcp_packet * packet,int length,struct packet_ctx * pc)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
usage(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
rdaemon(int devnull)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 *
print_hw_addr(int htype,int hlen,unsigned char * data)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
got_response(struct protocol * l)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
relay_agentinfo(struct packet_ctx * pc,struct interface_info * intf,int bootop)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
relay_agentinfo_cmp(struct packet_ctx * pc,uint8_t * p,int plen)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
relay_agentinfo_append(struct packet_ctx * pc,struct dhcp_packet * dp,size_t dplen)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
relay_agentinfo_remove(struct packet_ctx * pc,struct dhcp_packet * dp,size_t dplen)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
get_rdomain(char * name)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
l2relay(struct interface_info * ip,struct dhcp_packet * dp,int length,struct packet_ctx * pc)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