cosmetics
[tomato.git] / release / src / router / dhcpv6 / dhcp6relay.c
blobeb0bce9a24f1a3aab4b6928c29dfeb495ab750a0
1 /* $KAME: dhcp6relay.c,v 1.62 2006/01/19 04:25:16 jinmei Exp $ */
2 /*
3 * Copyright (C) 2000 WIDE Project.
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/queue.h>
34 #include <sys/uio.h>
35 #include <sys/signal.h>
37 #include <net/if.h>
38 #ifdef __FreeBSD__
39 #include <net/if_var.h>
40 #endif
42 #include <netinet/in.h>
44 #ifdef __KAME__
45 #include <netinet6/in6_var.h>
46 #endif
48 #include <netdb.h>
49 #include <arpa/inet.h>
51 #include <stdio.h>
52 #include <stdarg.h>
53 #include <syslog.h>
54 #include <unistd.h>
55 #include <stdlib.h> /* XXX: freebsd2 needs this for opt{arg,ind} */
56 #include <errno.h>
57 #include <err.h>
58 #include <string.h>
60 #include <dhcp6.h>
61 #include <config.h>
62 #include <common.h>
64 #define DHCP6RELAY_PIDFILE "/var/run/dhcp6relay.pid"
65 static char *pid_file = DHCP6RELAY_PIDFILE;
67 static int ssock; /* socket for relaying to servers */
68 static int csock; /* socket for clients */
69 static int maxfd; /* maxi file descriptor for select(2) */
71 static int debug = 0;
72 static sig_atomic_t sig_flags = 0;
73 #define SIGF_TERM 0x1
75 static char *relaydevice;
76 static char *boundaddr;
77 static char *serveraddr = DH6ADDR_ALLSERVER;
78 static char *scriptpath;
80 static char *rmsgctlbuf;
81 static socklen_t rmsgctllen;
82 static struct msghdr rmh;
83 static char rdatabuf[BUFSIZ];
84 static int relayifid;
86 static int mhops = DHCP6_RELAY_MULTICAST_HOPS;
88 static struct sockaddr_in6 sa6_server, sa6_client;
90 struct ifid_list {
91 TAILQ_ENTRY(ifid_list) ilink;
92 unsigned int ifid;
94 TAILQ_HEAD(, ifid_list) ifid_list;
95 struct prefix_list {
96 TAILQ_ENTRY(prefix_list) plink;
97 struct sockaddr_in6 paddr; /* contains meaningless but enough members */
98 int plen;
100 TAILQ_HEAD(, prefix_list) global_prefixes; /* list of non-link-local prefixes */
101 static char *global_strings[] = {
102 /* "fec0::/10", site-local unicast addresses were deprecated */
103 "2000::/3",
104 "FC00::/7", /* Unique Local Address (RFC4193) */
105 NULL
108 static void usage __P((void));
109 static struct prefix_list *make_prefix __P((char *));
110 static void relay6_init __P((int, char *[]));
111 static void relay6_loop __P((void));
112 static void relay6_recv __P((int, int));
113 static void process_signals __P((void));
114 static void relay6_signal __P((int));
115 static int make_msgcontrol __P((struct msghdr *, void *, socklen_t,
116 struct in6_pktinfo *, int));
117 static void relay_to_server __P((struct dhcp6 *, ssize_t,
118 struct sockaddr_in6 *, char *, unsigned int));
119 static void relay_to_client __P((struct dhcp6_relay *, ssize_t,
120 struct sockaddr *));
121 extern int relay6_script __P((char *, struct sockaddr_in6 *,
122 struct dhcp6 *, int));
125 static void
126 usage()
128 fprintf(stderr,
129 "usage: dhcp6relay [-dDf] [-b boundaddr] [-H hoplim] "
130 "[-r relay-IF] [-s serveraddr] [-p pidfile] [-S script] IF ...\n");
131 exit(0);
135 main(argc, argv)
136 int argc;
137 char *argv[];
139 int ch, pid;
140 char *progname;
141 char *p;
142 FILE *pidfp;
144 if ((progname = strrchr(*argv, '/')) == NULL)
145 progname = *argv;
146 else
147 progname++;
149 while((ch = getopt(argc, argv, "b:dDfH:r:s:S:p:")) != -1) {
150 switch(ch) {
151 case 'b':
152 boundaddr = optarg;
153 break;
154 case 'd':
155 debug = 1;
156 break;
157 case 'D':
158 debug = 2;
159 break;
160 case 'f':
161 foreground++;
162 break;
163 case 'H':
164 p = NULL;
165 mhops = (int)strtoul(optarg, &p, 10);
166 if (!*optarg || *p) {
167 errx(1, "illegal hop limit: %s", optarg);
168 /* NOTREACHED */
170 if (mhops <= 0 || mhops > 255) {
171 errx(1, "illegal hop limit: %d", mhops);
172 /* NOTREACHED */
174 break;
175 case 'r':
176 relaydevice = optarg;
177 break;
178 case 's':
179 serveraddr = optarg;
180 break;
181 case 'S':
182 scriptpath = optarg;
183 break;
184 case 'p':
185 pid_file = optarg;
186 break;
187 default:
188 usage();
189 exit(0);
192 argc -= optind;
193 argv += optind;
195 if (argc < 1) {
196 usage();
197 /* NOTREACHED */
199 if (relaydevice == NULL) {
200 if (argc != 1) {
201 fprintf(stderr, "you should explicitly specify a "
202 "relaying interface, when you are to "
203 "listen on multiple interfaces");
204 exit(0);
206 relaydevice = argv[0];
209 if (foreground == 0) {
210 int fd;
212 if (daemon(0, 0) < 0)
213 err(1, "daemon");
215 for (fd = 3; fd < 1024; fd++)
216 close(fd);
218 openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
220 setloglevel(debug);
222 /* dump current PID */
223 pid = getpid();
224 if ((pidfp = fopen(pid_file, "w")) != NULL) {
225 fprintf(pidfp, "%d\n", pid);
226 fclose(pidfp);
229 relay6_init(argc, argv);
231 dprintf(LOG_INFO, FNAME, "dhcp6relay started");
232 relay6_loop();
234 exit(0);
237 static struct prefix_list *
238 make_prefix(pstr0)
239 char *pstr0;
241 struct prefix_list *pent;
242 char *p, *ep;
243 int plen;
244 char pstr[BUFSIZ];
245 struct in6_addr paddr;
247 /* make a local copy for safety */
248 if (strlcpy(pstr, pstr0, sizeof (pstr)) >= sizeof (pstr)) {
249 dprintf(LOG_WARNING, FNAME,
250 "prefix string too long (maybe bogus): %s", pstr0);
251 return (NULL);
254 /* parse the string */
255 if ((p = strchr(pstr, '/')) == NULL)
256 plen = 128; /* assumes it as a host prefix */
257 else {
258 if (p[1] == '\0') {
259 dprintf(LOG_WARNING, FNAME,
260 "no prefix length (ignored): %s", p + 1);
261 return (NULL);
263 plen = (int)strtoul(p + 1, &ep, 10);
264 if (*ep != '\0') {
265 dprintf(LOG_WARNING, FNAME,
266 "illegal prefix length (ignored): %s", p + 1);
267 return (NULL);
269 *p = '\0';
271 if (inet_pton(AF_INET6, pstr, &paddr) != 1) {
272 dprintf(LOG_ERR, FNAME,
273 "inet_pton failed for %s", pstr);
274 return (NULL);
277 /* allocate a new entry */
278 if ((pent = (struct prefix_list *)malloc(sizeof (*pent))) == NULL) {
279 dprintf(LOG_WARNING, FNAME, "memory allocation failed");
280 return (NULL); /* or abort? */
283 /* fill in each member of the entry */
284 memset(pent, 0, sizeof (*pent));
285 pent->paddr.sin6_family = AF_INET6;
286 #ifdef HAVE_SA_LEN
287 pent->paddr.sin6_len = sizeof (struct sockaddr_in6);
288 #endif
289 pent->paddr.sin6_addr = paddr;
290 pent->plen = plen;
292 return (pent);
295 static void
296 relay6_init(int ifnum, char *iflist[])
298 struct addrinfo hints;
299 struct addrinfo *res, *res2;
300 int i, error, on;
301 struct ipv6_mreq mreq6;
302 static struct iovec iov[2];
304 /* initialize non-link-local prefixes list */
305 TAILQ_INIT(&global_prefixes);
306 for (i = 0; global_strings[i]; i++) {
307 struct prefix_list *p;
309 if ((p = make_prefix(global_strings[i])) != NULL)
310 TAILQ_INSERT_TAIL(&global_prefixes, p, plink);
313 /* initialize special socket addresses */
314 memset(&hints, 0, sizeof (hints));
315 hints.ai_family = PF_INET6;
316 hints.ai_socktype = SOCK_DGRAM;
317 hints.ai_protocol = IPPROTO_UDP;
318 hints.ai_flags = AI_PASSIVE;
319 error = getaddrinfo(serveraddr, DH6PORT_UPSTREAM, &hints, &res);
320 if (error) {
321 dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
322 gai_strerror(error));
323 goto failexit;
325 if (res->ai_family != PF_INET6 ||
326 res->ai_addrlen < sizeof (sa6_server)) {
327 /* this should be impossible, but check for safety */
328 dprintf(LOG_ERR, FNAME,
329 "getaddrinfo returned a bogus address: %s",
330 strerror(errno));
331 goto failexit;
333 /* XXX: assume only one DHCPv6 server address */
334 memcpy(&sa6_server, res->ai_addr, sizeof (sa6_server));
335 freeaddrinfo(res);
337 /* initialize send/receive buffer */
338 iov[0].iov_base = (caddr_t)rdatabuf;
339 iov[0].iov_len = sizeof (rdatabuf);
340 rmh.msg_iov = iov;
341 rmh.msg_iovlen = 1;
342 rmsgctllen = CMSG_SPACE(sizeof (struct in6_pktinfo));
343 if ((rmsgctlbuf = (char *)malloc(rmsgctllen)) == NULL) {
344 dprintf(LOG_ERR, FNAME, "memory allocation failed");
345 goto failexit;
349 * Setup a socket to communicate with clients.
351 memset(&hints, 0, sizeof (hints));
352 hints.ai_family = PF_INET6;
353 hints.ai_socktype = SOCK_DGRAM;
354 hints.ai_protocol = IPPROTO_UDP;
355 hints.ai_flags = AI_PASSIVE;
356 error = getaddrinfo(NULL, DH6PORT_UPSTREAM, &hints, &res);
357 if (error) {
358 dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
359 gai_strerror(error));
360 goto failexit;
362 csock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
363 if (csock < 0) {
364 dprintf(LOG_ERR, FNAME, "socket(csock): %s", strerror(errno));
365 goto failexit;
367 if (csock > maxfd)
368 maxfd = csock;
369 on = 1;
370 if (setsockopt(csock, SOL_SOCKET, SO_REUSEPORT,
371 &on, sizeof(on)) < 0) {
372 dprintf(LOG_ERR, FNAME, "setsockopt(csock, SO_REUSEPORT): %s",
373 strerror(errno));
374 goto failexit;
376 #ifdef IPV6_V6ONLY
377 if (setsockopt(csock, IPPROTO_IPV6, IPV6_V6ONLY,
378 &on, sizeof (on)) < 0) {
379 dprintf(LOG_ERR, FNAME, "setsockopt(csock, IPV6_V6ONLY): %s",
380 strerror(errno));
381 goto failexit;
383 #endif
384 if (bind(csock, res->ai_addr, res->ai_addrlen) < 0) {
385 dprintf(LOG_ERR, FNAME, "bind(csock): %s", strerror(errno));
386 goto failexit;
388 freeaddrinfo(res);
389 on = 1;
390 #ifdef IPV6_RECVPKTINFO
391 if (setsockopt(csock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
392 &on, sizeof (on)) < 0) {
393 dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_RECVPKTINFO): %s",
394 strerror(errno));
395 goto failexit;
397 #else
398 if (setsockopt(csock, IPPROTO_IPV6, IPV6_PKTINFO,
399 &on, sizeof (on)) < 0) {
400 dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_PKTINFO): %s",
401 strerror(errno));
402 goto failexit;
404 #endif
406 hints.ai_flags = 0;
407 error = getaddrinfo(DH6ADDR_ALLAGENT, 0, &hints, &res2);
408 if (error) {
409 dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
410 gai_strerror(error));
411 goto failexit;
413 memset(&mreq6, 0, sizeof (mreq6));
414 memcpy(&mreq6.ipv6mr_multiaddr,
415 &((struct sockaddr_in6 *)res2->ai_addr)->sin6_addr,
416 sizeof (mreq6.ipv6mr_multiaddr));
418 TAILQ_INIT(&ifid_list);
419 while (ifnum-- > 0) {
420 char *ifp = iflist[0];
421 struct ifid_list *ifd;
423 ifd = (struct ifid_list *)malloc(sizeof (*ifd));
424 if (ifd == NULL) {
425 dprintf(LOG_WARNING, FNAME,
426 "memory allocation failed");
427 goto failexit;
429 memset(ifd, 0, sizeof (*ifd));
430 ifd->ifid = if_nametoindex(ifp);
431 if (ifd->ifid == 0) {
432 dprintf(LOG_ERR, FNAME, "invalid interface %s", ifp);
433 goto failexit;
435 mreq6.ipv6mr_interface = ifd->ifid;
437 if (setsockopt(csock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
438 &mreq6, sizeof (mreq6))) {
439 dprintf(LOG_ERR, FNAME,
440 "setsockopt(csock, IPV6_JOIN_GROUP): %s",
441 strerror(errno));
442 goto failexit;
444 TAILQ_INSERT_TAIL(&ifid_list, ifd, ilink);
445 iflist++;
447 freeaddrinfo(res2);
450 * Setup a socket to relay to servers.
452 relayifid = if_nametoindex(relaydevice);
453 if (relayifid == 0)
454 dprintf(LOG_ERR, FNAME, "invalid interface %s", relaydevice);
456 * We are not really sure if we need to listen on the downstream
457 * port to receive packets from servers. We'll need to clarify the
458 * specification, but we do for now.
460 hints.ai_flags = AI_PASSIVE;
461 error = getaddrinfo(boundaddr, DH6PORT_DOWNSTREAM, &hints, &res);
462 if (error) {
463 dprintf(LOG_ERR, FNAME, "getaddrinfo: %s",
464 gai_strerror(error));
465 goto failexit;
467 memcpy(&sa6_client, res->ai_addr, sizeof (sa6_client));
468 ssock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
469 if (ssock < 0) {
470 dprintf(LOG_ERR, FNAME, "socket(outsock): %s",
471 strerror(error));
472 goto failexit;
474 if (ssock > maxfd)
475 maxfd = ssock;
476 on = 1;
478 * Both a relay and a client may run on a single node. If we need to
479 * listen on the downstream port, we need REUSEPORT to avoid conflict.
481 if (setsockopt(ssock, SOL_SOCKET, SO_REUSEPORT,
482 &on, sizeof (on)) < 0) {
483 dprintf(LOG_ERR, FNAME, "setsockopt(ssock, SO_REUSEPORT): %s",
484 strerror(errno));
485 goto failexit;
487 on = 1;
488 #ifdef IPV6_V6ONLY
489 if (setsockopt(ssock, IPPROTO_IPV6, IPV6_V6ONLY,
490 &on, sizeof (on)) < 0) {
491 dprintf(LOG_ERR, FNAME, "setsockopt(ssock, IPV6_V6ONLY): %s",
492 strerror(errno));
493 goto failexit;
495 #endif
496 if (bind(ssock, res->ai_addr, res->ai_addrlen) < 0) {
497 dprintf(LOG_ERR, FNAME, "bind(ssock): %s", strerror(errno));
498 goto failexit;
500 freeaddrinfo(res);
502 on = 1;
503 #ifdef IPV6_RECVPKTINFO
504 if (setsockopt(ssock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
505 &on, sizeof (on)) < 0) {
506 dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_RECVPKTINFO): %s",
507 strerror(errno));
508 goto failexit;
510 #else
511 if (setsockopt(ssock, IPPROTO_IPV6, IPV6_PKTINFO,
512 &on, sizeof (on)) < 0) {
513 dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_PKTINFO): %s",
514 strerror(errno));
515 goto failexit;
517 #endif
519 if (signal(SIGTERM, relay6_signal) == SIG_ERR) {
520 dprintf(LOG_WARNING, FNAME, "failed to set signal: %s",
521 strerror(errno));
522 exit(1);
524 return;
526 failexit:
527 exit(1);
530 static void
531 relay6_signal(sig)
532 int sig;
535 switch (sig) {
536 case SIGTERM:
537 sig_flags |= SIGF_TERM;
538 break;
542 static void
543 process_signals()
545 if ((sig_flags & SIGF_TERM)) {
546 unlink(pid_file);
547 exit(0);
551 static void
552 relay6_loop()
554 fd_set readfds;
555 int e;
557 while(1) {
558 if (sig_flags)
559 process_signals();
561 /* we'd rather use FD_COPY here, but it's not POSIX friendly */
562 FD_ZERO(&readfds);
563 FD_SET(csock, &readfds);
564 FD_SET(ssock, &readfds);
566 e = select(maxfd + 1, &readfds, NULL, NULL, NULL);
567 switch(e) {
568 case 0: /* impossible in our situation */
569 errx(1, "select returned 0");
570 /* NOTREACHED */
571 case -1:
572 if (errno != EINTR) {
573 err(1, "select");
574 /* NOTREACHED */
576 continue;
577 default:
578 break;
581 if (FD_ISSET(csock, &readfds))
582 relay6_recv(csock, 1);
584 if (FD_ISSET(ssock, &readfds))
585 relay6_recv(ssock, 0);
589 static void
590 relay6_recv(s, fromclient)
591 int s, fromclient;
593 ssize_t len;
594 struct sockaddr_storage from;
595 struct in6_pktinfo *pi = NULL;
596 struct cmsghdr *cm;
597 struct dhcp6 *dh6;
598 struct ifid_list *ifd;
599 char ifname[IF_NAMESIZE];
601 rmh.msg_control = (caddr_t)rmsgctlbuf;
602 rmh.msg_controllen = rmsgctllen;
604 rmh.msg_name = &from;
605 rmh.msg_namelen = sizeof (from);
607 if ((len = recvmsg(s, &rmh, 0)) < 0) {
608 dprintf(LOG_WARNING, FNAME, "recvmsg: %s", strerror(errno));
609 return;
612 dprintf(LOG_DEBUG, FNAME, "from %s, size %d",
613 addr2str((struct sockaddr *)&from), len);
615 if (((struct sockaddr *)&from)->sa_family != AF_INET6) {
616 dprintf(LOG_WARNING, FNAME,
617 "non-IPv6 packet is received (AF %d) ",
618 ((struct sockaddr *)&from)->sa_family);
619 return;
622 /* get optional information as ancillary data (if available) */
623 for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rmh); cm;
624 cm = (struct cmsghdr *)CMSG_NXTHDR(&rmh, cm)) {
625 if (cm->cmsg_level != IPPROTO_IPV6)
626 continue;
628 switch(cm->cmsg_type) {
629 case IPV6_PKTINFO:
630 pi = (struct in6_pktinfo *)CMSG_DATA(cm);
631 break;
634 if (pi == NULL) {
635 dprintf(LOG_WARNING, FNAME,
636 "failed to get the arrival interface");
637 return;
639 for (ifd = TAILQ_FIRST(&ifid_list); ifd;
640 ifd = TAILQ_NEXT(ifd, ilink)) {
641 if (pi->ipi6_ifindex == ifd->ifid)
642 break;
645 * DHCPv6 relay may receive a DHCPv6 packet from a non-listening
646 * interface, when a DHCPv6 server is running on that interface.
647 * This check prevents such reception.
649 if (ifd == NULL && pi->ipi6_ifindex != relayifid)
650 return;
651 if (if_indextoname(pi->ipi6_ifindex, ifname) == NULL) {
652 dprintf(LOG_WARNING, FNAME,
653 "if_indextoname(id = %d): %s",
654 pi->ipi6_ifindex, strerror(errno));
655 return;
658 /* packet validation */
659 if (len < sizeof (*dh6)) {
660 dprintf(LOG_INFO, FNAME, "short packet (%d bytes)", len);
661 return;
664 dh6 = (struct dhcp6 *)rdatabuf;
665 dprintf(LOG_DEBUG, FNAME, "received %s from %s",
666 dhcp6msgstr(dh6->dh6_msgtype), addr2str((struct sockaddr *)&from));
669 * Relay the packet according to the type. A client message or
670 * a relay forward message is forwarded to servers (or other relays),
671 * and a relay reply message is forwarded to the intended client.
673 if (fromclient) {
674 switch (dh6->dh6_msgtype) {
675 case DH6_SOLICIT:
676 case DH6_REQUEST:
677 case DH6_CONFIRM:
678 case DH6_RENEW:
679 case DH6_REBIND:
680 case DH6_RELEASE:
681 case DH6_DECLINE:
682 case DH6_INFORM_REQ:
683 case DH6_RELAY_FORW:
684 relay_to_server(dh6, len, (struct sockaddr_in6 *)&from,
685 ifname, htonl(pi->ipi6_ifindex));
686 break;
687 case DH6_RELAY_REPLY:
689 * The server may send a relay reply to the client
690 * port.
691 * XXX: need to clarify the port issue
693 relay_to_client((struct dhcp6_relay *)dh6, len,
694 (struct sockaddr *)&from);
695 break;
696 default:
697 dprintf(LOG_INFO, FNAME,
698 "unexpected message (%s) on the client side "
699 "from %s", dhcp6msgstr(dh6->dh6_msgtype),
700 addr2str((struct sockaddr *)&from));
701 break;
703 } else {
704 if (dh6->dh6_msgtype != DH6_RELAY_REPLY) {
705 dprintf(LOG_INFO, FNAME,
706 "unexpected message (%s) on the server side"
707 "from %s", dhcp6msgstr(dh6->dh6_msgtype),
708 addr2str((struct sockaddr *)&from));
709 return;
711 relay_to_client((struct dhcp6_relay *)dh6, len,
712 (struct sockaddr *)&from);
716 static int
717 make_msgcontrol(mh, ctlbuf, buflen, pktinfo, hlim)
718 struct msghdr *mh;
719 void *ctlbuf;
720 socklen_t buflen;
721 struct in6_pktinfo *pktinfo;
722 int hlim;
724 struct cmsghdr *cm;
725 socklen_t controllen;
727 controllen = 0;
728 if (pktinfo)
729 controllen += CMSG_SPACE(sizeof (*pktinfo));
730 if (hlim > 0)
731 controllen += CMSG_SPACE(sizeof (hlim));
732 if (buflen < controllen)
733 return (-1);
735 memset(ctlbuf, 0, buflen);
736 mh->msg_controllen = controllen;
737 mh->msg_control = ctlbuf;
739 cm = (struct cmsghdr *)CMSG_FIRSTHDR(mh);
740 if (pktinfo) {
741 cm->cmsg_len = CMSG_LEN(sizeof (*pktinfo));
742 cm->cmsg_level = IPPROTO_IPV6;
743 cm->cmsg_type = IPV6_PKTINFO;
744 memcpy(CMSG_DATA((struct cmsghdr *)cm), pktinfo,
745 sizeof (*pktinfo));
747 cm = CMSG_NXTHDR(mh, cm);
750 if (hlim > 0) {
751 cm->cmsg_len = CMSG_LEN(sizeof (hlim));
752 cm->cmsg_level = IPPROTO_IPV6;
753 cm->cmsg_type = IPV6_HOPLIMIT;
754 *(int *)CMSG_DATA((struct cmsghdr *)cm) = hlim;
756 cm = CMSG_NXTHDR(mh, cm); /* just in case */
759 return (0);
762 static void
763 relay_to_server(dh6, len, from, ifname, ifid)
764 struct dhcp6 *dh6;
765 ssize_t len;
766 struct sockaddr_in6 *from;
767 char *ifname;
768 unsigned int ifid;
770 struct dhcp6_optinfo optinfo;
771 struct dhcp6_relay *dh6relay;
772 struct in6_addr linkaddr;
773 struct prefix_list *p;
774 int optlen, relaylen;
775 int cc;
776 struct msghdr mh;
777 static struct iovec iov[2];
778 u_char relaybuf[sizeof (*dh6relay) + BUFSIZ];
779 struct in6_pktinfo pktinfo;
780 char ctlbuf[CMSG_SPACE(sizeof (struct in6_pktinfo))
781 + CMSG_SPACE(sizeof (int))];
784 * Prepare a relay forward option.
786 dhcp6_init_options(&optinfo);
788 /* Relay message */
789 if ((optinfo.relaymsg_msg = malloc(len)) == NULL) {
790 dprintf(LOG_WARNING, FNAME,
791 "failed to allocate memory to copy the original packet: "
792 "%s", strerror(errno));
793 goto out;
795 optinfo.relaymsg_len = len;
796 memcpy(optinfo.relaymsg_msg, dh6, len);
798 /* Interface-id. We always use this option. */
799 if ((optinfo.ifidopt_id = malloc(sizeof (ifid))) == NULL) {
800 dprintf(LOG_WARNING, FNAME,
801 "failed to allocate memory for IFID: %s", strerror(errno));
802 goto out;
804 optinfo.ifidopt_len = sizeof (ifid);
805 memcpy(optinfo.ifidopt_id, &ifid, sizeof (ifid));
808 * Construct a relay forward message.
810 memset(relaybuf, 0, sizeof (relaybuf));
812 dh6relay = (struct dhcp6_relay *)relaybuf;
813 memset(dh6relay, 0, sizeof (*dh6relay));
814 dh6relay->dh6relay_msgtype = DH6_RELAY_FORW;
815 memcpy(&dh6relay->dh6relay_peeraddr, &from->sin6_addr,
816 sizeof (dh6relay->dh6relay_peeraddr));
818 /* find a global address to fill in the link address field */
819 memset(&linkaddr, 0, sizeof (linkaddr));
820 for (p = TAILQ_FIRST(&global_prefixes); p; p = TAILQ_NEXT(p, plink)) {
821 if (getifaddr(&linkaddr, ifname, &p->paddr.sin6_addr,
822 p->plen, 1, IN6_IFF_INVALID) == 0) /* found */
823 break;
825 if (p == NULL) {
826 dprintf(LOG_NOTICE, FNAME,
827 "failed to find a global address on %s", ifname);
830 * When relaying a message from a client, we need a global
831 * link address.
832 * XXX: this may be too strong for the stateless case, but
833 * the DHCPv6 specification seems to require the behavior.
835 if (dh6->dh6_msgtype != DH6_RELAY_FORW)
836 goto out;
839 if (dh6->dh6_msgtype == DH6_RELAY_FORW) {
840 struct dhcp6_relay *dh6relay0 = (struct dhcp6_relay *)dh6;
842 /* Relaying a Message from a Relay Agent */
845 * If the hop-count in the message is greater than or equal to
846 * HOP_COUNT_LIMIT, the relay agent discards the received
847 * message.
848 * [RFC3315 Section 20.1.2]
850 if (dh6relay0->dh6relay_hcnt >= DHCP6_RELAY_HOP_COUNT_LIMIT) {
851 dprintf(LOG_INFO, FNAME, "too many relay forwardings");
852 goto out;
855 dh6relay->dh6relay_hcnt = dh6relay0->dh6relay_hcnt + 1;
858 * We can keep the link-address field 0, regardless of the
859 * scope of the source address, since we always include
860 * interface-ID option.
862 } else {
863 /* Relaying a Message from a Client */
864 memcpy(&dh6relay->dh6relay_linkaddr, &linkaddr,
865 sizeof (dh6relay->dh6relay_linkaddr));
866 dh6relay->dh6relay_hcnt = 0;
869 relaylen = sizeof (*dh6relay);
870 if ((optlen = dhcp6_set_options(DH6_RELAY_FORW,
871 (struct dhcp6opt *)(dh6relay + 1),
872 (struct dhcp6opt *)(relaybuf + sizeof (relaybuf)),
873 &optinfo)) < 0) {
874 dprintf(LOG_INFO, FNAME,
875 "failed to construct relay options");
876 goto out;
878 relaylen += optlen;
881 * Forward the message.
883 memset(&mh, 0, sizeof (mh));
884 iov[0].iov_base = relaybuf;
885 iov[0].iov_len = relaylen;
886 mh.msg_iov = iov;
887 mh.msg_iovlen = 1;
888 mh.msg_name = &sa6_server;
889 mh.msg_namelen = sizeof (sa6_server);
890 if (IN6_IS_ADDR_MULTICAST(&sa6_server.sin6_addr)) {
891 memset(&pktinfo, 0, sizeof (pktinfo));
892 pktinfo.ipi6_ifindex = relayifid;
893 if (make_msgcontrol(&mh, ctlbuf, sizeof (ctlbuf),
894 &pktinfo, mhops)) {
895 dprintf(LOG_WARNING, FNAME,
896 "failed to make message control data");
897 goto out;
901 if ((cc = sendmsg(ssock, &mh, 0)) < 0) {
902 dprintf(LOG_WARNING, FNAME,
903 "sendmsg %s failed: %s",
904 addr2str((struct sockaddr *)&sa6_server), strerror(errno));
905 } else if (cc != relaylen) {
906 dprintf(LOG_WARNING, FNAME,
907 "failed to send a complete packet to %s",
908 addr2str((struct sockaddr *)&sa6_server));
909 } else {
910 dprintf(LOG_DEBUG, FNAME,
911 "relay a message to a server %s",
912 addr2str((struct sockaddr *)&sa6_server));
915 out:
916 dhcp6_clear_options(&optinfo);
919 static void
920 relay_to_client(dh6relay, len, from)
921 struct dhcp6_relay *dh6relay;
922 ssize_t len;
923 struct sockaddr *from;
925 struct dhcp6_optinfo optinfo;
926 struct sockaddr_in6 peer;
927 unsigned int ifid;
928 char ifnamebuf[IFNAMSIZ];
929 int cc;
930 int relayed = 0;
931 struct dhcp6 *dh6;
932 struct msghdr mh;
933 struct in6_pktinfo pktinfo;
934 static struct iovec iov[2];
935 char ctlbuf[CMSG_SPACE(sizeof (struct in6_pktinfo))];
937 dprintf(LOG_DEBUG, FNAME,
938 "dhcp6 relay reply: hop=%d, linkaddr=%s, peeraddr=%s",
939 dh6relay->dh6relay_hcnt,
940 in6addr2str(&dh6relay->dh6relay_linkaddr, 0),
941 in6addr2str(&dh6relay->dh6relay_peeraddr, 0));
944 * parse and validate options in the relay reply message.
946 dhcp6_init_options(&optinfo);
947 if (dhcp6_get_options((struct dhcp6opt *)(dh6relay + 1),
948 (struct dhcp6opt *)((char *)dh6relay + len), &optinfo) < 0) {
949 dprintf(LOG_INFO, FNAME, "failed to parse options");
950 return;
953 /* A relay reply message must include a relay message option */
954 if (optinfo.relaymsg_msg == NULL) {
955 dprintf(LOG_INFO, FNAME, "relay reply message from %s "
956 "without a relay message", addr2str(from));
957 goto out;
960 /* minimum validation for the inner message */
961 if (optinfo.relaymsg_len < sizeof (struct dhcp6)) {
962 dprintf(LOG_INFO, FNAME, "short relay message from %s",
963 addr2str(from));
964 goto out;
968 * Extract interface ID which should be included in relay reply
969 * messages to us.
971 ifid = 0;
972 if (optinfo.ifidopt_id) {
973 if (optinfo.ifidopt_len != sizeof (ifid)) {
974 dprintf(LOG_INFO, FNAME,
975 "unexpected length (%d) for Interface ID from %s",
976 optinfo.ifidopt_len, addr2str(from));
977 goto out;
978 } else {
979 memcpy(&ifid, optinfo.ifidopt_id, sizeof (ifid));
980 ifid = ntohl(ifid);
982 /* validation for ID */
983 if ((if_indextoname(ifid, ifnamebuf)) == NULL) {
984 dprintf(LOG_INFO, FNAME,
985 "invalid interface ID: %x", ifid);
986 goto out;
989 } else {
990 dprintf(LOG_INFO, FNAME,
991 "Interface ID is not included from %s", addr2str(from));
993 * the responding server should be buggy, but we deal with it.
998 * If we fail, try to get the interface from the link address.
1000 if (ifid == 0 &&
1001 !IN6_IS_ADDR_UNSPECIFIED(&dh6relay->dh6relay_linkaddr) &&
1002 !IN6_IS_ADDR_LINKLOCAL(&dh6relay->dh6relay_linkaddr)) {
1003 if (getifidfromaddr(&dh6relay->dh6relay_linkaddr, &ifid))
1004 ifid = 0;
1007 if (ifid == 0) {
1008 dprintf(LOG_INFO, FNAME, "failed to determine relay link");
1009 goto out;
1012 peer = sa6_client;
1013 dh6 = (struct dhcp6 *) optinfo.relaymsg_msg;
1014 if (dh6->dh6_msgtype != DH6_RELAY_REPLY) {
1015 relayed++;
1016 } else {
1018 * change dst port to server/relay port, since it's a
1019 * reply to relay, not to a client
1021 peer.sin6_port = htons(547); /* DH6PORT_UPSTREAM */
1023 memcpy(&peer.sin6_addr, &dh6relay->dh6relay_peeraddr,
1024 sizeof (peer.sin6_addr));
1025 if (IN6_IS_ADDR_LINKLOCAL(&peer.sin6_addr))
1026 peer.sin6_scope_id = ifid; /* XXX: we assume a 1to1 map */
1028 /* construct a message structure specifying the outgoing interface */
1029 memset(&mh, 0, sizeof (mh));
1030 iov[0].iov_base = optinfo.relaymsg_msg;
1031 iov[0].iov_len = optinfo.relaymsg_len;
1032 mh.msg_iov = iov;
1033 mh.msg_iovlen = 1;
1034 mh.msg_name = &peer;
1035 mh.msg_namelen = sizeof (peer);
1036 memset(&pktinfo, 0, sizeof (pktinfo));
1037 pktinfo.ipi6_ifindex = ifid;
1038 if (make_msgcontrol(&mh, ctlbuf, sizeof (ctlbuf), &pktinfo, 0)) {
1039 dprintf(LOG_WARNING, FNAME,
1040 "failed to make message control data");
1041 goto out;
1044 /* send packet */
1045 if ((cc = sendmsg(csock, &mh, 0)) < 0) {
1046 dprintf(LOG_WARNING, FNAME,
1047 "sendmsg to %s failed: %s",
1048 addr2str((struct sockaddr *)&peer), strerror(errno));
1049 } else if (cc != optinfo.relaymsg_len) {
1050 dprintf(LOG_WARNING, FNAME,
1051 "failed to send a complete packet to %s",
1052 addr2str((struct sockaddr *)&peer));
1053 } else {
1054 dprintf(LOG_DEBUG, FNAME,
1055 "relay a message to a client %s",
1056 addr2str((struct sockaddr *)&peer));
1059 if (relayed && scriptpath != NULL)
1060 relay6_script(scriptpath, &peer, dh6, optinfo.relaymsg_len);
1062 out:
1063 dhcp6_clear_options(&optinfo);
1064 return;