diff options
Diffstat (limited to 'dhcp6relay.c')
-rw-r--r-- | dhcp6relay.c | 1059 |
1 files changed, 1059 insertions, 0 deletions
diff --git a/dhcp6relay.c b/dhcp6relay.c new file mode 100644 index 0000000..fb84ce3 --- /dev/null +++ b/dhcp6relay.c @@ -0,0 +1,1059 @@ +/* $KAME: dhcp6relay.c,v 1.62 2006/01/19 04:25:16 jinmei Exp $ */ +/* + * Copyright (C) 2000 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <sys/signal.h> + +#include <net/if.h> +#ifdef __FreeBSD__ +#include <net/if_var.h> +#endif + +#include <netinet/in.h> + +#ifdef __KAME__ +#include <netinet6/in6_var.h> +#endif + +#include <netdb.h> +#include <arpa/inet.h> + +#include <stdio.h> +#include <stdarg.h> +#include <syslog.h> +#include <unistd.h> +#include <stdlib.h> /* XXX: freebsd2 needs this for opt{arg,ind} */ +#include <errno.h> +#include <err.h> +#include <string.h> + +#include <dhcp6.h> +#include <config.h> +#include <common.h> + +#define DHCP6RELAY_PIDFILE "/var/run/dhcp6relay.pid" +static char *pid_file = DHCP6RELAY_PIDFILE; + +static int ssock; /* socket for relaying to servers */ +static int csock; /* socket for clients */ +static int maxfd; /* maxi file descriptor for select(2) */ + +static int debug = 0; +static sig_atomic_t sig_flags = 0; +#define SIGF_TERM 0x1 + +static char *relaydevice; +static char *boundaddr; +static char *serveraddr = DH6ADDR_ALLSERVER; +static char *scriptpath; + +static char *rmsgctlbuf; +static socklen_t rmsgctllen; +static struct msghdr rmh; +static char rdatabuf[BUFSIZ]; +static int relayifid; + +static int mhops = DHCP6_RELAY_MULTICAST_HOPS; + +static struct sockaddr_in6 sa6_server, sa6_client; + +struct ifid_list { + TAILQ_ENTRY(ifid_list) ilink; + unsigned int ifid; +}; +TAILQ_HEAD(, ifid_list) ifid_list; +struct prefix_list { + TAILQ_ENTRY(prefix_list) plink; + struct sockaddr_in6 paddr; /* contains meaningless but enough members */ + int plen; +}; +TAILQ_HEAD(, prefix_list) global_prefixes; /* list of non-link-local prefixes */ +static char *global_strings[] = { + /* "fec0::/10", site-local unicast addresses were deprecated */ + "2000::/3", + "FC00::/7", /* Unique Local Address (RFC4193) */ + NULL +}; + +static void usage __P((void)); +static struct prefix_list *make_prefix __P((char *)); +static void relay6_init __P((int, char *[])); +static void relay6_loop __P((void)); +static void relay6_recv __P((int, int)); +static void process_signals __P((void)); +static void relay6_signal __P((int)); +static int make_msgcontrol __P((struct msghdr *, void *, socklen_t, + struct in6_pktinfo *, int)); +static void relay_to_server __P((struct dhcp6 *, ssize_t, + struct sockaddr_in6 *, char *, unsigned int)); +static void relay_to_client __P((struct dhcp6_relay *, ssize_t, + struct sockaddr *)); +extern int relay6_script __P((char *, struct sockaddr_in6 *, + struct dhcp6 *, int)); + + +static void +usage() +{ + fprintf(stderr, + "usage: dhcp6relay [-dDf] [-b boundaddr] [-H hoplim] " + "[-r relay-IF] [-s serveraddr] [-p pidfile] [-S script] IF ...\n"); + exit(0); +} + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int ch, pid; + char *progname; + char *p; + FILE *pidfp; + + if ((progname = strrchr(*argv, '/')) == NULL) + progname = *argv; + else + progname++; + + while((ch = getopt(argc, argv, "b:dDfH:r:s:S:p:")) != -1) { + switch(ch) { + case 'b': + boundaddr = optarg; + break; + case 'd': + debug = 1; + break; + case 'D': + debug = 2; + break; + case 'f': + foreground++; + break; + case 'H': + p = NULL; + mhops = (int)strtoul(optarg, &p, 10); + if (!*optarg || *p) { + errx(1, "illegal hop limit: %s", optarg); + /* NOTREACHED */ + } + if (mhops <= 0 || mhops > 255) { + errx(1, "illegal hop limit: %d", mhops); + /* NOTREACHED */ + } + break; + case 'r': + relaydevice = optarg; + break; + case 's': + serveraddr = optarg; + break; + case 'S': + scriptpath = optarg; + break; + case 'p': + pid_file = optarg; + break; + default: + usage(); + exit(0); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); + /* NOTREACHED */ + } + if (relaydevice == NULL) { + if (argc != 1) { + fprintf(stderr, "you should explicitly specify a " + "relaying interface, when you are to " + "listen on multiple interfaces"); + exit(0); + } + relaydevice = argv[0]; + } + + if (foreground == 0) { + if (daemon(0, 0) < 0) + err(1, "daemon"); + openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON); + } + setloglevel(debug); + + /* dump current PID */ + pid = getpid(); + if ((pidfp = fopen(pid_file, "w")) != NULL) { + fprintf(pidfp, "%d\n", pid); + fclose(pidfp); + } + + relay6_init(argc, argv); + + dprintf(LOG_INFO, FNAME, "dhcp6relay started"); + relay6_loop(); + + exit(0); +} + +static struct prefix_list * +make_prefix(pstr0) + char *pstr0; +{ + struct prefix_list *pent; + char *p, *ep; + int plen; + char pstr[BUFSIZ]; + struct in6_addr paddr; + + /* make a local copy for safety */ + if (strlcpy(pstr, pstr0, sizeof (pstr)) >= sizeof (pstr)) { + dprintf(LOG_WARNING, FNAME, + "prefix string too long (maybe bogus): %s", pstr0); + return (NULL); + } + + /* parse the string */ + if ((p = strchr(pstr, '/')) == NULL) + plen = 128; /* assumes it as a host prefix */ + else { + if (p[1] == '\0') { + dprintf(LOG_WARNING, FNAME, + "no prefix length (ignored): %s", p + 1); + return (NULL); + } + plen = (int)strtoul(p + 1, &ep, 10); + if (*ep != '\0') { + dprintf(LOG_WARNING, FNAME, + "illegal prefix length (ignored): %s", p + 1); + return (NULL); + } + *p = '\0'; + } + if (inet_pton(AF_INET6, pstr, &paddr) != 1) { + dprintf(LOG_ERR, FNAME, + "inet_pton failed for %s", pstr); + return (NULL); + } + + /* allocate a new entry */ + if ((pent = (struct prefix_list *)malloc(sizeof (*pent))) == NULL) { + dprintf(LOG_WARNING, FNAME, "memory allocation failed"); + return (NULL); /* or abort? */ + } + + /* fill in each member of the entry */ + memset(pent, 0, sizeof (*pent)); + pent->paddr.sin6_family = AF_INET6; +#ifdef HAVE_SA_LEN + pent->paddr.sin6_len = sizeof (struct sockaddr_in6); +#endif + pent->paddr.sin6_addr = paddr; + pent->plen = plen; + + return (pent); +} + +static void +relay6_init(int ifnum, char *iflist[]) +{ + struct addrinfo hints; + struct addrinfo *res, *res2; + int i, error, on; + struct ipv6_mreq mreq6; + static struct iovec iov[2]; + + /* initialize non-link-local prefixes list */ + TAILQ_INIT(&global_prefixes); + for (i = 0; global_strings[i]; i++) { + struct prefix_list *p; + + if ((p = make_prefix(global_strings[i])) != NULL) + TAILQ_INSERT_TAIL(&global_prefixes, p, plink); + } + + /* initialize special socket addresses */ + memset(&hints, 0, sizeof (hints)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(serveraddr, DH6PORT_UPSTREAM, &hints, &res); + if (error) { + dprintf(LOG_ERR, FNAME, "getaddrinfo: %s", + gai_strerror(error)); + goto failexit; + } + if (res->ai_family != PF_INET6 || + res->ai_addrlen < sizeof (sa6_server)) { + /* this should be impossible, but check for safety */ + dprintf(LOG_ERR, FNAME, + "getaddrinfo returned a bogus address: %s", + strerror(errno)); + goto failexit; + } + /* XXX: assume only one DHCPv6 server address */ + memcpy(&sa6_server, res->ai_addr, sizeof (sa6_server)); + freeaddrinfo(res); + + /* initialize send/receive buffer */ + iov[0].iov_base = (caddr_t)rdatabuf; + iov[0].iov_len = sizeof (rdatabuf); + rmh.msg_iov = iov; + rmh.msg_iovlen = 1; + rmsgctllen = CMSG_SPACE(sizeof (struct in6_pktinfo)); + if ((rmsgctlbuf = (char *)malloc(rmsgctllen)) == NULL) { + dprintf(LOG_ERR, FNAME, "memory allocation failed"); + goto failexit; + } + + /* + * Setup a socket to communicate with clients. + */ + memset(&hints, 0, sizeof (hints)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(NULL, DH6PORT_UPSTREAM, &hints, &res); + if (error) { + dprintf(LOG_ERR, FNAME, "getaddrinfo: %s", + gai_strerror(error)); + goto failexit; + } + csock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (csock < 0) { + dprintf(LOG_ERR, FNAME, "socket(csock): %s", strerror(errno)); + goto failexit; + } + if (csock > maxfd) + maxfd = csock; + on = 1; + if (setsockopt(csock, SOL_SOCKET, SO_REUSEPORT, + &on, sizeof(on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(csock, SO_REUSEPORT): %s", + strerror(errno)); + goto failexit; + } +#ifdef IPV6_V6ONLY + if (setsockopt(csock, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(csock, IPV6_V6ONLY): %s", + strerror(errno)); + goto failexit; + } +#endif + if (bind(csock, res->ai_addr, res->ai_addrlen) < 0) { + dprintf(LOG_ERR, FNAME, "bind(csock): %s", strerror(errno)); + goto failexit; + } + freeaddrinfo(res); + on = 1; +#ifdef IPV6_RECVPKTINFO + if (setsockopt(csock, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_RECVPKTINFO): %s", + strerror(errno)); + goto failexit; + } +#else + if (setsockopt(csock, IPPROTO_IPV6, IPV6_PKTINFO, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_PKTINFO): %s", + strerror(errno)); + goto failexit; + } +#endif + + hints.ai_flags = 0; + error = getaddrinfo(DH6ADDR_ALLAGENT, 0, &hints, &res2); + if (error) { + dprintf(LOG_ERR, FNAME, "getaddrinfo: %s", + gai_strerror(error)); + goto failexit; + } + memset(&mreq6, 0, sizeof (mreq6)); + memcpy(&mreq6.ipv6mr_multiaddr, + &((struct sockaddr_in6 *)res2->ai_addr)->sin6_addr, + sizeof (mreq6.ipv6mr_multiaddr)); + + TAILQ_INIT(&ifid_list); + while (ifnum-- > 0) { + char *ifp = iflist[0]; + struct ifid_list *ifd; + + ifd = (struct ifid_list *)malloc(sizeof (*ifd)); + if (ifd == NULL) { + dprintf(LOG_WARNING, FNAME, + "memory allocation failed"); + goto failexit; + } + memset(ifd, 0, sizeof (*ifd)); + ifd->ifid = if_nametoindex(ifp); + if (ifd->ifid == 0) { + dprintf(LOG_ERR, FNAME, "invalid interface %s", ifp); + goto failexit; + } + mreq6.ipv6mr_interface = ifd->ifid; + + if (setsockopt(csock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq6, sizeof (mreq6))) { + dprintf(LOG_ERR, FNAME, + "setsockopt(csock, IPV6_JOIN_GROUP): %s", + strerror(errno)); + goto failexit; + } + TAILQ_INSERT_TAIL(&ifid_list, ifd, ilink); + iflist++; + } + freeaddrinfo(res2); + + /* + * Setup a socket to relay to servers. + */ + relayifid = if_nametoindex(relaydevice); + if (relayifid == 0) + dprintf(LOG_ERR, FNAME, "invalid interface %s", relaydevice); + /* + * We are not really sure if we need to listen on the downstream + * port to receive packets from servers. We'll need to clarify the + * specification, but we do for now. + */ + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(boundaddr, DH6PORT_DOWNSTREAM, &hints, &res); + if (error) { + dprintf(LOG_ERR, FNAME, "getaddrinfo: %s", + gai_strerror(error)); + goto failexit; + } + memcpy(&sa6_client, res->ai_addr, sizeof (sa6_client)); + ssock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (ssock < 0) { + dprintf(LOG_ERR, FNAME, "socket(outsock): %s", + strerror(error)); + goto failexit; + } + if (ssock > maxfd) + maxfd = ssock; + on = 1; + /* + * Both a relay and a client may run on a single node. If we need to + * listen on the downstream port, we need REUSEPORT to avoid conflict. + */ + if (setsockopt(ssock, SOL_SOCKET, SO_REUSEPORT, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(ssock, SO_REUSEPORT): %s", + strerror(errno)); + goto failexit; + } + on = 1; +#ifdef IPV6_V6ONLY + if (setsockopt(ssock, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(ssock, IPV6_V6ONLY): %s", + strerror(errno)); + goto failexit; + } +#endif + if (bind(ssock, res->ai_addr, res->ai_addrlen) < 0) { + dprintf(LOG_ERR, FNAME, "bind(ssock): %s", strerror(errno)); + goto failexit; + } + freeaddrinfo(res); + + on = 1; +#ifdef IPV6_RECVPKTINFO + if (setsockopt(ssock, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_RECVPKTINFO): %s", + strerror(errno)); + goto failexit; + } +#else + if (setsockopt(ssock, IPPROTO_IPV6, IPV6_PKTINFO, + &on, sizeof (on)) < 0) { + dprintf(LOG_ERR, FNAME, "setsockopt(IPV6_PKTINFO): %s", + strerror(errno)); + goto failexit; + } +#endif + + if (signal(SIGTERM, relay6_signal) == SIG_ERR) { + dprintf(LOG_WARNING, FNAME, "failed to set signal: %s", + strerror(errno)); + exit(1); + } + return; + + failexit: + exit(1); +} + +static void +relay6_signal(sig) + int sig; +{ + + switch (sig) { + case SIGTERM: + sig_flags |= SIGF_TERM; + break; + } +} + +static void +process_signals() +{ + if ((sig_flags & SIGF_TERM)) { + unlink(pid_file); + exit(0); + } +} + +static void +relay6_loop() +{ + fd_set readfds; + int e; + + while(1) { + if (sig_flags) + process_signals(); + + /* we'd rather use FD_COPY here, but it's not POSIX friendly */ + FD_ZERO(&readfds); + FD_SET(csock, &readfds); + FD_SET(ssock, &readfds); + + e = select(maxfd + 1, &readfds, NULL, NULL, NULL); + switch(e) { + case 0: /* impossible in our situation */ + errx(1, "select returned 0"); + /* NOTREACHED */ + case -1: + if (errno != EINTR) { + err(1, "select"); + /* NOTREACHED */ + } + continue; + default: + break; + } + + if (FD_ISSET(csock, &readfds)) + relay6_recv(csock, 1); + + if (FD_ISSET(ssock, &readfds)) + relay6_recv(ssock, 0); + } +} + +static void +relay6_recv(s, fromclient) + int s, fromclient; +{ + ssize_t len; + struct sockaddr_storage from; + struct in6_pktinfo *pi = NULL; + struct cmsghdr *cm; + struct dhcp6 *dh6; + struct ifid_list *ifd; + char ifname[IF_NAMESIZE]; + + rmh.msg_control = (caddr_t)rmsgctlbuf; + rmh.msg_controllen = rmsgctllen; + + rmh.msg_name = &from; + rmh.msg_namelen = sizeof (from); + + if ((len = recvmsg(s, &rmh, 0)) < 0) { + dprintf(LOG_WARNING, FNAME, "recvmsg: %s", strerror(errno)); + return; + } + + dprintf(LOG_DEBUG, FNAME, "from %s, size %d", + addr2str((struct sockaddr *)&from), len); + + if (((struct sockaddr *)&from)->sa_family != AF_INET6) { + dprintf(LOG_WARNING, FNAME, + "non-IPv6 packet is received (AF %d) ", + ((struct sockaddr *)&from)->sa_family); + return; + } + + /* get optional information as ancillary data (if available) */ + for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rmh); cm; + cm = (struct cmsghdr *)CMSG_NXTHDR(&rmh, cm)) { + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + + switch(cm->cmsg_type) { + case IPV6_PKTINFO: + pi = (struct in6_pktinfo *)CMSG_DATA(cm); + break; + } + } + if (pi == NULL) { + dprintf(LOG_WARNING, FNAME, + "failed to get the arrival interface"); + return; + } + for (ifd = TAILQ_FIRST(&ifid_list); ifd; + ifd = TAILQ_NEXT(ifd, ilink)) { + if (pi->ipi6_ifindex == ifd->ifid) + break; + } + /* + * DHCPv6 relay may receive a DHCPv6 packet from a non-listening + * interface, when a DHCPv6 server is running on that interface. + * This check prevents such reception. + */ + if (ifd == NULL && pi->ipi6_ifindex != relayifid) + return; + if (if_indextoname(pi->ipi6_ifindex, ifname) == NULL) { + dprintf(LOG_WARNING, FNAME, + "if_indextoname(id = %d): %s", + pi->ipi6_ifindex, strerror(errno)); + return; + } + + /* packet validation */ + if (len < sizeof (*dh6)) { + dprintf(LOG_INFO, FNAME, "short packet (%d bytes)", len); + return; + } + + dh6 = (struct dhcp6 *)rdatabuf; + dprintf(LOG_DEBUG, FNAME, "received %s from %s", + dhcp6msgstr(dh6->dh6_msgtype), addr2str((struct sockaddr *)&from)); + + /* + * Relay the packet according to the type. A client message or + * a relay forward message is forwarded to servers (or other relays), + * and a relay reply message is forwarded to the intended client. + */ + if (fromclient) { + switch (dh6->dh6_msgtype) { + case DH6_SOLICIT: + case DH6_REQUEST: + case DH6_CONFIRM: + case DH6_RENEW: + case DH6_REBIND: + case DH6_RELEASE: + case DH6_DECLINE: + case DH6_INFORM_REQ: + case DH6_RELAY_FORW: + relay_to_server(dh6, len, (struct sockaddr_in6 *)&from, + ifname, htonl(pi->ipi6_ifindex)); + break; + case DH6_RELAY_REPLY: + /* + * The server may send a relay reply to the client + * port. + * XXX: need to clarify the port issue + */ + relay_to_client((struct dhcp6_relay *)dh6, len, + (struct sockaddr *)&from); + break; + default: + dprintf(LOG_INFO, FNAME, + "unexpected message (%s) on the client side " + "from %s", dhcp6msgstr(dh6->dh6_msgtype), + addr2str((struct sockaddr *)&from)); + break; + } + } else { + if (dh6->dh6_msgtype != DH6_RELAY_REPLY) { + dprintf(LOG_INFO, FNAME, + "unexpected message (%s) on the server side" + "from %s", dhcp6msgstr(dh6->dh6_msgtype), + addr2str((struct sockaddr *)&from)); + return; + } + relay_to_client((struct dhcp6_relay *)dh6, len, + (struct sockaddr *)&from); + } +} + +static int +make_msgcontrol(mh, ctlbuf, buflen, pktinfo, hlim) + struct msghdr *mh; + void *ctlbuf; + socklen_t buflen; + struct in6_pktinfo *pktinfo; + int hlim; +{ + struct cmsghdr *cm; + socklen_t controllen; + + controllen = 0; + if (pktinfo) + controllen += CMSG_SPACE(sizeof (*pktinfo)); + if (hlim > 0) + controllen += CMSG_SPACE(sizeof (hlim)); + if (buflen < controllen) + return (-1); + + memset(ctlbuf, 0, buflen); + mh->msg_controllen = controllen; + mh->msg_control = ctlbuf; + + cm = (struct cmsghdr *)CMSG_FIRSTHDR(mh); + if (pktinfo) { + cm->cmsg_len = CMSG_LEN(sizeof (*pktinfo)); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + memcpy(CMSG_DATA((struct cmsghdr *)cm), pktinfo, + sizeof (*pktinfo)); + + cm = CMSG_NXTHDR(mh, cm); + } + + if (hlim > 0) { + cm->cmsg_len = CMSG_LEN(sizeof (hlim)); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_HOPLIMIT; + *(int *)CMSG_DATA((struct cmsghdr *)cm) = hlim; + + cm = CMSG_NXTHDR(mh, cm); /* just in case */ + } + + return (0); +} + +static void +relay_to_server(dh6, len, from, ifname, ifid) + struct dhcp6 *dh6; + ssize_t len; + struct sockaddr_in6 *from; + char *ifname; + unsigned int ifid; +{ + struct dhcp6_optinfo optinfo; + struct dhcp6_relay *dh6relay; + struct in6_addr linkaddr; + struct prefix_list *p; + int optlen, relaylen; + int cc; + struct msghdr mh; + static struct iovec iov[2]; + u_char relaybuf[sizeof (*dh6relay) + BUFSIZ]; + struct in6_pktinfo pktinfo; + char ctlbuf[CMSG_SPACE(sizeof (struct in6_pktinfo)) + + CMSG_SPACE(sizeof (int))]; + + /* + * Prepare a relay forward option. + */ + dhcp6_init_options(&optinfo); + + /* Relay message */ + if ((optinfo.relaymsg_msg = malloc(len)) == NULL) { + dprintf(LOG_WARNING, FNAME, + "failed to allocate memory to copy the original packet: " + "%s", strerror(errno)); + goto out; + } + optinfo.relaymsg_len = len; + memcpy(optinfo.relaymsg_msg, dh6, len); + + /* Interface-id. We always use this option. */ + if ((optinfo.ifidopt_id = malloc(sizeof (ifid))) == NULL) { + dprintf(LOG_WARNING, FNAME, + "failed to allocate memory for IFID: %s", strerror(errno)); + goto out; + } + optinfo.ifidopt_len = sizeof (ifid); + memcpy(optinfo.ifidopt_id, &ifid, sizeof (ifid)); + + /* + * Construct a relay forward message. + */ + memset(relaybuf, 0, sizeof (relaybuf)); + + dh6relay = (struct dhcp6_relay *)relaybuf; + memset(dh6relay, 0, sizeof (*dh6relay)); + dh6relay->dh6relay_msgtype = DH6_RELAY_FORW; + memcpy(&dh6relay->dh6relay_peeraddr, &from->sin6_addr, + sizeof (dh6relay->dh6relay_peeraddr)); + + /* find a global address to fill in the link address field */ + memset(&linkaddr, 0, sizeof (linkaddr)); + for (p = TAILQ_FIRST(&global_prefixes); p; p = TAILQ_NEXT(p, plink)) { + if (getifaddr(&linkaddr, ifname, &p->paddr.sin6_addr, + p->plen, 1, IN6_IFF_INVALID) == 0) /* found */ + break; + } + if (p == NULL) { + dprintf(LOG_NOTICE, FNAME, + "failed to find a global address on %s", ifname); + + /* + * When relaying a message from a client, we need a global + * link address. + * XXX: this may be too strong for the stateless case, but + * the DHCPv6 specification seems to require the behavior. + */ + if (dh6->dh6_msgtype != DH6_RELAY_FORW) + goto out; + } + + if (dh6->dh6_msgtype == DH6_RELAY_FORW) { + struct dhcp6_relay *dh6relay0 = (struct dhcp6_relay *)dh6; + + /* Relaying a Message from a Relay Agent */ + + /* + * If the hop-count in the message is greater than or equal to + * HOP_COUNT_LIMIT, the relay agent discards the received + * message. + * [RFC3315 Section 20.1.2] + */ + if (dh6relay0->dh6relay_hcnt >= DHCP6_RELAY_HOP_COUNT_LIMIT) { + dprintf(LOG_INFO, FNAME, "too many relay forwardings"); + goto out; + } + + dh6relay->dh6relay_hcnt = dh6relay0->dh6relay_hcnt + 1; + + /* + * We can keep the link-address field 0, regardless of the + * scope of the source address, since we always include + * interface-ID option. + */ + } else { + /* Relaying a Message from a Client */ + memcpy(&dh6relay->dh6relay_linkaddr, &linkaddr, + sizeof (dh6relay->dh6relay_linkaddr)); + dh6relay->dh6relay_hcnt = 0; + } + + relaylen = sizeof (*dh6relay); + if ((optlen = dhcp6_set_options(DH6_RELAY_FORW, + (struct dhcp6opt *)(dh6relay + 1), + (struct dhcp6opt *)(relaybuf + sizeof (relaybuf)), + &optinfo)) < 0) { + dprintf(LOG_INFO, FNAME, + "failed to construct relay options"); + goto out; + } + relaylen += optlen; + + /* + * Forward the message. + */ + memset(&mh, 0, sizeof (mh)); + iov[0].iov_base = relaybuf; + iov[0].iov_len = relaylen; + mh.msg_iov = iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa6_server; + mh.msg_namelen = sizeof (sa6_server); + if (IN6_IS_ADDR_MULTICAST(&sa6_server.sin6_addr)) { + memset(&pktinfo, 0, sizeof (pktinfo)); + pktinfo.ipi6_ifindex = relayifid; + if (make_msgcontrol(&mh, ctlbuf, sizeof (ctlbuf), + &pktinfo, mhops)) { + dprintf(LOG_WARNING, FNAME, + "failed to make message control data"); + goto out; + } + } + + if ((cc = sendmsg(ssock, &mh, 0)) < 0) { + dprintf(LOG_WARNING, FNAME, + "sendmsg %s failed: %s", + addr2str((struct sockaddr *)&sa6_server), strerror(errno)); + } else if (cc != relaylen) { + dprintf(LOG_WARNING, FNAME, + "failed to send a complete packet to %s", + addr2str((struct sockaddr *)&sa6_server)); + } else { + dprintf(LOG_DEBUG, FNAME, + "relay a message to a server %s", + addr2str((struct sockaddr *)&sa6_server)); + } + + out: + dhcp6_clear_options(&optinfo); +} + +static void +relay_to_client(dh6relay, len, from) + struct dhcp6_relay *dh6relay; + ssize_t len; + struct sockaddr *from; +{ + struct dhcp6_optinfo optinfo; + struct sockaddr_in6 peer; + unsigned int ifid; + char ifnamebuf[IFNAMSIZ]; + int cc; + int relayed = 0; + struct dhcp6 *dh6; + struct msghdr mh; + struct in6_pktinfo pktinfo; + static struct iovec iov[2]; + char ctlbuf[CMSG_SPACE(sizeof (struct in6_pktinfo))]; + + dprintf(LOG_DEBUG, FNAME, + "dhcp6 relay reply: hop=%d, linkaddr=%s, peeraddr=%s", + dh6relay->dh6relay_hcnt, + in6addr2str(&dh6relay->dh6relay_linkaddr, 0), + in6addr2str(&dh6relay->dh6relay_peeraddr, 0)); + + /* + * parse and validate options in the relay reply message. + */ + dhcp6_init_options(&optinfo); + if (dhcp6_get_options((struct dhcp6opt *)(dh6relay + 1), + (struct dhcp6opt *)((char *)dh6relay + len), &optinfo) < 0) { + dprintf(LOG_INFO, FNAME, "failed to parse options"); + return; + } + + /* A relay reply message must include a relay message option */ + if (optinfo.relaymsg_msg == NULL) { + dprintf(LOG_INFO, FNAME, "relay reply message from %s " + "without a relay message", addr2str(from)); + goto out; + } + + /* minimum validation for the inner message */ + if (optinfo.relaymsg_len < sizeof (struct dhcp6)) { + dprintf(LOG_INFO, FNAME, "short relay message from %s", + addr2str(from)); + goto out; + } + + /* + * Extract interface ID which should be included in relay reply + * messages to us. + */ + ifid = 0; + if (optinfo.ifidopt_id) { + if (optinfo.ifidopt_len != sizeof (ifid)) { + dprintf(LOG_INFO, FNAME, + "unexpected length (%d) for Interface ID from %s", + optinfo.ifidopt_len, addr2str(from)); + goto out; + } else { + memcpy(&ifid, optinfo.ifidopt_id, sizeof (ifid)); + ifid = ntohl(ifid); + + /* validation for ID */ + if ((if_indextoname(ifid, ifnamebuf)) == NULL) { + dprintf(LOG_INFO, FNAME, + "invalid interface ID: %x", ifid); + goto out; + } + } + } else { + dprintf(LOG_INFO, FNAME, + "Interface ID is not included from %s", addr2str(from)); + /* + * the responding server should be buggy, but we deal with it. + */ + } + + /* + * If we fail, try to get the interface from the link address. + */ + if (ifid == 0 && + !IN6_IS_ADDR_UNSPECIFIED(&dh6relay->dh6relay_linkaddr) && + !IN6_IS_ADDR_LINKLOCAL(&dh6relay->dh6relay_linkaddr)) { + if (getifidfromaddr(&dh6relay->dh6relay_linkaddr, &ifid)) + ifid = 0; + } + + if (ifid == 0) { + dprintf(LOG_INFO, FNAME, "failed to determine relay link"); + goto out; + } + + peer = sa6_client; + dh6 = (struct dhcp6 *) optinfo.relaymsg_msg; + if (dh6->dh6_msgtype != DH6_RELAY_REPLY) { + relayed++; + } else { + /* + * change dst port to server/relay port, since it's a + * reply to relay, not to a client + */ + peer.sin6_port = htons(547); /* DH6PORT_UPSTREAM */ + } + memcpy(&peer.sin6_addr, &dh6relay->dh6relay_peeraddr, + sizeof (peer.sin6_addr)); + if (IN6_IS_ADDR_LINKLOCAL(&peer.sin6_addr)) + peer.sin6_scope_id = ifid; /* XXX: we assume a 1to1 map */ + + /* construct a message structure specifying the outgoing interface */ + memset(&mh, 0, sizeof (mh)); + iov[0].iov_base = optinfo.relaymsg_msg; + iov[0].iov_len = optinfo.relaymsg_len; + mh.msg_iov = iov; + mh.msg_iovlen = 1; + mh.msg_name = &peer; + mh.msg_namelen = sizeof (peer); + memset(&pktinfo, 0, sizeof (pktinfo)); + pktinfo.ipi6_ifindex = ifid; + if (make_msgcontrol(&mh, ctlbuf, sizeof (ctlbuf), &pktinfo, 0)) { + dprintf(LOG_WARNING, FNAME, + "failed to make message control data"); + goto out; + } + + /* send packet */ + if ((cc = sendmsg(csock, &mh, 0)) < 0) { + dprintf(LOG_WARNING, FNAME, + "sendmsg to %s failed: %s", + addr2str((struct sockaddr *)&peer), strerror(errno)); + } else if (cc != optinfo.relaymsg_len) { + dprintf(LOG_WARNING, FNAME, + "failed to send a complete packet to %s", + addr2str((struct sockaddr *)&peer)); + } else { + dprintf(LOG_DEBUG, FNAME, + "relay a message to a client %s", + addr2str((struct sockaddr *)&peer)); + } + + if (relayed && scriptpath != NULL) + relay6_script(scriptpath, &peer, dh6, optinfo.relaymsg_len); + + out: + dhcp6_clear_options(&optinfo); + return; +} |