summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrzalamena <rzalamena@openbsd.org>2017-03-17 14:45:16 +0000
committerrzalamena <rzalamena@openbsd.org>2017-03-17 14:45:16 +0000
commit9cbab5830b690836fba8f7e46ae8cef69a6fd5c1 (patch)
tree3689b31c8d81168cfc9a5e906dde28f186038f22
parentAdd h and l for collapse and expand in choose mode with vi(1) keys, from (diff)
downloadwireguard-openbsd-9cbab5830b690836fba8f7e46ae8cef69a6fd5c1.tar.xz
wireguard-openbsd-9cbab5830b690836fba8f7e46ae8cef69a6fd5c1.zip
Import the DHCPv6 relay implementation.
This code was based on the dhcrelay(8) daemon and shares a lot of the structures and functions. This daemon implements the following RFCs: * RFC 3315 Section 20: Relay Agent Behavior * RFC 4649: Relay Agent Remote-ID option * RFC 6221: Lightweight DHCPv6 Relay Agent
-rw-r--r--usr.sbin/dhcrelay6/Makefile13
-rw-r--r--usr.sbin/dhcrelay6/bpf.c411
-rw-r--r--usr.sbin/dhcrelay6/dhcp.h140
-rw-r--r--usr.sbin/dhcrelay6/dhcpd.h183
-rw-r--r--usr.sbin/dhcrelay6/dhcrelay6.8210
-rw-r--r--usr.sbin/dhcrelay6/dhcrelay6.c1027
-rw-r--r--usr.sbin/dhcrelay6/dispatch.c489
-rw-r--r--usr.sbin/dhcrelay6/log.c199
-rw-r--r--usr.sbin/dhcrelay6/log.h46
-rw-r--r--usr.sbin/dhcrelay6/packet.c231
10 files changed, 2949 insertions, 0 deletions
diff --git a/usr.sbin/dhcrelay6/Makefile b/usr.sbin/dhcrelay6/Makefile
new file mode 100644
index 00000000000..6dd1fe517e6
--- /dev/null
+++ b/usr.sbin/dhcrelay6/Makefile
@@ -0,0 +1,13 @@
+# $OpenBSD: Makefile,v 1.1 2017/03/17 14:45:16 rzalamena Exp $
+
+.include <bsd.own.mk>
+
+SRCS= dhcrelay6.c dispatch.c log.c bpf.c packet.c
+PROG= dhcrelay6
+MAN= dhcrelay6.8
+
+CFLAGS+=-Wall -Wextra -Werror
+CFLAGS+=-Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
+CFLAGS+=-Wshadow -Wpointer-arith -Wsign-compare
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/dhcrelay6/bpf.c b/usr.sbin/dhcrelay6/bpf.c
new file mode 100644
index 00000000000..1289d9a504a
--- /dev/null
+++ b/usr.sbin/dhcrelay6/bpf.c
@@ -0,0 +1,411 @@
+/* $OpenBSD: bpf.c,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/* BPF socket interface code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "dhcpd.h"
+#include "log.h"
+
+ssize_t send_packet_layer3(struct interface_info *,
+ void *, size_t, struct packet_ctx *);
+
+/*
+ * Called by get_interface_list for each interface that's discovered.
+ * Opens a packet filter for each interface and adds it to the select
+ * mask.
+ */
+int
+if_register_bpf(struct interface_info *info)
+{
+ int sock;
+
+ /* Open the BPF device */
+ if ((sock = open("/dev/bpf0", O_RDWR)) == -1)
+ fatal("Can't open bpf device");
+
+ /* Set the BPF device to point at this interface. */
+ if (ioctl(sock, BIOCSETIF, &info->ifr) == -1)
+ fatal("Can't attach interface %s to bpf device", info->name);
+
+ return (sock);
+}
+
+void
+if_register_send(struct interface_info *info)
+{
+ /*
+ * If we're using the bpf API for sending and receiving, we
+ * don't need to register this interface twice.
+ */
+ info->wfdesc = info->rfdesc;
+}
+
+/* DHCPv6 BPF filters. */
+
+/*
+ * Packet filter program: 'ip6 and udp and dst port DHCP6_SERVER_PORT'
+ */
+struct bpf_insn dhcp6_bpf_sfilter[] = {
+ /* Make sure this is an IP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IPV6, 0, 5),
+
+ /* Make sure this is an UDP packet. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 3),
+
+ /* Make sure it is the right port. */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 56),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP6_SERVER_PORT, 0, 1),
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+int dhcp6_bpf_sfilter_len = sizeof(dhcp6_bpf_sfilter) / sizeof(struct bpf_insn);
+
+/*
+ * Packet filter program: 'ip6 and udp'
+ */
+struct bpf_insn dhcp6_bpf_wfilter[] = {
+ /* Make sure this is an IP packet... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IPV6, 0, 3),
+
+ /* Make sure this is an UDP packet. */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 1),
+
+ /* If we passed all the tests, ask for the whole packet. */
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ /* Otherwise, drop it. */
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+int dhcp6_bpf_wfilter_len = sizeof(dhcp6_bpf_wfilter) / sizeof(struct bpf_insn);
+
+
+void
+if_register_receive(struct interface_info *info)
+{
+ struct bpf_version v;
+ struct bpf_program p;
+ int flag = 1, sz, cmplt = 0;
+
+ /* Open a BPF device and hang it on this interface... */
+ info->rfdesc = if_register_bpf(info);
+
+ /* Make sure the BPF version is in range... */
+ if (ioctl(info->rfdesc, BIOCVERSION, &v) == -1)
+ fatal("Can't get BPF version");
+
+ if (v.bv_major != BPF_MAJOR_VERSION ||
+ v.bv_minor < BPF_MINOR_VERSION)
+ fatalx("Kernel BPF version out of range - recompile dhcpd!");
+
+ /*
+ * Set immediate mode so that reads return as soon as a packet
+ * comes in, rather than waiting for the input buffer to fill
+ * with packets.
+ */
+ if (ioctl(info->rfdesc, BIOCIMMEDIATE, &flag) == -1)
+ fatal("Can't set immediate mode on bpf device");
+
+ /* make sure kernel fills in the source ethernet address */
+ if (ioctl(info->rfdesc, BIOCSHDRCMPLT, &cmplt) == -1)
+ fatal("Can't set header complete flag on bpf device");
+
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl(info->rfdesc, BIOCGBLEN, &sz) == -1)
+ fatal("Can't get bpf buffer length");
+ info->rbuf_max = sz;
+ info->rbuf = malloc(info->rbuf_max);
+ if (!info->rbuf)
+ fatalx("Can't allocate %lu bytes for bpf input buffer.",
+ (unsigned long)info->rbuf_max);
+ info->rbuf_offset = 0;
+ info->rbuf_len = 0;
+
+ /* Set up the bpf filter program structure. */
+ p.bf_len = dhcp6_bpf_sfilter_len;
+ p.bf_insns = dhcp6_bpf_sfilter;
+ if (ioctl(info->rfdesc, BIOCSETF, &p) == -1)
+ fatal("Can't install packet filter program");
+
+ /* Set up the bpf write filter program structure. */
+ p.bf_len = dhcp6_bpf_wfilter_len;
+ p.bf_insns = dhcp6_bpf_wfilter;
+ if (ioctl(info->rfdesc, BIOCSETWF, &p) == -1)
+ fatal("Can't install write filter program");
+
+ /* Only get input packets. */
+ flag = BPF_DIRECTION_OUT;
+ if (ioctl(info->rfdesc, BIOCSDIRFILT , &flag) == -1)
+ fatal("Can't set BPF direction capture");
+
+ /* Drop them so they don't go up in the network stack. */
+ flag = 1;
+ if (ioctl(info->rfdesc, BIOCSFILDROP, &flag) == -1)
+ fatal("Can't set BPF filter drop");
+
+ /* make sure these settings cannot be changed after dropping privs */
+ if (ioctl(info->rfdesc, BIOCLOCK) == -1)
+ fatal("Failed to lock bpf descriptor");
+}
+
+ssize_t
+send_packet_layer3(struct interface_info *intf, void *raw, size_t len,
+ struct packet_ctx *pc)
+{
+ struct cmsghdr *cmsg;
+ ssize_t sendlen;
+ struct msghdr msg;
+ struct in6_pktinfo *ipi6;
+ struct sockaddr_storage ss;
+ struct iovec iov[2];
+ uint8_t cmsgbuf[
+ CMSG_SPACE(sizeof(struct in6_pktinfo))
+ ];
+
+ log_debug(" sending %ld bytes to %s:%d via %s",
+ len, v6addr2str(&ss2sin6(&pc->pc_dst)->sin6_addr),
+ ntohs(ss2sin6(&pc->pc_dst)->sin6_port), intf->name);
+
+ memset(&msg, 0, sizeof(msg));
+ iov[0].iov_base = raw;
+ iov[0].iov_len = len;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ ss = pc->pc_dst;
+ ss2sin6(&ss)->sin6_scope_id = intf->index;
+ msg.msg_name = &ss;
+ msg.msg_namelen = ss.ss_len;
+
+ /* If binded to multicast we should select an interface. */
+ if (IN6_IS_ADDR_MULTICAST(&ss2sin6(&ss)->sin6_addr)) {
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+
+ /* Use the IPV6_PKTINFO to select the interface. */
+ cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi6));
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+
+ ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ ipi6->ipi6_ifindex = intf->index;
+ }
+
+ if ((sendlen = sendmsg(pc->pc_sd, &msg, 0)) == -1) {
+ log_warn(" failed to send message");
+ return -1;
+ }
+ if (sendlen < (ssize_t)len)
+ log_warnx(" sent less bytes than expected (%ld < %ld)",
+ sendlen, len);
+
+ return sendlen;
+}
+
+ssize_t
+send_packet(struct interface_info *interface,
+ void *raw, size_t len, struct packet_ctx *pc)
+{
+ unsigned char buf[256];
+ struct iovec iov[2];
+ int result, bufp = 0;
+
+ if (pc->pc_sd != 0)
+ return send_packet_layer3(interface, raw, len, pc);
+
+ /* Assemble the headers... */
+ assemble_hw_header(buf, &bufp, pc);
+ assemble_udp_ip6_header(buf, &bufp, pc, raw, len);
+
+ /* Fire it off */
+ iov[0].iov_base = (char *)buf;
+ iov[0].iov_len = bufp;
+ iov[1].iov_base = (char *)raw;
+ iov[1].iov_len = len;
+
+ result = writev(interface->wfdesc, iov, 2);
+ if (result == -1)
+ log_warn("send_packet");
+
+ return (result);
+}
+
+ssize_t
+receive_packet(struct interface_info *interface, unsigned char *buf,
+ size_t len, struct packet_ctx *pc)
+{
+ int length = 0, offset = 0;
+ struct bpf_hdr hdr;
+
+ /*
+ * All this complexity is because BPF doesn't guarantee that
+ * only one packet will be returned at a time. We're getting
+ * what we deserve, though - this is a terrible abuse of the BPF
+ * interface. Sigh.
+ */
+
+ /* Process packets until we get one we can return or until we've
+ * done a read and gotten nothing we can return...
+ */
+ do {
+ /* If the buffer is empty, fill it. */
+ if (interface->rbuf_offset == interface->rbuf_len) {
+ length = read(interface->rfdesc, interface->rbuf,
+ interface->rbuf_max);
+ if (length <= 0)
+ return (length);
+ interface->rbuf_offset = 0;
+ interface->rbuf_len = length;
+ }
+
+ /*
+ * If there isn't room for a whole bpf header, something
+ * went wrong, but we'll ignore it and hope it goes
+ * away... XXX
+ */
+ if (interface->rbuf_len - interface->rbuf_offset <
+ sizeof(hdr)) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /* Copy out a bpf header... */
+ memcpy(&hdr, &interface->rbuf[interface->rbuf_offset],
+ sizeof(hdr));
+
+ /*
+ * If the bpf header plus data doesn't fit in what's
+ * left of the buffer, stick head in sand yet again...
+ */
+ if (interface->rbuf_offset + hdr.bh_hdrlen + hdr.bh_caplen >
+ interface->rbuf_len) {
+ interface->rbuf_offset = interface->rbuf_len;
+ continue;
+ }
+
+ /*
+ * If the captured data wasn't the whole packet, or if
+ * the packet won't fit in the input buffer, all we can
+ * do is drop it.
+ */
+ if (hdr.bh_caplen != hdr.bh_datalen) {
+ interface->rbuf_offset += hdr.bh_hdrlen =
+ hdr.bh_caplen;
+ continue;
+ }
+
+ /* Skip over the BPF header... */
+ interface->rbuf_offset += hdr.bh_hdrlen;
+
+ /* Decode the physical header... */
+ offset = decode_hw_header(interface->rbuf,
+ interface->rbuf_offset, pc);
+
+ /*
+ * If a physical layer checksum failed (dunno of any
+ * physical layer that supports this, but WTH), skip
+ * this packet.
+ */
+ if (offset < 0) {
+ interface->rbuf_offset += hdr.bh_caplen;
+ continue;
+ }
+ interface->rbuf_offset += offset;
+ hdr.bh_caplen -= offset;
+
+ /* Decode the IP and UDP headers... */
+ offset = decode_udp_ip6_header(interface->rbuf,
+ interface->rbuf_offset, pc, hdr.bh_caplen);
+
+ /* If the IP or UDP checksum was bad, skip the packet... */
+ if (offset < 0) {
+ interface->rbuf_offset += hdr.bh_caplen;
+ continue;
+ }
+ interface->rbuf_offset += offset;
+ hdr.bh_caplen -= offset;
+
+ /*
+ * If there's not enough room to stash the packet data,
+ * we have to skip it (this shouldn't happen in real
+ * life, though).
+ */
+ if (hdr.bh_caplen > len) {
+ interface->rbuf_offset += hdr.bh_caplen;
+ continue;
+ }
+
+ /* Copy out the data in the packet... */
+ memcpy(buf, interface->rbuf + interface->rbuf_offset,
+ hdr.bh_caplen);
+ interface->rbuf_offset += hdr.bh_caplen;
+ return (hdr.bh_caplen);
+ } while (!length);
+ return (0);
+}
diff --git a/usr.sbin/dhcrelay6/dhcp.h b/usr.sbin/dhcrelay6/dhcp.h
new file mode 100644
index 00000000000..5e43bbb3d83
--- /dev/null
+++ b/usr.sbin/dhcrelay6/dhcp.h
@@ -0,0 +1,140 @@
+/* $OpenBSD: dhcp.h,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalamena@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _DHCP_H_
+#define _DHCP_H_
+
+/* Maximum DHCP packet size. */
+#define DHCP_MTU_MAX 1500
+
+/*
+ * Enterprise numbers can be found at:
+ * http://www.iana.org/assignments/enterprise-numbers
+ */
+#define OPENBSD_ENTERPRISENO 30155
+#define ENTERPRISENO_LEN sizeof(uint32_t)
+
+/*
+ * DHCPv6 definitions and structures defined by the protocol
+ * specification (RFC 3315).
+ *
+ * The relay part of DHCPv6 is specified in section 20 of RFC 3315 and
+ * in the RFC 6221.
+ */
+
+/*
+ * RFC 3315 Section 5.1 Multicast Addresses:
+ * All_DHCP_Relay_Agents_and_Servers: FF02::1:2
+ * All_DHCP_Servers: FF05::1:3
+ */
+#define DHCP6_ADDR_RELAYSERVER "FF02::1:2"
+#define DHCP6_ADDR_SERVER "FF05::1:3"
+
+/* DHCPv6 client/server ports */
+#define DHCP6_CLIENT_PORT 546
+#define DHCP6_CLIENT_PORT_STR "546"
+#define DHCP6_SERVER_PORT 547
+#define DHCP6_SERVER_PORT_STR "547"
+
+/* DHCPv6 message types. */
+#define DHCP6_MT_SOLICIT 1
+#define DHCP6_MT_ADVERTISE 2
+#define DHCP6_MT_REQUEST 3
+#define DHCP6_MT_CONFIRM 4
+#define DHCP6_MT_RENEW 5
+#define DHCP6_MT_REBIND 6
+#define DHCP6_MT_REPLY 7
+#define DHCP6_MT_RELEASE 8
+#define DHCP6_MT_DECLINE 9
+#define DHCP6_MT_RECONFIGURE 10
+#define DHCP6_MT_INFORMATIONREQUEST 11
+#define DHCP6_MT_RELAYFORW 12
+#define DHCP6_MT_RELAYREPL 13
+
+/* Maximum amount of hops limit by default. */
+#define DHCP6_HOP_LIMIT 32
+
+/* DHCPv6 option definitions. */
+#define DHCP6_OPT_CLIENTID 1
+#define DHCP6_OPT_SERVERID 2
+/* IANA - Identity Association for Non-temporary Address */
+#define DHCP6_OPT_IANA 3
+/* IATA - Identity Association for Temporary Address */
+#define DHCP6_OPT_IATA 4
+/* IA Addr - Identity Association Address */
+#define DHCP6_OPT_IAADDR 5
+/* ORO - Option Request Option */
+#define DHCP6_OPT_ORO 6
+#define DHCP6_OPT_PREFERENCE 7
+#define DHCP6_OPT_ELAPSED_TIME 8
+#define DHCP6_OPT_RELAY_MSG 9
+#define DHCP6_OPT_AUTH 11
+#define DHCP6_OPT_UNICAST 12
+#define DHCP6_OPT_STATUSCODE 13
+#define DHCP6_OPT_RAPIDCOMMIT 14
+#define DHCP6_OPT_USERCLASS 15
+#define DHCP6_OPT_VENDORCLASS 16
+#define DHCP6_OPT_VENDOROPTS 17
+#define DHCP6_OPT_INTERFACEID 18
+#define DHCP6_OPT_RECONFMSG 19
+#define DHCP6_OPT_RECONFACCEPT 20
+/* Remote-ID is defined at RFC 4649. */
+#define DHCP6_OPT_REMOTEID 37
+
+/* DHCP option used in DHCP request/reply/relay packets. */
+struct dhcp6_option {
+ /* DHCP option code. */
+ uint16_t dso_code;
+ /* DHCP option payload length. */
+ uint16_t dso_length;
+ /* DHCP option data. */
+ uint8_t dso_data[];
+} __packed;
+
+/* Client / Server message format. */
+struct dhcp6_packet {
+ uint8_t ds_msgtype; /* message type */
+ uint8_t ds_transactionid[3]; /* transaction id */
+ struct dhcp6_option ds_options[];
+} __packed;
+
+/* Relay Agent/Server message format. */
+struct dhcp6_relay_packet {
+ /* Message type: DHCP6_MT_RELAYFORW or DHCP6_MT_RELAYREPL */
+ uint8_t dsr_msgtype;
+
+ /* Number of relay agents that relayed this message. */
+ uint8_t dsr_hopcount;
+
+ /*
+ * A global or site local address used by the server to Identify
+ * the client link.
+ */
+ struct in6_addr dsr_linkaddr;
+
+ /*
+ * The address of the client or relay agent from which the relayed
+ * message was received.
+ */
+ struct in6_addr dsr_peer;
+
+ /* Must include a relay message option. */
+ struct dhcp6_option dsr_options[];
+} __packed;
+
+#endif /* _DHCP_H_ */
diff --git a/usr.sbin/dhcrelay6/dhcpd.h b/usr.sbin/dhcrelay6/dhcpd.h
new file mode 100644
index 00000000000..41703cc6893
--- /dev/null
+++ b/usr.sbin/dhcrelay6/dhcpd.h
@@ -0,0 +1,183 @@
+/* $OpenBSD: dhcpd.h,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalamena@openbsd.org>
+ * Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#ifndef _DHCPD_H_
+#define _DHCPD_H_
+
+#include <sys/queue.h>
+
+#define DHCRELAY6_USER "_dhcp"
+
+/* Maximum size of client hardware address. */
+#define CHADDR_SIZE 16
+
+/* Packet context for assembly/disassembly. */
+struct packet_ctx {
+ /* Client or server socket descriptor. */
+ int pc_sd;
+
+ /* Layer 2 address information. */
+ uint8_t pc_htype;
+ uint8_t pc_hlen;
+ uint8_t pc_smac[CHADDR_SIZE];
+ uint8_t pc_dmac[CHADDR_SIZE];
+
+ /* Layer 3 address information. */
+ uint16_t pc_ethertype;
+ struct sockaddr_storage pc_srcorig;
+ struct sockaddr_storage pc_src;
+ struct sockaddr_storage pc_dst;
+
+ /* Relay Agent Information data. */
+ uint8_t *pc_raidata;
+ size_t pc_raidatalen;
+ uint32_t pc_enterpriseno;
+ uint8_t *pc_remote;
+ size_t pc_remotelen;
+};
+
+struct hardware {
+ u_int8_t htype;
+ u_int8_t hlen;
+ u_int8_t haddr[CHADDR_SIZE];
+};
+
+/* DHCP relaying modes. */
+enum dhcp_relay_mode {
+ DRM_UNKNOWN,
+ DRM_LAYER2,
+ DRM_LAYER3,
+};
+
+struct interface_info {
+ struct hardware hw_address;
+ struct in_addr primary_address;
+ char name[IFNAMSIZ];
+ int rfdesc;
+ int wfdesc;
+ unsigned char *rbuf;
+ size_t rbuf_max;
+ size_t rbuf_offset;
+ size_t rbuf_len;
+ struct ifreq ifr;
+ int noifmedia;
+ int errors;
+ int dead;
+ uint16_t index;
+
+ int ipv6; /* Has any IPv6 address. */
+ int gipv6; /* Has global IPv6 address. */
+ struct in6_addr linklocal; /* IPv6 link-local address. */
+
+ TAILQ_ENTRY(interface_info) entry;
+};
+TAILQ_HEAD(intfq, interface_info);
+
+struct timeout {
+ struct timeout *next;
+ time_t when;
+ void (*func)(void *);
+ void *what;
+};
+
+struct protocol {
+ struct protocol *next;
+ int fd;
+ void (*handler)(struct protocol *);
+ void *local;
+};
+
+struct server_list {
+ struct interface_info *intf;
+ struct sockaddr_storage to;
+ int fd;
+ int siteglobaladdr;
+ TAILQ_ENTRY(server_list) entry;
+};
+TAILQ_HEAD(serverq, server_list);
+
+/* bpf.c */
+int if_register_bpf(struct interface_info *);
+void if_register_send(struct interface_info *);
+void if_register_receive(struct interface_info *);
+ssize_t send_packet(struct interface_info *,
+ void *, size_t, struct packet_ctx *);
+ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
+ struct packet_ctx *);
+
+/* dispatch.c */
+extern void (*bootp_packet_handler)(struct interface_info *,
+ void *, size_t, struct packet_ctx *);
+void setup_iflist(void);
+struct interface_info *iflist_getbyname(const char *);
+struct interface_info *iflist_getbyindex(unsigned int);
+struct interface_info *iflist_getbyaddr6(struct in6_addr *);
+int if_hasaddress(struct interface_info *);
+struct interface_info *register_interface(const char *,
+ void (*)(struct protocol *));
+void dispatch(void);
+void got_one(struct protocol *);
+void add_protocol(char *, int, void (*)(struct protocol *), void *);
+void remove_protocol(struct protocol *);
+
+/* packet.c */
+void assemble_hw_header(unsigned char *, int *, struct packet_ctx *);
+void assemble_udp_ip6_header(unsigned char *, int *, struct packet_ctx *,
+ unsigned char *, int);
+ssize_t decode_hw_header(unsigned char *, int, struct packet_ctx *);
+ssize_t decode_udp_ip6_header(unsigned char *, int, struct packet_ctx *,
+ size_t);
+
+/* dhcrelay6.c */
+const char *v6addr2str(struct in6_addr *);
+
+extern struct intfq intflist;
+extern struct serverq svlist;
+extern time_t cur_time;
+
+static inline struct sockaddr_in6 *
+ss2sin6(struct sockaddr_storage *ss)
+{
+ return ((struct sockaddr_in6 *)ss);
+}
+
+#endif /* _DHCPD_H_ */
diff --git a/usr.sbin/dhcrelay6/dhcrelay6.8 b/usr.sbin/dhcrelay6/dhcrelay6.8
new file mode 100644
index 00000000000..4e6b26e7daf
--- /dev/null
+++ b/usr.sbin/dhcrelay6/dhcrelay6.8
@@ -0,0 +1,210 @@
+.\" $OpenBSD: dhcrelay6.8,v 1.1 2017/03/17 14:45:16 rzalamena Exp $
+.\"
+.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+.\"
+.\" This software has been written for the Internet Software Consortium
+.\" by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+.\" Enterprises. To learn more about the Internet Software Consortium,
+.\" see ``http://www.isc.org/isc''. To learn more about Vixie
+.\" Enterprises, see ``http://www.vix.com''.
+.\"
+.Dd $Mdocdate: March 17 2017 $
+.Dt DHCRELAY6 8
+.Os
+.Sh NAME
+.Nm dhcrelay6
+.Nd Dynamic Host Configuration Protocol relay agent
+.Sh SYNOPSIS
+.Nm
+.Op Fl dlov
+.Op Fl E Ar enterprise-number
+.Op Fl I Ar interface-id
+.Op Fl R Ar remote-id
+.Fl i Ar interface
+.Ar destination
+.Op Ar ... destinationN
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a means for relaying DHCPv6 requests from a subnet to
+which no DHCP server is directly connected to one or more DHCPv6 servers
+on other subnets.
+.Pp
+.Nm
+listens for DHCPv6 requests on a given interface.
+When a query is received,
+.Nm
+forwards it to the list of DHCP destinations specified on the command
+line.
+When a reply is received, it is sent on the network from whence the
+original request came.
+.Pp
+The
+.Ar destination
+might be an address followed by an '%' and interface or just an
+interface (e.g. '2001:db8::1%em0' or 'em1').
+When no address is specified
+.Nm
+will use multicast in the specified interface.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl E Ar enterprise-number
+Choose the
+.Ar enterprise-number
+that will be used by the Remote-ID option (this only has effect when
+using
+.Fl R Ar remote-id
+).
+.It Fl I Ar interface-id
+The
+.Ar interface-id
+relay agent information option value that
+.Nm
+should use on relayed packets.
+If this option is not specified it will use the interface name by
+default.
+.Pp
+Avoid using this option when using Lightweight DHCPv6 Relay Mode
+(layer 2 relay), otherwise
+.Nm
+will always send replies back to the client interface which will break
+networks with multiple DHCPv6 layer 2 relay agents.
+.It Fl i Ar interface
+The name of the network interface that
+.Nm
+will received the client DHCPv6 requests.
+For layer 3 mode at least one IPv6 local, site or global address has to
+be configured on this interface.
+.It Fl l
+Use the Lightweight DHCPv6 Relay Agent mode (layer 2 relaying).
+.It Fl o
+Add the Interface-ID option.
+This option is activated by default when using layer 2 relaying.
+.It Fl R
+Enable and add the specified Relay Agent Remote-ID option to identify
+this relay segment.
+.It Fl v
+Debug mode.
+This option will make
+.Nm
+run in the foreground, log to
+.Em stderr
+and show verbose messages.
+.El
+.Sh EXAMPLES
+Relay multicast packets in the current network to an unicast address
+(relay must have a global address in em0):
+.Pp
+.Dl # dhcrelay6 -i em0 2001:db8::1000%em0
+.Pp
+Listen to one subnet and multicast DHCPv6 packets to another
+(requires at least link-local addresses):
+.Pp
+.Dl # dhcrelay6 -i em0 em1
+.Pp
+Relay DHCPv6 packets with Interface-ID (option 18) using the input
+interface as its content:
+.Pp
+.Dl # dhcrelay6 -o -i em0 2001:db8::1000%em0
+.Pp
+Same thing as before but with a custom Interface-ID:
+.Pp
+.Dl # dhcrelay6 -o -I \(dqOpenBSD Router 1\(dq -i em0 2001:db8::1000%em0
+.Pp
+Use Lightweight DHCPv6 Relay Agent (layer 2 relay) in a bridged or
+switched network (no IPv6 address required). Only makes sense when
+.Ar em0
+and
+.Ar em1
+are configured in a
+.Xr bridge 4 ,
+since
+.Nm
+needs to drop the original DHCPv6 packets and send modified ones with
+Interface-ID option.
+.Pp
+.Dl # dhcrelay6 -l -i em0 em1
+.Pp
+Identify a segment using Lightweight DHCPv6 Relay Agent (layer 2
+relay) with Remote-ID (option 37) instead of Interface-ID:
+.Pp
+.Dl # dhcrelay6 -l -R \(dqOpenBSD Router A\(dq -i em0 em1
+.Pp
+.Sh SEE ALSO
+.Xr bridge 4 ,
+.Xr dhcrelay 8
+.Sh STANDARDS
+.Rs
+.%A R. Droms
+.%A J. Bound
+.%A B. Volz
+.%A T. Lemon
+.%A C. Perkins
+.%A M. Carney
+.%D July 2003
+.%R RFC 3315
+.%T Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
+.Re
+.Pp
+.Rs
+.%A B. Volz
+.%D August 2006
+.%R RFC 4649
+.%T Dynamic Host Configuration Protocol for IPv6 (DHCPv6) Relay Agent Remote-ID Option
+.Re
+.Pp
+.Rs
+.%A D. Miles
+.%A S. Ooghe
+.%A W. Dec
+.%A S. Krishnan
+.%A A. Kavanagh
+.%D May 2011
+.%R RFC 6221
+.%T Lightweight DHCPv6 Relay Agent
+.Re
+.Sh AUTHORS
+.An -nosplit
+.Xr dhcrelay 8
+was written by
+.An Ted Lemon Aq Mt mellon@fugue.com
+and reworked by
+.An Henning Brauer Aq Mt henning@openbsd.org .
+.Pp
+IPv6 support was implemented by
+.An Rafael Zalamena Aq Mt rzalamena@openbsd.org .
diff --git a/usr.sbin/dhcrelay6/dhcrelay6.c b/usr.sbin/dhcrelay6/dhcrelay6.c
new file mode 100644
index 00000000000..c27da6bfc03
--- /dev/null
+++ b/usr.sbin/dhcrelay6/dhcrelay6.c
@@ -0,0 +1,1027 @@
+/* $OpenBSD: dhcrelay6.c,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalamena@openbsd.org>
+ * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org>
+ * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "dhcpd.h"
+#include "log.h"
+
+/*
+ * RFC 3315 Section 5.1 Multicast Addresses:
+ * All_DHCP_Relay_Agents_and_Servers: FF02::1:2
+ * All_DHCP_Servers: FF05::1:3
+ */
+struct in6_addr in6alldhcprelay = {
+ {{ 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0, 0x02 }}
+};
+struct in6_addr in6alldhcp = {
+ {{ 0xff, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0, 0x03 }}
+};
+
+__dead void usage(void);
+struct server_list *parse_destination(const char *);
+int rdaemon(int);
+void relay6_setup(void);
+int s6fromaddr(struct sockaddr_in6 *, const char *, const char *, int);
+char *print_hw_addr(int, int, unsigned char *);
+const char *dhcp6type2str(uint8_t);
+int relay6_pushrelaymsg(struct packet_ctx *, struct interface_info *,
+ uint8_t *, size_t *, size_t);
+int relay6_poprelaymsg(struct packet_ctx *, struct interface_info **,
+ uint8_t *, size_t *);
+void rai_configure(struct packet_ctx *, struct interface_info *);
+void relay6_logsrcaddr(struct packet_ctx *, struct interface_info *,
+ uint8_t);
+void relay6(struct interface_info *, void *, size_t,
+ struct packet_ctx *);
+void mcast6_recv(struct protocol *);
+
+/* Shared variables */
+int clientsd;
+int serversd;
+int oflag;
+time_t cur_time;
+
+struct intfq intflist;
+struct serverq svlist;
+struct interface_info *interfaces;
+char *rai_data;
+size_t rai_datalen;
+uint32_t enterpriseno = OPENBSD_ENTERPRISENO;
+char *remote_data;
+size_t remote_datalen;
+enum dhcp_relay_mode drm = DRM_LAYER3;
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dlov] [-I interface-id] "
+ "[-R remote-id] -i interface\n\tdestination ...\n",
+ __progname);
+ exit(1);
+}
+
+struct server_list *
+parse_destination(const char *dest)
+{
+ struct server_list *sp;
+ const char *ifname;
+ char buf[128];
+
+ if ((sp = calloc(1, sizeof(*sp))) == NULL)
+ fatal("calloc");
+ TAILQ_INSERT_HEAD(&svlist, sp, entry);
+
+ /* Detect interface only destinations. */
+ if ((sp->intf = iflist_getbyname(dest)) != NULL)
+ return sp;
+
+ /* Split address from interface and save it. */
+ ifname = strchr(dest, '%');
+ if (ifname == NULL)
+ fatalx("%s doesn't specify an output interface", dest);
+
+ if (strlcpy(buf, dest, sizeof(buf)) >= sizeof(buf))
+ fatalx("%s is an invalid IPv6 address", dest);
+
+ /* Remove '%' from the address string. */
+ buf[ifname - dest] = 0;
+ if ((sp->intf = iflist_getbyname(ifname + 1)) == NULL)
+ fatalx("interface '%s' not found", ifname);
+ if (s6fromaddr(ss2sin6(&sp->to), buf,
+ DHCP6_SERVER_PORT_STR, 1) == -1)
+ fatalx("%s: unknown host", buf);
+
+ /*
+ * User configured a non-local address, we must require a
+ * proper address to route this.
+ */
+ if (!IN6_IS_ADDR_LINKLOCAL(&ss2sin6(&sp->to)->sin6_addr))
+ sp->siteglobaladdr = 1;
+
+ return sp;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int devnull = -1, daemonize = 1, debug = 0;
+ const char *errp;
+ struct passwd *pw;
+ int ch;
+
+ log_init(1, LOG_DAEMON); /* log to stderr until daemonized */
+ log_setverbose(1);
+
+ setup_iflist();
+
+ while ((ch = getopt(argc, argv, "dE:I:i:loR:v")) != -1) {
+ switch (ch) {
+ case 'd':
+ daemonize = 0;
+ break;
+ case 'E':
+ enterpriseno = strtonum(optarg, 1, UINT32_MAX, &errp);
+ if (errp != NULL)
+ fatalx("invalid enterprise number: %s", errp);
+ break;
+ case 'I':
+ rai_data = optarg;
+ rai_datalen = strlen(optarg);
+ if (rai_datalen == 0)
+ fatalx("can't use empty Interface-ID");
+ break;
+ case 'i':
+ if (interfaces != NULL)
+ usage();
+
+ interfaces = iflist_getbyname(optarg);
+ if (interfaces == NULL)
+ fatalx("interface '%s' not found", optarg);
+ break;
+ case 'l':
+ drm = DRM_LAYER2;
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'R':
+ remote_data = optarg;
+ remote_datalen = strlen(remote_data);
+ if (remote_datalen == 0)
+ fatalx("can't use empty Remote-ID");
+ break;
+ case 'v':
+ daemonize = 0;
+ debug = 1;
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ while (argc > 0) {
+ parse_destination(argv[0]);
+ argc--;
+ argv++;
+ }
+
+ if (daemonize) {
+ devnull = open(_PATH_DEVNULL, O_RDWR, 0);
+ if (devnull == -1)
+ fatal("open(%s)", _PATH_DEVNULL);
+ }
+ if (interfaces == NULL)
+ fatalx("no interface given");
+ if (TAILQ_EMPTY(&svlist))
+ fatalx("no destination selected");
+
+ relay6_setup();
+ bootp_packet_handler = relay6;
+
+ tzset();
+ time(&cur_time);
+
+ if ((pw = getpwnam(DHCRELAY6_USER)) == NULL)
+ fatalx("user \"%s\" not found", DHCRELAY6_USER);
+ if (chroot(_PATH_VAREMPTY) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("can't drop privileges");
+
+ if (daemonize) {
+ if (rdaemon(devnull) == -1)
+ fatal("rdaemon");
+ }
+ log_init(daemonize == 0, LOG_DAEMON); /* stop loggoing to stderr */
+ log_setverbose(debug);
+
+ if (pledge("inet stdio route", NULL) == -1)
+ fatalx("pledge");
+
+ dispatch();
+ /* not reached */
+
+ exit(0);
+}
+
+int
+rdaemon(int devnull)
+{
+ if (devnull == -1) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (fcntl(devnull, F_GETFL) == -1)
+ return (-1);
+
+ switch (fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ if (setsid() == -1)
+ return (-1);
+
+ (void)dup2(devnull, STDIN_FILENO);
+ (void)dup2(devnull, STDOUT_FILENO);
+ (void)dup2(devnull, STDERR_FILENO);
+ if (devnull > 2)
+ (void)close(devnull);
+
+ return (0);
+}
+
+int
+s6fromaddr(struct sockaddr_in6 *sin6, const char *addr, const char *serv,
+ int passive)
+{
+ struct sockaddr_in6 *sin6p;
+ struct addrinfo *aip;
+ struct addrinfo ai;
+ int rv;
+
+ memset(&ai, 0, sizeof(ai));
+ ai.ai_family = PF_INET6;
+ ai.ai_socktype = SOCK_DGRAM;
+ ai.ai_protocol = IPPROTO_UDP;
+ ai.ai_flags = (passive) ? AI_PASSIVE : 0;
+ if ((rv = getaddrinfo(addr, serv, &ai, &aip)) != 0) {
+ log_debug("getaddrinfo: %s", gai_strerror(rv));
+ return -1;
+ }
+
+ sin6p = (struct sockaddr_in6 *)aip->ai_addr;
+ *sin6 = *sin6p;
+
+ freeaddrinfo(aip);
+
+ return 0;
+}
+
+void
+relay6_setup(void)
+{
+ struct interface_info *intf;
+ struct server_list *sp;
+ int flag = 1;
+ struct sockaddr_in6 sin6;
+ struct ipv6_mreq mreq6;
+
+ /* Don't allow disabled interfaces. */
+ TAILQ_FOREACH(sp, &svlist, entry) {
+ if (sp->intf == NULL)
+ continue;
+
+ if (sp->intf->dead)
+ fatalx("interface '%s' is down", sp->intf->name);
+ }
+
+ /* Check for layer 2 dependencies. */
+ if (drm == DRM_LAYER2) {
+ TAILQ_FOREACH(sp, &svlist, entry) {
+ sp->intf = register_interface(sp->intf->name,
+ got_one);
+ if (sp->intf == NULL)
+ fatalx("destination interface "
+ "registration failed");
+ }
+ interfaces = register_interface(interfaces->name, got_one);
+ if (interfaces == NULL)
+ fatalx("input interface not configured");
+
+ return;
+ }
+
+ /*
+ * Layer 3 requires at least one IPv6 address on all configured
+ * interfaces.
+ */
+ TAILQ_FOREACH(sp, &svlist, entry) {
+ if (!sp->intf->ipv6)
+ fatalx("%s: no IPv6 address configured",
+ sp->intf->name);
+
+ if (sp->siteglobaladdr && !sp->intf->gipv6)
+ fatalx("%s: no IPv6 site/global address configured",
+ sp->intf->name);
+ }
+ if (!interfaces->ipv6)
+ fatalx("%s: no IPv6 address configured", interfaces->name);
+
+ /* Setup the client side socket. */
+ clientsd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (clientsd == -1)
+ fatal("socket");
+
+ if (setsockopt(clientsd, SOL_SOCKET, SO_REUSEPORT, &flag,
+ sizeof(flag)) == -1)
+ fatal("setsockopt(SO_REUSEPORT)");
+
+ if (s6fromaddr(&sin6, NULL, DHCP6_SERVER_PORT_STR, 1) == -1)
+ fatalx("s6fromaddr");
+ if (bind(clientsd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1)
+ fatal("bind");
+
+ if (setsockopt(clientsd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &flag,
+ sizeof(flag)) == -1)
+ fatal("setsockopt(IPV6_RECVPKTINFO)");
+
+ memset(&mreq6, 0, sizeof(mreq6));
+ if (s6fromaddr(&sin6, DHCP6_ADDR_RELAYSERVER, NULL, 0) == -1)
+ fatalx("s6fromaddr");
+ memcpy(&mreq6.ipv6mr_multiaddr, &sin6.sin6_addr,
+ sizeof(mreq6.ipv6mr_multiaddr));
+ TAILQ_FOREACH(intf, &intflist, entry) {
+ /* Skip interfaces without IPv6. */
+ if (!intf->ipv6)
+ continue;
+
+ mreq6.ipv6mr_interface = intf->index;
+ if (setsockopt(clientsd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq6, sizeof(mreq6)) == -1)
+ fatal("setsockopt(IPV6_JOIN_GROUP)");
+ }
+
+ add_protocol("clientsd", clientsd, mcast6_recv, &clientsd);
+
+ /* Setup the server side socket. */
+ serversd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (serversd == -1)
+ fatal("socket");
+
+ if (setsockopt(serversd, SOL_SOCKET, SO_REUSEPORT, &flag,
+ sizeof(flag)) == -1)
+ fatal("setsockopt(SO_REUSEPORT)");
+
+ if (s6fromaddr(&sin6, NULL, DHCP6_SERVER_PORT_STR, 1) == -1)
+ fatalx("s6fromaddr");
+ if (bind(serversd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1)
+ fatal("bind");
+
+ if (setsockopt(serversd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &flag,
+ sizeof(flag)) == -1)
+ fatal("setsockopt(IPV6_RECVPKTINFO)");
+
+ add_protocol("serversd", serversd, mcast6_recv, &serversd);
+}
+
+char *
+print_hw_addr(int htype, int hlen, unsigned char *data)
+{
+ static char habuf[49];
+ char *s = habuf;
+ int i, j, slen = sizeof(habuf);
+
+ if (htype == 0 || hlen == 0) {
+bad:
+ strlcpy(habuf, "<null>", sizeof habuf);
+ return habuf;
+ }
+
+ for (i = 0; i < hlen; i++) {
+ j = snprintf(s, slen, "%02x", data[i]);
+ if (j <= 0 || j >= slen)
+ goto bad;
+ j = strlen (s);
+ s += j;
+ slen -= (j + 1);
+ *s++ = ':';
+ }
+ *--s = '\0';
+ return habuf;
+}
+
+const char *
+v6addr2str(struct in6_addr *addr)
+{
+ static int bufpos = 0;
+ static char buf[3][256];
+
+ bufpos = (bufpos + 1) % 3;
+ buf[bufpos][0] = '[';
+ if (inet_ntop(AF_INET6, addr, &buf[bufpos][1],
+ sizeof(buf[bufpos])) == NULL)
+ return "[unknown]";
+
+ strlcat(buf[bufpos], "]", sizeof(buf[bufpos]));
+
+ return buf[bufpos];
+}
+
+const char *
+dhcp6type2str(uint8_t msgtype)
+{
+ switch (msgtype) {
+ case DHCP6_MT_REQUEST:
+ return "REQUEST";
+ case DHCP6_MT_RENEW:
+ return "RENEW";
+ case DHCP6_MT_REBIND:
+ return "REBIND";
+ case DHCP6_MT_RELEASE:
+ return "RELEASE";
+ case DHCP6_MT_DECLINE:
+ return "DECLINE";
+ case DHCP6_MT_INFORMATIONREQUEST:
+ return "INFORMATION-REQUEST";
+ case DHCP6_MT_SOLICIT:
+ return "SOLICIT";
+ case DHCP6_MT_ADVERTISE:
+ return "ADVERTISE";
+ case DHCP6_MT_CONFIRM:
+ return "CONFIRM";
+ case DHCP6_MT_REPLY:
+ return "REPLY";
+ case DHCP6_MT_RECONFIGURE:
+ return "RECONFIGURE";
+ case DHCP6_MT_RELAYREPL:
+ return "RELAY-REPLY";
+ case DHCP6_MT_RELAYFORW:
+ return "RELAY-FORWARD";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+int
+relay6_pushrelaymsg(struct packet_ctx *pc, struct interface_info *intf,
+ uint8_t *p, size_t *plen, size_t ptotal)
+{
+ struct dhcp6_relay_packet *dsr;
+ struct dhcp6_option *dso;
+ size_t rmlen, dhcplen, optoff;
+ size_t railen, remotelen;
+
+ if (pc->pc_raidata != NULL)
+ railen = sizeof(*dso) + pc->pc_raidatalen;
+ else
+ railen = 0;
+
+ if (pc->pc_remote)
+ remotelen = sizeof(*dso) + ENTERPRISENO_LEN +
+ pc->pc_remotelen;
+ else
+ remotelen = 0;
+
+ /*
+ * Check if message bigger than MTU and log (RFC 6221
+ * Section 5.3.1).
+ */
+ dhcplen = sizeof(*dsr) + railen + remotelen + sizeof(*dso) + *plen;
+ rmlen = sizeof(struct ether_header) + sizeof(struct ip6_hdr) +
+ sizeof(struct udphdr) + dhcplen;
+ if (rmlen > ptotal) {
+ log_info("Relay message too big");
+ return -1;
+ }
+
+ /* Move the DHCP payload to option. */
+ optoff = sizeof(*dsr) + railen + remotelen + sizeof(*dso);
+ memmove(p + optoff, p, *plen);
+
+ /* Write the new DHCP packet header for relay-message. */
+ dsr = (struct dhcp6_relay_packet *)p;
+ dsr->dsr_msgtype = DHCP6_MT_RELAYFORW;
+
+ /*
+ * When the destination is All_DHCP_Relay_Agents_and_Servers we
+ * start the hop count from zero, otherwise set it to
+ * DHCP6_HOP_LIMIT to limit the packet to a single network.
+ */
+ if (memcmp(&ss2sin6(&pc->pc_dst)->sin6_addr,
+ &in6alldhcprelay, sizeof(in6alldhcprelay)) == 0)
+ dsr->dsr_hopcount = 0;
+ else
+ dsr->dsr_hopcount = DHCP6_HOP_LIMIT;
+
+ /*
+ * XXX RFC 6221 Section 6.1: layer 2 mode does not set
+ * linkaddr, but we'll use our link-local always to identify the
+ * interface where the packet came in so we don't need to keep
+ * the interface addresses updated.
+ */
+ dsr->dsr_linkaddr = intf->linklocal;
+
+ memcpy(&dsr->dsr_peer, &ss2sin6(&pc->pc_src)->sin6_addr,
+ sizeof(dsr->dsr_peer));
+
+ /* Append Interface-ID DHCP option to identify this segment. */
+ if (railen > 0) {
+ dso = dsr->dsr_options;
+ dso->dso_code = htons(DHCP6_OPT_INTERFACEID);
+ dso->dso_length = htons(pc->pc_raidatalen);
+ memcpy(dso->dso_data, pc->pc_raidata, pc->pc_raidatalen);
+ }
+
+ /* Append the Remote-ID DHCP option to identify this segment. */
+ if (remotelen > 0) {
+ dso = (struct dhcp6_option *)
+ ((uint8_t *)dsr->dsr_options + railen);
+ dso->dso_code = htons(DHCP6_OPT_REMOTEID);
+ dso->dso_length =
+ htons(sizeof(pc->pc_enterpriseno) + pc->pc_remotelen);
+ memcpy(dso->dso_data, &pc->pc_enterpriseno,
+ sizeof(pc->pc_enterpriseno));
+ memcpy(dso->dso_data + sizeof(pc->pc_enterpriseno),
+ pc->pc_remote, pc->pc_remotelen);
+ }
+
+ /* Write the Relay-Message option header. */
+ dso = (struct dhcp6_option *)
+ ((uint8_t *)dsr->dsr_options + railen + remotelen);
+ dso->dso_code = htons(DHCP6_OPT_RELAY_MSG);
+ dso->dso_length = htons(*plen);
+
+ /* Update the packet length. */
+ *plen = dhcplen;
+
+ return 0;
+}
+
+int
+relay6_poprelaymsg(struct packet_ctx *pc, struct interface_info **intf,
+ uint8_t *p, size_t *plen)
+{
+ struct dhcp6_relay_packet *dsr = (struct dhcp6_relay_packet *)p;
+ struct dhcp6_packet *ds = NULL;
+ struct dhcp6_option *dso;
+ struct in6_addr linkaddr;
+ size_t pleft = *plen, ifnamelen = 0;
+ size_t dsolen, dhcplen = 0;
+ uint16_t optcode;
+ char ifname[64];
+
+ *intf = NULL;
+
+ /* Sanity check: this is a relay message of the right type. */
+ if (dsr->dsr_msgtype != DHCP6_MT_RELAYREPL) {
+ log_debug("Invalid relay-message (%s) to pop",
+ dhcp6type2str(dsr->dsr_msgtype));
+ return -1;
+ }
+
+ /* Set the client address based on relay message. */
+ ss2sin6(&pc->pc_dst)->sin6_addr = dsr->dsr_peer;
+ linkaddr = dsr->dsr_linkaddr;
+
+ dso = dsr->dsr_options;
+ pleft -= sizeof(*dsr);
+ while (pleft > sizeof(*dso)) {
+ optcode = ntohs(dso->dso_code);
+ dsolen = sizeof(*dso) + ntohs(dso->dso_length);
+
+ /* Sanity check: do we have the payload? */
+ if (dsolen > pleft) {
+ log_debug("invalid packet: payload greater than "
+ "packet content (%ld, bytes left %ld)",
+ dsolen, pleft);
+ return -1;
+ }
+
+ /* Use the interface suggested by the packet. */
+ if (optcode == DHCP6_OPT_INTERFACEID) {
+ ifnamelen = dsolen - sizeof(*dso);
+ if (ifnamelen >= sizeof(ifname)) {
+ log_info("received interface id with "
+ "truncated interface name");
+ ifnamelen = sizeof(ifname) - 1;
+ }
+
+ memcpy(ifname, dso->dso_data, ifnamelen);
+ ifname[ifnamelen] = 0;
+
+ dso = (struct dhcp6_option *)
+ ((uint8_t *)dso + dsolen);
+ pleft -= dsolen;
+ continue;
+ }
+
+ /* Ignore unsupported options. */
+ if (optcode != DHCP6_OPT_RELAY_MSG) {
+ log_debug("ignoring option type %d", optcode);
+ dso = (struct dhcp6_option *)
+ ((uint8_t *)dso + dsolen);
+ pleft -= dsolen;
+ continue;
+ }
+
+ /* Save the pointer for the DHCP payload. */
+ ds = (struct dhcp6_packet *)dso->dso_data;
+ dhcplen = ntohs(dso->dso_length);
+
+ dso = (struct dhcp6_option *)((uint8_t *)dso + dsolen);
+ pleft -= dsolen;
+ }
+ if (ds == NULL || dhcplen == 0) {
+ log_debug("Could not find relay-message option");
+ return -1;
+ }
+
+ /* Move the encapsulated DHCP payload. */
+ memmove(p, ds, dhcplen);
+ *plen = dhcplen;
+
+ /*
+ * If the new message is for the client, we must change the
+ * destination port to the client's, otherwise keep the port
+ * for the next relay.
+ */
+ ds = (struct dhcp6_packet *)p;
+ if (ds->ds_msgtype != DHCP6_MT_RELAYREPL)
+ ss2sin6(&pc->pc_dst)->sin6_port =
+ htons(DHCP6_CLIENT_PORT);
+
+ /* No Interface-ID specified. */
+ if (ifnamelen == 0)
+ goto use_linkaddr;
+
+ /* Look out for the specified interface, */
+ if ((*intf = iflist_getbyname(ifname)) == NULL) {
+ log_debug(" Interface-ID found, but no interface matches.");
+
+ /*
+ * Use client interface as fallback, but try
+ * link-address (if any) before giving up.
+ */
+ *intf = interfaces;
+ }
+
+ use_linkaddr:
+ /* Use link-addr to determine output interface if present. */
+ if (memcmp(&linkaddr, &in6addr_any, sizeof(linkaddr)) != 0) {
+ if ((*intf = iflist_getbyaddr6(&linkaddr)) != NULL)
+ return 0;
+
+ log_debug("Could not find interface using "
+ "address %s", v6addr2str(&linkaddr));
+ }
+
+ return 0;
+}
+
+void
+rai_configure(struct packet_ctx *pc, struct interface_info *intf)
+{
+ if (remote_data != NULL) {
+ pc->pc_remote = remote_data;
+ pc->pc_remotelen = remote_datalen;
+ pc->pc_enterpriseno = htonl(enterpriseno);
+ }
+
+ /* Layer-2 must include Interface-ID (Option 18). */
+ if (drm == DRM_LAYER2)
+ goto select_rai;
+
+ /* User did not configure Interface-ID. */
+ if (oflag == 0)
+ return;
+
+ select_rai:
+ if (rai_data == NULL) {
+ pc->pc_raidata = intf->name;
+ pc->pc_raidatalen = strlen(intf->name);
+ } else {
+ pc->pc_raidata = rai_data;
+ pc->pc_raidatalen = rai_datalen;
+ }
+}
+
+void
+relay6_logsrcaddr(struct packet_ctx *pc, struct interface_info *intf,
+ uint8_t msgtype)
+{
+ const char *type;
+
+ type = (msgtype == DHCP6_MT_RELAYREPL) ? "reply" : "forward";
+ if (drm == DRM_LAYER2)
+ log_info("forwarded relay-%s for %s to %s",
+ type, print_hw_addr(pc->pc_htype, pc->pc_hlen,
+ pc->pc_smac), intf->name);
+ else
+ log_info("forwarded relay-%s for %s to %s%%%s",
+ type,
+ v6addr2str(&ss2sin6(&pc->pc_srcorig)->sin6_addr),
+ v6addr2str(&ss2sin6(&pc->pc_dst)->sin6_addr),
+ intf->name);
+}
+
+void
+relay6(struct interface_info *intf, void *p, size_t plen,
+ struct packet_ctx *pc)
+{
+ struct dhcp6_packet *ds = (struct dhcp6_packet *)p;
+ struct dhcp6_relay_packet *dsr = (struct dhcp6_relay_packet *)p;
+ struct interface_info *dstif = NULL;
+ struct server_list *sp;
+ size_t buflen = plen;
+ int clientdir = (intf != interfaces);
+ uint8_t msgtype, hopcount = 0;
+
+ /* Sanity check: we have at least the DHCP header. */
+ if (plen < (int)sizeof(*ds)) {
+ log_debug("invalid packet size");
+ return;
+ }
+
+ /* Set Relay Agent Information fields. */
+ rai_configure(pc, intf);
+
+ /*
+ * RFC 3315 section 20 relay messages:
+ * For client messages prepend a new DHCP payload with the
+ * relay-forward, otherwise update the DHCP relay header.
+ */
+ msgtype = ds->ds_msgtype;
+
+ log_debug("%s: received %s from %s",
+ intf->name, dhcp6type2str(msgtype),
+ v6addr2str(&ss2sin6(&pc->pc_src)->sin6_addr));
+
+ switch (msgtype) {
+ case DHCP6_MT_ADVERTISE:
+ case DHCP6_MT_REPLY:
+ case DHCP6_MT_RECONFIGURE:
+ /*
+ * Don't forward reply packets coming from the client
+ * interface.
+ *
+ * RFC 6221 Section 6.1.1.
+ */
+ if (clientdir == 0) {
+ log_debug(" dropped reply in opposite direction");
+ return;
+ }
+ /* FALLTHROUGH */
+
+ case DHCP6_MT_REQUEST:
+ case DHCP6_MT_RENEW:
+ case DHCP6_MT_REBIND:
+ case DHCP6_MT_RELEASE:
+ case DHCP6_MT_DECLINE:
+ case DHCP6_MT_INFORMATIONREQUEST:
+ case DHCP6_MT_SOLICIT:
+ case DHCP6_MT_CONFIRM:
+ /*
+ * Encapsulate the client/server message with the
+ * relay-message header.
+ */
+ if (relay6_pushrelaymsg(pc, intf, (uint8_t *)p,
+ &buflen, DHCP_MTU_MAX) == -1) {
+ log_debug(" message encapsulation failed");
+ return;
+ }
+ break;
+
+ case DHCP6_MT_RELAYREPL:
+ /*
+ * Don't forward reply packets coming from the client
+ * interface.
+ *
+ * RFC 6221 Section 6.1.1.
+ */
+ if (clientdir == 0) {
+ log_debug(" dropped reply in opposite direction");
+ return;
+ }
+
+ if (relay6_poprelaymsg(pc, &dstif, (uint8_t *)p,
+ &buflen) == -1) {
+ log_debug(" failed to pop relay-message");
+ return;
+ }
+
+ pc->pc_sd = clientsd;
+ break;
+
+ case DHCP6_MT_RELAYFORW:
+ /*
+ * We can only have multiple hops when the destination
+ * address is All_DHCP_Relay_Agents_and_Servers, otherwise
+ * drop it.
+ */
+ if (memcmp(&ss2sin6(&pc->pc_dst)->sin6_addr,
+ &in6alldhcprelay, sizeof(in6alldhcprelay)) != 0) {
+ log_debug(" wrong destination");
+ return;
+ }
+
+ hopcount = dsr->dsr_hopcount + 1;
+ if (hopcount >= DHCP6_HOP_LIMIT) {
+ log_debug(" hop limit reached");
+ return;
+ }
+
+ /* Stack into another relay-message. */
+ if (relay6_pushrelaymsg(pc, intf, (uint8_t *)p,
+ &buflen, DHCP_MTU_MAX) == -1) {
+ log_debug(" failed to push relay message");
+ return;
+ }
+
+ dsr = (struct dhcp6_relay_packet *)p;
+ dsr->dsr_msgtype = msgtype;
+ dsr->dsr_peer = ss2sin6(&pc->pc_src)->sin6_addr;
+ dsr->dsr_hopcount = hopcount;
+ break;
+
+ default:
+ log_debug(" unknown message type %d", ds->ds_msgtype);
+ return;
+ }
+
+ /* We received an packet with Interface-ID, use it. */
+ if (dstif != NULL) {
+ relay6_logsrcaddr(pc, dstif, msgtype);
+ send_packet(dstif, p, buflen, pc);
+ return;
+ }
+
+ /* Or send packet to the client. */
+ if (clientdir) {
+ relay6_logsrcaddr(pc, interfaces, msgtype);
+ send_packet(interfaces, p, buflen, pc);
+ return;
+ }
+
+ /* Otherwise broadcast it to other relays/servers. */
+ TAILQ_FOREACH(sp, &svlist, entry) {
+ /*
+ * Don't send in the same interface it came in if we are
+ * using multicast.
+ */
+ if (sp->intf == intf &&
+ sp->to.ss_family == 0)
+ continue;
+
+ /*
+ * When forwarding a packet use the configured address
+ * (if any) instead of multicasting.
+ */
+ if (msgtype != DHCP6_MT_REPLY &&
+ sp->to.ss_family == AF_INET6)
+ pc->pc_dst = sp->to;
+
+ relay6_logsrcaddr(pc, sp->intf, msgtype);
+ send_packet(sp->intf, p, buflen, pc);
+ }
+}
+
+void
+mcast6_recv(struct protocol *l)
+{
+ struct in6_pktinfo *ipi6 = NULL;
+ struct cmsghdr *cmsg;
+ struct interface_info *intf;
+ int sd = *(int *)l->local;
+ ssize_t recvlen;
+ struct packet_ctx pc;
+ struct msghdr msg;
+ struct sockaddr_storage ss;
+ struct iovec iov[2];
+ uint8_t iovbuf[4096];
+ uint8_t cmsgbuf[
+ CMSG_SPACE(sizeof(struct in6_pktinfo))
+ ];
+
+ memset(&pc, 0, sizeof(pc));
+
+ iov[0].iov_base = iovbuf;
+ iov[0].iov_len = sizeof(iovbuf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ msg.msg_name = &ss;
+ msg.msg_namelen = sizeof(ss);
+ if ((recvlen = recvmsg(sd, &msg, 0)) == -1) {
+ log_warn("%s: recvmsg failed", __func__);
+ return;
+ }
+
+ /* Sanity check: this is an IPv6 packet. */
+ if (ss.ss_family != AF_INET6) {
+ log_debug("received non IPv6 packet");
+ return;
+ }
+
+ /* Drop packets that we sent. */
+ if (iflist_getbyaddr6(&ss2sin6(&ss)->sin6_addr) != NULL)
+ return;
+
+ /* Save the sender address. */
+ pc.pc_srcorig = pc.pc_src = ss;
+
+ /* Pre-configure destination to the default multicast address. */
+ ss2sin6(&pc.pc_dst)->sin6_family = AF_INET6;
+ ss2sin6(&pc.pc_dst)->sin6_len = sizeof(struct sockaddr_in6);
+ ss2sin6(&pc.pc_dst)->sin6_addr = in6alldhcprelay;
+ ss2sin6(&pc.pc_dst)->sin6_port = htons(DHCP6_SERVER_PORT);
+ pc.pc_sd = serversd;
+
+ /* Find out input interface. */
+ for (cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg); cmsg;
+ cmsg = (struct cmsghdr *)CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != IPPROTO_IPV6)
+ continue;
+
+ switch (cmsg->cmsg_type) {
+ case IPV6_PKTINFO:
+ ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+ break;
+ }
+ }
+ if (ipi6 == NULL) {
+ log_debug("failed to get packet interface");
+ return;
+ }
+
+ intf = iflist_getbyindex(ipi6->ipi6_ifindex);
+ if (intf == NULL) {
+ log_debug("failed to find packet interface: %u",
+ ipi6->ipi6_ifindex);
+ return;
+ }
+
+ /* Pass it to the relay routine. */
+ if (bootp_packet_handler)
+ (*bootp_packet_handler)(intf, iovbuf, recvlen, &pc);
+}
diff --git a/usr.sbin/dhcrelay6/dispatch.c b/usr.sbin/dhcrelay6/dispatch.c
new file mode 100644
index 00000000000..2c80cfc9688
--- /dev/null
+++ b/usr.sbin/dhcrelay6/dispatch.c
@@ -0,0 +1,489 @@
+/* $OpenBSD: dispatch.c,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999
+ * The Internet Software Consortium. 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "dhcpd.h"
+#include "log.h"
+
+/*
+ * Macros implementation used to generate link-local addresses. This
+ * code was copied from: sys/netinet6/in6_ifattach.c.
+ */
+#define EUI64_UBIT 0x02
+#define EUI64_TO_IFID(in6) \
+ do { (in6)->s6_addr[8] ^= EUI64_UBIT; } while (0)
+
+struct protocol *protocols;
+struct timeout *timeouts;
+static struct timeout *free_timeouts;
+static int interfaces_invalidated;
+
+void (*bootp_packet_handler)(struct interface_info *,
+ void *, size_t, struct packet_ctx *);
+
+static int interface_status(struct interface_info *ifinfo);
+
+struct interface_info *
+iflist_getbyindex(unsigned int index)
+{
+ struct interface_info *intf;
+
+ TAILQ_FOREACH(intf, &intflist, entry) {
+ if (intf->index != index)
+ continue;
+
+ return intf;
+ }
+
+ return NULL;
+}
+
+struct interface_info *
+iflist_getbyname(const char *name)
+{
+ struct interface_info *intf;
+
+ TAILQ_FOREACH(intf, &intflist, entry) {
+ if (strcmp(intf->name, name) != 0)
+ continue;
+
+ return intf;
+ }
+
+ return NULL;
+}
+
+struct interface_info *
+iflist_getbyaddr6(struct in6_addr *addr)
+{
+ struct interface_info *intf;
+
+ TAILQ_FOREACH(intf, &intflist, entry) {
+ /* Look for link-layer address. */
+ if (memcmp(&intf->linklocal, addr, sizeof(*addr)) == 0)
+ return intf;
+ }
+
+ return NULL;
+}
+
+void
+setup_iflist(void)
+{
+ struct interface_info *intf;
+ struct sockaddr_dl *sdl;
+ struct ifaddrs *ifap, *ifa;
+ struct if_data *ifi;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+
+ TAILQ_INIT(&intflist);
+ if (getifaddrs(&ifap))
+ fatalx("getifaddrs failed");
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if ((ifa->ifa_flags & IFF_LOOPBACK) ||
+ (ifa->ifa_flags & IFF_POINTOPOINT))
+ continue;
+
+ /* Find interface or create it. */
+ intf = iflist_getbyname(ifa->ifa_name);
+ if (intf == NULL) {
+ intf = calloc(1, sizeof(*intf));
+ if (intf == NULL)
+ fatal("calloc");
+
+ strlcpy(intf->name, ifa->ifa_name,
+ sizeof(intf->name));
+ TAILQ_INSERT_HEAD(&intflist, intf, entry);
+ }
+
+ /* Signal disabled interface. */
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ intf->dead = 1;
+
+ if (ifa->ifa_addr->sa_family == AF_LINK) {
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ ifi = (struct if_data *)ifa->ifa_data;
+
+ /* Skip non ethernet interfaces. */
+ if (ifi->ifi_type != IFT_ETHER &&
+ ifi->ifi_type != IFT_ENC) {
+ TAILQ_REMOVE(&intflist, intf, entry);
+ free(intf);
+ continue;
+ }
+
+ intf->index = sdl->sdl_index;
+ intf->hw_address.hlen = sdl->sdl_alen;
+ memcpy(intf->hw_address.haddr,
+ LLADDR(sdl), sdl->sdl_alen);
+ } else if (ifa->ifa_addr->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *)ifa->ifa_addr;
+ if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK) ||
+ intf->primary_address.s_addr != INADDR_ANY)
+ continue;
+
+ intf->primary_address = sin->sin_addr;
+ } else if (ifa->ifa_addr->sa_family == AF_INET6) {
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ /* Remove the scope from address if link-local. */
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ intf->linklocal = sin6->sin6_addr;
+ intf->linklocal.s6_addr[2] = 0;
+ intf->linklocal.s6_addr[3] = 0;
+ } else
+ intf->gipv6 = 1;
+
+ /* At least one IPv6 address was found. */
+ intf->ipv6 = 1;
+ }
+ }
+
+ freeifaddrs(ifap);
+
+ /*
+ * Generate link-local IPv6 address for interfaces without it.
+ *
+ * For IPv6 DHCP Relay it doesn't matter what is used for
+ * link-addr field, so let's generate an address that won't
+ * change during execution so we can always find the interface
+ * to relay packets back. This is only used for layer 2 relaying
+ * when the interface might not have an address.
+ */
+ TAILQ_FOREACH(intf, &intflist, entry) {
+ if (memcmp(&intf->linklocal, &in6addr_any,
+ sizeof(in6addr_any)) != 0)
+ continue;
+
+ intf->linklocal.s6_addr[0] = 0xfe;
+ intf->linklocal.s6_addr[1] = 0x80;
+ intf->linklocal.s6_addr[8] = intf->hw_address.haddr[0];
+ intf->linklocal.s6_addr[9] = intf->hw_address.haddr[1];
+ intf->linklocal.s6_addr[10] = intf->hw_address.haddr[2];
+ intf->linklocal.s6_addr[11] = 0xff;
+ intf->linklocal.s6_addr[12] = 0xfe;
+ intf->linklocal.s6_addr[13] = intf->hw_address.haddr[3];
+ intf->linklocal.s6_addr[14] = intf->hw_address.haddr[4];
+ intf->linklocal.s6_addr[15] = intf->hw_address.haddr[5];
+ EUI64_TO_IFID(&intf->linklocal);
+ }
+}
+
+struct interface_info *
+register_interface(const char *ifname, void (*handler)(struct protocol *))
+{
+ struct interface_info *intf;
+
+ if ((intf = iflist_getbyname(ifname)) == NULL)
+ return NULL;
+
+ /* Don't register disabled interfaces. */
+ if (intf->dead)
+ return NULL;
+
+ /* Check if we already registered the interface. */
+ if (intf->ifr.ifr_name[0] != 0)
+ return intf;
+
+ if (strlcpy(intf->ifr.ifr_name, ifname,
+ sizeof(intf->ifr.ifr_name)) >= sizeof(intf->ifr.ifr_name))
+ fatalx("interface name '%s' too long", ifname);
+
+ if_register_receive(intf);
+ if_register_send(intf);
+ add_protocol(intf->name, intf->rfdesc, handler, intf);
+
+ return intf;
+}
+
+/*
+ * Wait for packets to come in using poll(). When a packet comes in,
+ * call receive_packet to receive the packet and possibly strip hardware
+ * addressing information from it, and then call through the
+ * bootp_packet_handler hook to try to do something with it.
+ */
+void
+dispatch(void)
+{
+ int count, i, to_msec, nfds = 0;
+ struct protocol *l;
+ struct pollfd *fds;
+ time_t howlong;
+
+ nfds = 0;
+ for (l = protocols; l; l = l->next)
+ nfds++;
+
+ fds = calloc(nfds, sizeof(struct pollfd));
+ if (fds == NULL)
+ fatalx("Can't allocate poll structures.");
+
+ do {
+ /*
+ * Call any expired timeouts, and then if there's still
+ * a timeout registered, time out the select call then.
+ */
+another:
+ if (timeouts) {
+ if (timeouts->when <= cur_time) {
+ struct timeout *t = timeouts;
+
+ timeouts = timeouts->next;
+ (*(t->func))(t->what);
+ t->next = free_timeouts;
+ free_timeouts = t;
+ goto another;
+ }
+
+ /*
+ * Figure timeout in milliseconds, and check for
+ * potential overflow, so we can cram into an
+ * int for poll, while not polling with a
+ * negative timeout and blocking indefinitely.
+ */
+ howlong = timeouts->when - cur_time;
+ if (howlong > INT_MAX / 1000)
+ howlong = INT_MAX / 1000;
+ to_msec = howlong * 1000;
+ } else
+ to_msec = -1;
+
+ /* Set up the descriptors to be polled. */
+ i = 0;
+
+ for (l = protocols; l; l = l->next) {
+ struct interface_info *ip = l->local;
+
+ if (ip && (l->handler != got_one || !ip->dead)) {
+ fds[i].fd = l->fd;
+ fds[i].events = POLLIN;
+ fds[i].revents = 0;
+ i++;
+ }
+ }
+
+ if (i == 0)
+ fatalx("No live interfaces to poll on - exiting.");
+
+ /* Wait for a packet or a timeout... XXX */
+ count = poll(fds, nfds, to_msec);
+
+ /* Not likely to be transitory... */
+ if (count == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ time(&cur_time);
+ continue;
+ }
+ else
+ fatal("poll");
+ }
+
+ /* Get the current time... */
+ time(&cur_time);
+
+ i = 0;
+ for (l = protocols; l; l = l->next) {
+ struct interface_info *ip = l->local;
+
+ if ((fds[i].revents & (POLLIN | POLLHUP))) {
+ fds[i].revents = 0;
+ if (ip && (l->handler != got_one ||
+ !ip->dead))
+ (*(l->handler))(l);
+ if (interfaces_invalidated)
+ break;
+ }
+ i++;
+ }
+ interfaces_invalidated = 0;
+ } while (1);
+}
+
+
+void
+got_one(struct protocol *l)
+{
+ struct packet_ctx pc;
+ ssize_t result;
+ uint8_t buf[4096];
+ struct interface_info *ip = l->local;
+
+ memset(&pc, 0, sizeof(pc));
+
+ if ((result = receive_packet(ip, buf, sizeof(buf), &pc)) == -1) {
+ log_warn("receive_packet failed on %s", ip->name);
+ ip->errors++;
+ if ((!interface_status(ip)) ||
+ (ip->noifmedia && ip->errors > 20)) {
+ /* our interface has gone away. */
+ log_warnx("Interface %s no longer appears valid.",
+ ip->name);
+ ip->dead = 1;
+ interfaces_invalidated = 1;
+ close(l->fd);
+ remove_protocol(l);
+ free(ip);
+ }
+ return;
+ }
+ if (result == 0)
+ return;
+
+ if (bootp_packet_handler)
+ (*bootp_packet_handler)(ip, buf, result, &pc);
+}
+
+int
+interface_status(struct interface_info *ifinfo)
+{
+ char *ifname = ifinfo->name;
+ int ifsock = ifinfo->rfdesc;
+ struct ifreq ifr;
+ struct ifmediareq ifmr;
+
+ /* get interface flags */
+ memset(&ifr, 0, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(ifsock, SIOCGIFFLAGS, &ifr) == -1) {
+ log_warn("ioctl(SIOCGIFFLAGS) on %s", ifname);
+ goto inactive;
+ }
+ /*
+ * if one of UP and RUNNING flags is dropped,
+ * the interface is not active.
+ */
+ if ((ifr.ifr_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
+ goto inactive;
+ }
+ /* Next, check carrier on the interface, if possible */
+ if (ifinfo->noifmedia)
+ goto active;
+ memset(&ifmr, 0, sizeof(ifmr));
+ strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+ if (ioctl(ifsock, SIOCGIFMEDIA, (caddr_t)&ifmr) == -1) {
+ if (errno != EINVAL) {
+ log_debug("ioctl(SIOCGIFMEDIA) on %s", ifname);
+ ifinfo->noifmedia = 1;
+ goto active;
+ }
+ /*
+ * EINVAL (or ENOTTY) simply means that the interface
+ * does not support the SIOCGIFMEDIA ioctl. We regard it alive.
+ */
+ ifinfo->noifmedia = 1;
+ goto active;
+ }
+ if (ifmr.ifm_status & IFM_AVALID) {
+ switch (ifmr.ifm_active & IFM_NMASK) {
+ case IFM_ETHER:
+ if (ifmr.ifm_status & IFM_ACTIVE)
+ goto active;
+ else
+ goto inactive;
+ break;
+ default:
+ goto inactive;
+ }
+ }
+inactive:
+ return (0);
+active:
+ return (1);
+}
+
+/* Add a protocol to the list of protocols... */
+void
+add_protocol(char *name, int fd, void (*handler)(struct protocol *),
+ void *local)
+{
+ struct protocol *p;
+
+ p = malloc(sizeof(*p));
+ if (!p)
+ fatalx("can't allocate protocol struct for %s", name);
+
+ p->fd = fd;
+ p->handler = handler;
+ p->local = local;
+ p->next = protocols;
+ protocols = p;
+}
+
+void
+remove_protocol(struct protocol *proto)
+{
+ struct protocol *p, *next, *prev;
+
+ prev = NULL;
+ for (p = protocols; p; p = next) {
+ next = p->next;
+ if (p == proto) {
+ if (prev)
+ prev->next = p->next;
+ else
+ protocols = p->next;
+ free(p);
+ }
+ }
+}
diff --git a/usr.sbin/dhcrelay6/log.c b/usr.sbin/dhcrelay6/log.c
new file mode 100644
index 00000000000..3ceb9faf08b
--- /dev/null
+++ b/usr.sbin/dhcrelay6/log.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: log.c,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int debug;
+static int verbose;
+static const char *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(__progname);
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "fatal in %s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/usr.sbin/dhcrelay6/log.h b/usr.sbin/dhcrelay6/log.h
new file mode 100644
index 00000000000..ccb75bbc711
--- /dev/null
+++ b/usr.sbin/dhcrelay6/log.h
@@ -0,0 +1,46 @@
+/* $OpenBSD: log.h,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
diff --git a/usr.sbin/dhcrelay6/packet.c b/usr.sbin/dhcrelay6/packet.c
new file mode 100644
index 00000000000..6f634282d37
--- /dev/null
+++ b/usr.sbin/dhcrelay6/packet.c
@@ -0,0 +1,231 @@
+/* $OpenBSD: packet.c,v 1.1 2017/03/17 14:45:16 rzalamena Exp $ */
+
+/* Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
+ * Enterprises. To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''. To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <netinet/if_ether.h>
+
+#include <string.h>
+
+#include "dhcp.h"
+#include "dhcpd.h"
+#include "log.h"
+
+
+u_int32_t checksum(unsigned char *, unsigned, u_int32_t);
+u_int32_t wrapsum(u_int32_t);
+
+u_int32_t
+checksum(unsigned char *buf, unsigned nbytes, u_int32_t sum)
+{
+ unsigned int i;
+
+ /* Checksum all the pairs of bytes first... */
+ for (i = 0; i < (nbytes & ~1U); i += 2) {
+ sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ /*
+ * If there's a single byte left over, checksum it, too.
+ * Network byte order is big-endian, so the remaining byte is
+ * the high byte.
+ */
+ if (i < nbytes) {
+ sum += buf[i] << 8;
+ if (sum > 0xFFFF)
+ sum -= 0xFFFF;
+ }
+
+ return (sum);
+}
+
+u_int32_t
+wrapsum(u_int32_t sum)
+{
+ sum = ~sum & 0xFFFF;
+ return (htons(sum));
+}
+
+void
+assemble_hw_header(unsigned char *buf, int *bufix, struct packet_ctx *pc)
+{
+ struct ether_header eh;
+
+ memcpy(eh.ether_shost, pc->pc_smac, ETHER_ADDR_LEN);
+ memcpy(eh.ether_dhost, pc->pc_dmac, ETHER_ADDR_LEN);
+ eh.ether_type = htons(pc->pc_ethertype);
+
+ memcpy(&buf[*bufix], &eh, ETHER_HDR_LEN);
+ *bufix += ETHER_HDR_LEN;
+}
+
+void
+assemble_udp_ip6_header(unsigned char *p, int *off, struct packet_ctx *pc,
+ unsigned char *payload, int plen)
+{
+ struct ip6_hdr ip6;
+ struct udphdr uh;
+
+ memset(&ip6, 0, sizeof(ip6));
+ ip6.ip6_vfc = IPV6_VERSION;
+ ip6.ip6_nxt = IPPROTO_UDP;
+ ip6.ip6_src = ss2sin6(&pc->pc_src)->sin6_addr;
+ ip6.ip6_dst = ss2sin6(&pc->pc_dst)->sin6_addr;
+ ip6.ip6_plen = htons(sizeof(uh) + plen);
+ ip6.ip6_hlim = 64;
+ memcpy(&p[*off], &ip6, sizeof(ip6));
+ *off += sizeof(ip6);
+
+ memset(&uh, 0, sizeof(uh));
+ uh.uh_ulen = ip6.ip6_plen;
+ uh.uh_sport = ss2sin6(&pc->pc_src)->sin6_port;
+ uh.uh_dport = ss2sin6(&pc->pc_dst)->sin6_port;
+ uh.uh_sum = wrapsum(
+ checksum((unsigned char *)&uh, sizeof(uh),
+ checksum(payload, plen,
+ checksum((unsigned char *)&ip6.ip6_src, sizeof(ip6.ip6_src),
+ checksum((unsigned char *)&ip6.ip6_dst, sizeof(ip6.ip6_dst),
+ IPPROTO_UDP + ntohs(ip6.ip6_plen)
+ ))))
+ );
+ memcpy(&p[*off], &uh, sizeof(uh));
+ *off += sizeof(uh);
+}
+
+ssize_t
+decode_hw_header(unsigned char *buf, int bufix, struct packet_ctx *pc)
+{
+ struct ether_header *ether;
+
+ ether = (struct ether_header *)(buf + bufix);
+ memcpy(pc->pc_dmac, ether->ether_dhost, ETHER_ADDR_LEN);
+ memcpy(pc->pc_smac, ether->ether_shost, ETHER_ADDR_LEN);
+ pc->pc_ethertype = ntohs(ether->ether_type);
+
+ pc->pc_htype = ARPHRD_ETHER;
+ pc->pc_hlen = ETHER_ADDR_LEN;
+
+ return sizeof(struct ether_header);
+}
+
+ssize_t
+decode_udp_ip6_header(unsigned char *p, int off, struct packet_ctx *pc,
+ size_t plen)
+{
+ struct ip6_hdr *ip6;
+ struct udphdr *uh;
+ struct in6_addr *asrc, *adst;
+ size_t ptotal, poff = 0;
+ uint16_t ocksum, cksum;
+
+ /* Check the IPv6 header. */
+ if (plen < sizeof(*ip6)) {
+ log_debug("package too small (%ld)", plen);
+ return -1;
+ }
+
+ ip6 = (struct ip6_hdr *)(p + off);
+ if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) {
+ log_debug("invalid IPv6 version");
+ return -1;
+ }
+
+ poff += sizeof(*ip6);
+
+ ptotal = ntohs(ip6->ip6_plen);
+ if (ptotal > plen) {
+ log_debug("expected %ld bytes, but got %ld", ptotal, plen);
+ return (-1);
+ }
+
+ pc->pc_src.ss_len = sizeof(struct sockaddr_in6);
+ pc->pc_src.ss_family = AF_INET6;
+ asrc = &ss2sin6(&pc->pc_src)->sin6_addr;
+ memcpy(asrc, &ip6->ip6_src, sizeof(*asrc));
+
+ pc->pc_dst.ss_len = sizeof(struct sockaddr_in6);
+ pc->pc_dst.ss_family = AF_INET6;
+ adst = &ss2sin6(&pc->pc_dst)->sin6_addr;
+ memcpy(adst, &ip6->ip6_dst, sizeof(*adst));
+
+ /* Deal with the UDP header. */
+ if (ip6->ip6_nxt != IPPROTO_UDP) {
+ /* We don't support skipping extensions yet. */
+ log_debug("expected UDP header, got %#02X", ip6->ip6_nxt);
+ return -1;
+ }
+
+ uh = (struct udphdr *)((uint8_t *)ip6 + sizeof(*ip6));
+ ss2sin6(&pc->pc_src)->sin6_port = uh->uh_sport;
+ ss2sin6(&pc->pc_dst)->sin6_port = uh->uh_dport;
+ ocksum = uh->uh_sum;
+ uh->uh_sum = 0;
+ poff += sizeof(*uh);
+
+ /* Validate the packet. */
+ cksum = wrapsum(
+ checksum((unsigned char *)asrc, sizeof(*asrc),
+ checksum((unsigned char *)adst, sizeof(*adst),
+ checksum((unsigned char *)uh, sizeof(*uh),
+ checksum(p + off + poff, ptotal - sizeof(*uh),
+ IPPROTO_UDP + ntohs(uh->uh_ulen)))))
+ );
+
+ if (ocksum != cksum) {
+ log_debug("checksum invalid (%#04x != %#04x)",
+ ocksum, cksum);
+ return -1;
+ }
+
+ return poff;
+}