diff options
author | 2017-03-17 14:45:16 +0000 | |
---|---|---|
committer | 2017-03-17 14:45:16 +0000 | |
commit | 9cbab5830b690836fba8f7e46ae8cef69a6fd5c1 (patch) | |
tree | 3689b31c8d81168cfc9a5e906dde28f186038f22 | |
parent | Add h and l for collapse and expand in choose mode with vi(1) keys, from (diff) | |
download | wireguard-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/Makefile | 13 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/bpf.c | 411 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/dhcp.h | 140 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/dhcpd.h | 183 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/dhcrelay6.8 | 210 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/dhcrelay6.c | 1027 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/dispatch.c | 489 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/log.c | 199 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/log.h | 46 | ||||
-rw-r--r-- | usr.sbin/dhcrelay6/packet.c | 231 |
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; +} |