mirror of
https://github.com/openbsd/src.git
synced 2025-01-03 06:45:37 -08:00
ac6360cb39
OK dlg
929 lines
22 KiB
C
929 lines
22 KiB
C
/* $OpenBSD: dhcrelay.c,v 1.67 2024/08/21 10:35:12 florian 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 addrinfo hints, *res;
|
|
struct in_addr ia, *iap = NULL;
|
|
|
|
if ((sp = calloc(1, sizeof(*sp))) == NULL)
|
|
fatalx("calloc");
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_INET;
|
|
|
|
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 (getaddrinfo(argv[0], NULL, &hints, &res) != 0)
|
|
log_warnx("%s: host unknown", argv[0]);
|
|
else {
|
|
ia = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
|
|
iap = &ia;
|
|
freeaddrinfo(res);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|