[BACK]Return to dhcrelay.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / dhcrelay

File: [local] / src / usr.sbin / dhcrelay / dhcrelay.c (download)

Revision 1.66, Sun Oct 24 21:24:18 2021 UTC (2 years, 7 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, HEAD
Changes since 1.65: +2 -2 lines

For open/openat, if the flags parameter does not contain O_CREAT, the
3rd (variadic) mode_t parameter is irrelevant.  Many developers in the past
have passed mode_t (0, 044, 0644, or such), which might lead future people
to copy this broken idiom, and perhaps even believe this parameter has some
meaning or implication or application. Delete them all.
This comes out of a conversation where tb@ noticed that a strange (but
intentional) pledge behaviour is to always knock-out high-bits from
mode_t on a number of system calls as a safety factor, and his bewilderment
that this appeared to be happening against valid modes (at least visually),
but no sorry, they are all irrelevant junk.  They could all be 0xdeafbeef.
ok millert

/*	$OpenBSD: dhcrelay.c,v 1.66 2021/10/24 21:24:18 deraadt Exp $ */

/*
 * 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/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#include <net/if.h>

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.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"

void	 usage(void);
int	 rdaemon(int);
void	 relay(struct interface_info *, struct dhcp_packet *, int,
	    struct packet_ctx *);
void	 l2relay(struct interface_info *, struct dhcp_packet *, int,
	    struct packet_ctx *);
char	*print_hw_addr(int, int, unsigned char *);
void	 got_response(struct protocol *);
int	 get_rdomain(char *);

void	 relay_agentinfo(struct packet_ctx *, struct interface_info *, int);

int	 relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *, int);
ssize_t	 relay_agentinfo_append(struct packet_ctx *, struct dhcp_packet *,
	    size_t);
ssize_t	 relay_agentinfo_remove(struct packet_ctx *, struct dhcp_packet *,
	    size_t);

time_t cur_time;

struct interface_info *interfaces = NULL;
struct server_list *servers;
struct iflist intflist;
int server_fd;
int oflag;

enum dhcp_relay_mode	 drm = DRM_UNKNOWN;
const char		*rai_circuit = NULL;
const char		*rai_remote = NULL;
int			 rai_replace = 0;

int
main(int argc, char *argv[])
{
	int			 ch, devnull = -1, daemonize, opt, rdomain;
	struct server_list	*sp = NULL;
	struct passwd		*pw;
	struct sockaddr_in	 laddr;
	int			 optslen;

	daemonize = 1;

	log_init(1, LOG_DAEMON);	/* log to stderr until daemonized */

	setup_iflist();

	while ((ch = getopt(argc, argv, "aC:di:oR:r")) != -1) {
		switch (ch) {
		case 'C':
			rai_circuit = optarg;
			break;
		case 'd':
			daemonize = 0;
			break;
		case 'i':
			if (interfaces != NULL)
				usage();

			interfaces = iflist_getbyname(optarg);
			if (interfaces == NULL)
				fatalx("interface '%s' not found", optarg);
			break;
		case 'o':
			/* add the relay agent information option */
			oflag++;
			break;
		case 'R':
			rai_remote = optarg;
			break;
		case 'r':
			rai_replace = 1;
			break;

		default:
			usage();
			/* not reached */
		}
	}

	argc -= optind;
	argv += optind;

	if (argc < 1)
		usage();

	if (rai_remote != NULL && rai_circuit == NULL)
		fatalx("you must specify a circuit-id with a remote-id");

	/* Validate that we have space for all suboptions. */
	if (rai_circuit != NULL) {
		optslen = 2 + strlen(rai_circuit);
		if (rai_remote != NULL)
			optslen += 2 + strlen(rai_remote);

		if (optslen > DHCP_OPTION_MAXLEN)
			fatalx("relay agent information is too long");
	}

	while (argc > 0) {
		struct hostent		*he;
		struct in_addr		 ia, *iap = NULL;

		if ((sp = calloc(1, sizeof(*sp))) == NULL)
			fatalx("calloc");

		if ((sp->intf = register_interface(argv[0], got_one,
		    1)) != NULL) {
			if (drm == DRM_LAYER3)
				fatalx("don't mix interfaces with hosts");

			if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL)
				fatalx("can't use IPsec with layer 2");

			sp->next = servers;
			servers = sp;

			drm = DRM_LAYER2;
			argc--;
			argv++;
			continue;
		}

		if (inet_aton(argv[0], &ia))
			iap = &ia;
		else {
			he = gethostbyname(argv[0]);
			if (!he)
				log_warnx("%s: host unknown", argv[0]);
			else
				iap = ((struct in_addr *)he->h_addr_list[0]);
		}
		if (iap) {
			if (drm == DRM_LAYER2)
				fatalx("don't mix interfaces with hosts");

			drm = DRM_LAYER3;
			sp->next = servers;
			servers = sp;
			memcpy(&ss2sin(&sp->to)->sin_addr, iap, sizeof(*iap));
		} else
			free(sp);

		argc--;
		argv++;
	}

	if (daemonize) {
		devnull = open(_PATH_DEVNULL, O_RDWR);
		if (devnull == -1)
			fatal("open(%s)", _PATH_DEVNULL);
	}

	if (interfaces == NULL ||
	    register_interface(interfaces->name, got_one, 0) == NULL)
		fatalx("no interface given");

	/* We need an address for running layer 3 mode. */
	if (drm == DRM_LAYER3 &&
	    (interfaces->hw_address.htype != HTYPE_IPSEC_TUNNEL &&
	    interfaces->primary_address.s_addr == 0))
		fatalx("interface '%s' does not have an address",
		    interfaces->name);

	/* We need at least one server. */
	if (!sp)
		usage();

	rdomain = get_rdomain(interfaces->name);

	/* Enable the relay agent option by default for enc0 */
	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL)
		oflag++;

	bzero(&laddr, sizeof laddr);
	laddr.sin_len = sizeof laddr;
	laddr.sin_family = AF_INET;
	laddr.sin_port = htons(SERVER_PORT);
	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
	/* Set up the server sockaddrs. */
	for (sp = servers; sp; sp = sp->next) {
		if (sp->intf != NULL)
			break;

		ss2sin(&sp->to)->sin_port = htons(SERVER_PORT);
		ss2sin(&sp->to)->sin_family = AF_INET;
		ss2sin(&sp->to)->sin_len = sizeof(struct sockaddr_in);
		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
		if (sp->fd == -1)
			fatal("socket");
		opt = 1;
		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
		    &opt, sizeof(opt)) == -1)
			fatal("setsockopt");
		if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain,
		    sizeof(rdomain)) == -1)
			fatal("setsockopt");
		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) ==
		    -1)
			fatal("bind");
		if (connect(sp->fd, (struct sockaddr *)&sp->to,
		    sp->to.ss_len) == -1)
			fatal("connect");
		add_protocol("server", sp->fd, got_response, sp);
	}

	/* Socket used to forward packets to the DHCP client */
	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
		laddr.sin_addr.s_addr = INADDR_ANY;
		server_fd = socket(AF_INET, SOCK_DGRAM, 0);
		if (server_fd == -1)
			fatal("socket");
		opt = 1;
		if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
		    &opt, sizeof(opt)) == -1)
			fatal("setsockopt");
		if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain,
		    sizeof(rdomain)) == -1)
			fatal("setsockopt");
		if (bind(server_fd, (struct sockaddr *)&laddr,
		    sizeof(laddr)) == -1)
			fatal("bind");
	}

	tzset();

	time(&cur_time);
	if (drm == DRM_LAYER3)
		bootp_packet_handler = relay;
	else
		bootp_packet_handler = l2relay;

	if ((pw = getpwnam("_dhcp")) == NULL)
		fatalx("user \"_dhcp\" not found");
	if (chroot(pw->pw_dir) == -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(0, LOG_DAEMON);	/* stop logging to stderr */
	}

	if (pledge("stdio route", NULL) == -1)
		fatalx("pledge");

	dispatch();
	/* not reached */

	exit(0);
}

void
relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
    struct packet_ctx *pc)
{
	struct server_list	*sp;
	struct sockaddr_in	 to;

	if (packet->hlen > sizeof packet->chaddr) {
		log_info("Discarding packet with invalid hlen.");
		return;
	}

	/* If it's a bootreply, forward it to the client. */
	if (packet->op == BOOTREPLY) {
		/* Filter packet that were not meant for us. */
		if (packet->giaddr.s_addr !=
		    interfaces->primary_address.s_addr)
			return;

		bzero(&to, sizeof(to));
		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
			to.sin_addr = packet->yiaddr;
			to.sin_port = htons(CLIENT_PORT);
		} else {
			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
			to.sin_port = htons(CLIENT_PORT);
		}
		to.sin_family = AF_INET;
		to.sin_len = sizeof to;
		*ss2sin(&pc->pc_dst) = to;

		/*
		 * Set up the hardware destination address.  If it's a reply
		 * with the BROADCAST flag set, we should send an L2 broad-
		 * cast as well.
		 */
		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
			pc->pc_hlen = packet->hlen;
			if (pc->pc_hlen > CHADDR_SIZE)
				pc->pc_hlen = CHADDR_SIZE;
			memcpy(pc->pc_dmac, packet->chaddr, pc->pc_hlen);
			pc->pc_htype = packet->htype;
		} else {
			memset(pc->pc_dmac, 0xff, sizeof(pc->pc_dmac));
		}

		relay_agentinfo(pc, interfaces, packet->op);
		if ((length = relay_agentinfo_remove(pc, packet,
		    length)) == -1) {
			log_info("ignoring BOOTREPLY with invalid "
			    "relay agent information");
			return;
		}

		/*
		 * VMware PXE "ROMs" confuse the DHCP gateway address
		 * with the IP gateway address. This is a problem if your
		 * DHCP relay is running on something that's not your
		 * network gateway.
		 *
		 * It is purely informational from the relay to the client
		 * so we can safely clear it.
		 */
		packet->giaddr.s_addr = 0x0;

		ss2sin(&pc->pc_src)->sin_addr = interfaces->primary_address;
		if (send_packet(interfaces, packet, length, pc) != -1)
			log_debug("forwarded BOOTREPLY for %s to %s",
			    print_hw_addr(packet->htype, packet->hlen,
			    packet->chaddr), inet_ntoa(to.sin_addr));
		return;
	}

	if (ip == NULL) {
		log_info("ignoring non BOOTREPLY from server");
		return;
	}

	if (packet->hops > 16) {
		log_info("ignoring BOOTREQUEST with hop count of %d",
		    packet->hops);
		return;
	}
	packet->hops++;

	/*
	 * Set the giaddr so the server can figure out what net it's
	 * from and so that we can later forward the response to the
	 * correct net.  The RFC specifies that we have to keep the
	 * initial giaddr (in case we relay over multiple hops).
	 */
	if (!packet->giaddr.s_addr)
		packet->giaddr = ip->primary_address;

	relay_agentinfo(pc, interfaces, packet->op);
	if ((length = relay_agentinfo_append(pc, packet, length)) == -1) {
		log_info("ignoring BOOTREQUEST with invalid "
		    "relay agent information");
		return;
	}

	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
	   servers. */
	for (sp = servers; sp; sp = sp->next) {
		if (send(sp->fd, packet, length, 0) != -1) {
			log_debug("forwarded BOOTREQUEST for %s to %s",
			    print_hw_addr(packet->htype, packet->hlen,
			    packet->chaddr),
			    inet_ntoa(ss2sin(&sp->to)->sin_addr));
		}
	}

}

void
usage(void)
{
	extern char	*__progname;

	fprintf(stderr, "usage: %s [-dor] [-C circuit-id] [-R remote-id] "
	    "-i interface\n\tdestination ...\n",
	    __progname);
	exit(1);
}

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);
}

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;
}

void
got_response(struct protocol *l)
{
	struct packet_ctx pc;
	ssize_t result;
	union {
		/*
		 * Packet input buffer.  Must be as large as largest
		 * possible MTU.
		 */
		unsigned char packbuf[4095];
		struct dhcp_packet packet;
	} u;
	struct server_list *sp = l->local;

	memset(&u, DHO_END, sizeof(u));
	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
	    errno != ECONNREFUSED) {
		/*
		 * Ignore ECONNREFUSED as too many dhcp servers send a bogus
		 * icmp unreach for every request.
		 */
		log_warn("recv failed for %s",
		    inet_ntoa(ss2sin(&sp->to)->sin_addr));
		return;
	}
	if (result == -1 && errno == ECONNREFUSED)
		return;

	if (result == 0)
		return;

	if (result < BOOTP_MIN_LEN) {
		log_info("Discarding packet with invalid size.");
		return;
	}

	memset(&pc, 0, sizeof(pc));
	pc.pc_src.ss_family = AF_INET;
	pc.pc_src.ss_len = sizeof(struct sockaddr_in);
	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &ss2sin(&sp->to)->sin_addr,
	    sizeof(ss2sin(&pc.pc_src)->sin_addr));
	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);

	pc.pc_dst.ss_family = AF_INET;
	pc.pc_dst.ss_len = sizeof(struct sockaddr_in);
	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);

	if (bootp_packet_handler)
		(*bootp_packet_handler)(NULL, &u.packet, result, &pc);
}

void
relay_agentinfo(struct packet_ctx *pc, struct interface_info *intf,
    int bootop)
{
	static u_int8_t		 buf[8];
	struct sockaddr_in	*sin;

	if (oflag == 0)
		return;

	if (rai_remote != NULL) {
		pc->pc_remote = rai_remote;
		pc->pc_remotelen = strlen(rai_remote);
	} else
		pc->pc_remotelen = 0;

	if (rai_circuit == NULL) {
		buf[0] = (uint8_t)(intf->index << 8);
		buf[1] = intf->index & 0xff;
		pc->pc_circuit = buf;
		pc->pc_circuitlen = 2;

		if (rai_remote == NULL) {
			if (bootop == BOOTREPLY)
				sin = ss2sin(&pc->pc_dst);
			else
				sin = ss2sin(&pc->pc_src);

			pc->pc_remote =
			    (uint8_t *)&sin->sin_addr;
			pc->pc_remotelen =
			    sizeof(sin->sin_addr);
		}
	} else {
		pc->pc_circuit = rai_circuit;
		pc->pc_circuitlen = strlen(rai_circuit);
	}
}

int
relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *p, int plen)
{
	int		 len;
	char		 buf[256];

	if (oflag == 0)
		return (-1);

	len = *(p + 1);
	if (len > plen)
		return (-1);

	switch (*p) {
	case RAI_CIRCUIT_ID:
		if (pc->pc_circuit == NULL)
			return (-1);
		if (pc->pc_circuitlen != len)
			return (-1);

		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
		return (memcmp(pc->pc_circuit, buf, len));

	case RAI_REMOTE_ID:
		if (pc->pc_remote == NULL)
			return (-1);
		if (pc->pc_remotelen != len)
			return (-1);

		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
		return (memcmp(pc->pc_remote, buf, len));

	default:
		/* Unmatched type */
		log_info("unmatched relay info %d", *p);
		return (0);
	}
}

ssize_t
relay_agentinfo_append(struct packet_ctx *pc, struct dhcp_packet *dp,
    size_t dplen)
{
	uint8_t		*p, *startp;
	ssize_t		 newtotal = dplen;
	int		 opttotal, optlen, i, hasinfo = 0;
	int		 maxlen, neededlen;

	/* Only append when enabled. */
	if (oflag == 0)
		return (dplen);

	startp = (uint8_t *)dp;
	p = (uint8_t *)&dp->options;
	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
		log_info("invalid dhcp options cookie");
		return (-1);
	}

	p += DHCP_OPTIONS_COOKIE_LEN;
	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
	if (maxlen < 1 || opttotal < 1)
		return (dplen);

	for (i = 0; i < opttotal && *p != DHO_END;) {
		if (*p == DHO_PAD)
			optlen = 1;
		else
			optlen = p[1] + DHCP_OPTION_HDR_LEN;

		if ((i + optlen) > opttotal) {
			log_info("truncated dhcp options");
			return (-1);
		}

		if (*p == DHO_RELAY_AGENT_INFORMATION) {
			if (rai_replace) {
				memmove(p, p + optlen, opttotal - i);
				opttotal -= optlen;
				optlen = 0;
			} else
				hasinfo = 1;
		}

		p += optlen;
		i += optlen;

		/* We reached the end, append the relay agent info. */
		if (i < opttotal && *p == DHO_END) {
			/* We already have the Relay Agent Info, skip it. */
			if (hasinfo)
				continue;

			/* Calculate needed length to append new data. */
			neededlen = newtotal + DHCP_OPTION_HDR_LEN;
			if (pc->pc_circuitlen > 0)
				neededlen += DHCP_OPTION_HDR_LEN +
				    pc->pc_circuitlen;
			if (pc->pc_remotelen > 0)
				neededlen += DHCP_OPTION_HDR_LEN +
				    pc->pc_remotelen;

			/* Save one byte for DHO_END. */
			neededlen += 1;

			/* Check if we have space for the new options. */
			if (neededlen > maxlen) {
				log_warnx("no space for relay agent info");
				return (newtotal);
			}

			/* New option header: 2 bytes. */
			newtotal += DHCP_OPTION_HDR_LEN;

			*p++ = DHO_RELAY_AGENT_INFORMATION;
			*p = 0;
			if (pc->pc_circuitlen > 0) {
				newtotal += DHCP_OPTION_HDR_LEN +
				    pc->pc_circuitlen;
				*p = (*p) + DHCP_OPTION_HDR_LEN +
				    pc->pc_circuitlen;
			}

			if (pc->pc_remotelen > 0) {
				newtotal += DHCP_OPTION_HDR_LEN +
				    pc->pc_remotelen;
				*p = (*p) + DHCP_OPTION_HDR_LEN +
				    pc->pc_remotelen;
			}

			p++;

			/* Sub-option circuit-id header plus value. */
			if (pc->pc_circuitlen > 0) {
				*p++ = RAI_CIRCUIT_ID;
				*p++ = pc->pc_circuitlen;
				memcpy(p, pc->pc_circuit, pc->pc_circuitlen);

				p += pc->pc_circuitlen;
			}

			/* Sub-option remote-id header plus value. */
			if (pc->pc_remotelen > 0) {
				*p++ = RAI_REMOTE_ID;
				*p++ = pc->pc_remotelen;
				memcpy(p, pc->pc_remote, pc->pc_remotelen);

				p += pc->pc_remotelen;
			}

			*p = DHO_END;
		}
	}

	/* Zero the padding so we don't leak anything. */
	p++;
	if (p < (startp + maxlen))
		memset(p, 0, (startp + maxlen) - p);

	return (newtotal);
}

ssize_t
relay_agentinfo_remove(struct packet_ctx *pc, struct dhcp_packet *dp,
    size_t dplen)
{
	uint8_t		*p, *np, *startp, *endp;
	int		 opttotal, optleft;
	int		 suboptlen, optlen, i;
	int		 maxlen, remaining, matched = 0;

	startp = (uint8_t *)dp;
	p = (uint8_t *)&dp->options;
	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
		log_info("invalid dhcp options cookie");
		return (-1);
	}

	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
	optleft = opttotal;

	p += DHCP_OPTIONS_COOKIE_LEN;
	endp = p + opttotal;

	for (i = 0; i < opttotal && *p != DHO_END;) {
		if (*p == DHO_PAD)
			optlen = 1;
		else
			optlen = p[1] + DHCP_OPTION_HDR_LEN;

		if ((i + optlen) > opttotal) {
			log_info("truncated dhcp options");
			return (-1);
		}

		if (*p == DHO_RELAY_AGENT_INFORMATION) {
			/* Fast case: there is no next option. */
			np = p + optlen;
			if (*np == DHO_END) {
				*p = *np;
				endp = p + 1;
				/* Zero the padding so we don't leak data. */
				if (endp < (startp + maxlen))
					memset(endp, 0,
					    (startp + maxlen) - endp);

				return (dplen);
			}

			remaining = optlen;
			while (remaining > 0) {
				suboptlen = *(p + 1);
				remaining -= DHCP_OPTION_HDR_LEN + suboptlen;

				matched = 1;
				if (relay_agentinfo_cmp(pc, p, suboptlen) == 0)
					continue;

				matched = 0;
				break;
			}
			/* It is not ours Relay Agent Info, don't remove it. */
			if (matched == 0)
				break;

			/* Move the other options on top of this one. */
			optleft -= optlen;
			endp -= optlen;

			/* Replace the old agent relay info. */
			memmove(p, dp, optleft);

			endp++;
			/* Zero the padding so we don't leak data. */
			if (endp < (startp + maxlen))
				memset(endp, 0,
				    (startp + maxlen) - endp);

			return (endp - startp);
		}

		p += optlen;
		i += optlen;
		optleft -= optlen;
	}

	return (endp - startp);
}

int
get_rdomain(char *name)
{
	int rv = 0, s;
	struct  ifreq ifr;

	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		fatal("get_rdomain socket");

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
		rv = ifr.ifr_rdomainid;

	close(s);
	return rv;
}

void
l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length,
    struct packet_ctx *pc)
{
	struct server_list	*sp;
	ssize_t			 dplen;

	if (dp->hlen > sizeof(dp->chaddr)) {
		log_info("Discarding packet with invalid hlen.");
		return;
	}

	relay_agentinfo(pc, ip, dp->op);

	switch (dp->op) {
	case BOOTREQUEST:
		/* Add the relay agent info asked by the user. */
		if ((dplen = relay_agentinfo_append(pc, dp, length)) == -1)
			return;

		/*
		 * Re-send the packet to every interface except the one
		 * it came in.
		 */
		for (sp = servers; sp != NULL; sp = sp->next) {
			if (sp->intf == ip)
				continue;

			log_debug("forwarded BOOTREQUEST for %s to %s",
			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
			    pc->pc_smac), sp->intf->name);

			send_packet(sp->intf, dp, dplen, pc);
		}
		if (ip != interfaces) {
			log_debug("forwarded BOOTREQUEST for %s to %s",
			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
			    pc->pc_smac), interfaces->name);

			send_packet(interfaces, dp, dplen, pc);
		}
		break;

	case BOOTREPLY:
		/* Remove relay agent info on offer. */
		if ((dplen = relay_agentinfo_remove(pc, dp, length)) == -1)
			return;

		if (ip != interfaces) {
			log_debug("forwarded BOOTREPLY for %s to %s",
			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
			    pc->pc_dmac), interfaces->name);
			send_packet(interfaces, dp, dplen, pc);
		}
		break;

	default:
		log_debug("invalid operation type '%d'", dp->op);
		return;
	}
}