summaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2009-09-03 11:56:50 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2009-09-03 11:56:50 +0000
commit7cc9eb902b01b4c94c6be5f94c7016c74798a565 (patch)
tree19f75d0c86cb7d38d2dd343cf60faab30f2a791d /usr.sbin
parent72065703e8247a211550c6430814ff3553b7a7ec (diff)
Add support for "DHCP-over-IPsec" by implementing RFC 3046 (DHCP Relay
Agent Information Option) and RFC 3456 (DHCP Configuration of IPsec Tunnel Mode). This allows to configure various IPsec clients dynamically via DHCP; dhcrelay needs to listen on enc0 and forward requests to a DHCP server that supports RFC 3046, like I recently did for dhcpd(8). ok krw@
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/dhcrelay/bpf.c57
-rw-r--r--usr.sbin/dhcrelay/dhcp.h16
-rw-r--r--usr.sbin/dhcrelay/dhcpd.h6
-rw-r--r--usr.sbin/dhcrelay/dhcrelay.822
-rw-r--r--usr.sbin/dhcrelay/dhcrelay.c147
-rw-r--r--usr.sbin/dhcrelay/dispatch.c11
-rw-r--r--usr.sbin/dhcrelay/packet.c25
7 files changed, 261 insertions, 23 deletions
diff --git a/usr.sbin/dhcrelay/bpf.c b/usr.sbin/dhcrelay/bpf.c
index a442b5978d2..51bd2dd9e16 100644
--- a/usr.sbin/dhcrelay/bpf.c
+++ b/usr.sbin/dhcrelay/bpf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: bpf.c,v 1.6 2008/09/15 20:39:21 claudio Exp $ */
+/* $OpenBSD: bpf.c,v 1.7 2009/09/03 11:56:49 reyk Exp $ */
/* BPF socket interface code, originally contributed by Archie Cobbs. */
@@ -45,6 +45,8 @@
#include <sys/uio.h>
#include <net/bpf.h>
+#include <net/if_types.h>
+
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
@@ -126,6 +128,41 @@ struct bpf_insn dhcp_bpf_filter[] = {
int dhcp_bpf_filter_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn);
+/*
+ * Packet filter program: encapsulated 'ip and udp and dst port SERVER_PORT'
+ */
+struct bpf_insn dhcp_bpf_efilter[] = {
+ /* Make sure this is an encapsulated AF_INET packet... */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, AF_INET << 24, 0, 10),
+
+ /* Make sure it's an IPIP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 21),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_IPIP, 0, 8),
+
+ /* Make sure it's an encapsulated UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 41),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+ /* Make sure this isn't a fragment... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 38),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 32),
+
+ /* Make sure it's to the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 34),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 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 dhcp_bpf_efilter_len = sizeof(dhcp_bpf_efilter) / sizeof(struct bpf_insn);
/*
* Packet write filter program: 'ip and udp and src port SERVER_PORT'
@@ -201,9 +238,13 @@ if_register_receive(struct interface_info *info)
info->rbuf_len = 0;
/* Set up the bpf filter program structure. */
- p.bf_len = dhcp_bpf_filter_len;
- p.bf_insns = dhcp_bpf_filter;
-
+ if (info->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
+ p.bf_len = dhcp_bpf_efilter_len;
+ p.bf_insns = dhcp_bpf_efilter;
+ } else {
+ p.bf_len = dhcp_bpf_filter_len;
+ p.bf_insns = dhcp_bpf_filter;
+ }
if (ioctl(info->rfdesc, BIOCSETF, &p) == -1)
error("Can't install packet filter program: %m");
@@ -228,6 +269,13 @@ send_packet(struct interface_info *interface,
struct iovec iov[2];
int result, bufp = 0;
+ if (interface->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
+ socklen_t slen = sizeof(*to);
+ result = sendto(server_fd, raw, len, 0,
+ (struct sockaddr *)to, slen);
+ goto done;
+ }
+
/* Assemble the headers... */
assemble_hw_header(interface, buf, &bufp, hto);
assemble_udp_ip_header(interface, buf, &bufp, from.s_addr,
@@ -240,6 +288,7 @@ send_packet(struct interface_info *interface,
iov[1].iov_len = len;
result = writev(interface->wfdesc, iov, 2);
+ done:
if (result == -1)
warning("send_packet: %m");
return (result);
diff --git a/usr.sbin/dhcrelay/dhcp.h b/usr.sbin/dhcrelay/dhcp.h
index 50281ea6f92..67feae846a2 100644
--- a/usr.sbin/dhcrelay/dhcp.h
+++ b/usr.sbin/dhcrelay/dhcp.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: dhcp.h,v 1.4 2007/03/02 18:26:29 stevesk Exp $ */
+/* $OpenBSD: dhcp.h,v 1.5 2009/09/03 11:56:49 reyk Exp $ */
/* Protocol structures... */
@@ -80,13 +80,15 @@ struct dhcp_packet {
#define BOOTP_BROADCAST 32768L
/* Possible values for hardware type (htype) field... */
-#define HTYPE_ETHER 1 /* Ethernet */
-#define HTYPE_IEEE802 6 /* IEEE 802.2 Token Ring... */
-#define HTYPE_FDDI 8 /* FDDI... */
+#define HTYPE_ETHER 1 /* Ethernet */
+#define HTYPE_IEEE802 6 /* IEEE 802.2 Token Ring... */
+#define HTYPE_FDDI 8 /* FDDI... */
+#define HTYPE_IPSEC_TUNNEL 31 /* IPsec Tunnel (RFC3456) */
/* Magic cookie validating dhcp options field (and bootp vendor
extensions field). */
#define DHCP_OPTIONS_COOKIE "\143\202\123\143"
+#define DHCP_OPTIONS_COOKIE_LEN 4
/* DHCP Option codes: */
@@ -153,6 +155,7 @@ struct dhcp_packet {
#define DHO_DHCP_CLASS_IDENTIFIER 60
#define DHO_DHCP_CLIENT_IDENTIFIER 61
#define DHO_DHCP_USER_CLASS_ID 77
+#define DHO_RELAY_AGENT_INFORMATION 82
#define DHO_END 255
/* DHCP message types. */
@@ -164,3 +167,8 @@ struct dhcp_packet {
#define DHCPNAK 6
#define DHCPRELEASE 7
#define DHCPINFORM 8
+
+/* Relay Agent Information sub-options */
+#define RAI_CIRCUIT_ID 1
+#define RAI_REMOTE_ID 2
+#define RAI_AGENT_ID 3
diff --git a/usr.sbin/dhcrelay/dhcpd.h b/usr.sbin/dhcrelay/dhcpd.h
index 332dfb33a14..c1d6917460b 100644
--- a/usr.sbin/dhcrelay/dhcpd.h
+++ b/usr.sbin/dhcrelay/dhcpd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: dhcpd.h,v 1.11 2007/01/04 19:12:41 stevesk Exp $ */
+/* $OpenBSD: dhcpd.h,v 1.12 2009/09/03 11:56:49 reyk Exp $ */
/*
* Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
@@ -150,7 +150,8 @@ ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
/* dispatch.c */
extern void (*bootp_packet_handler)(struct interface_info *,
- struct dhcp_packet *, int, unsigned int, struct iaddr, struct hardware *);
+ struct dhcp_packet *, int, unsigned int, struct iaddr,
+ struct hardware *);
void discover_interfaces(struct interface_info *);
void dispatch(void);
void got_one(struct protocol *);
@@ -170,6 +171,7 @@ ssize_t decode_udp_ip_header(struct interface_info *, unsigned char *,
/* dhcrelay.c */
extern u_int16_t server_port;
extern u_int16_t client_port;
+extern int server_fd;
/* crap */
extern time_t cur_time;
diff --git a/usr.sbin/dhcrelay/dhcrelay.8 b/usr.sbin/dhcrelay/dhcrelay.8
index ab5794391b1..07893c00432 100644
--- a/usr.sbin/dhcrelay/dhcrelay.8
+++ b/usr.sbin/dhcrelay/dhcrelay.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: dhcrelay.8,v 1.8 2007/05/31 19:20:23 jmc Exp $
+.\" $OpenBSD: dhcrelay.8,v 1.9 2009/09/03 11:56:49 reyk Exp $
.\"
.\" Copyright (c) 1997 The Internet Software Consortium.
.\" All rights reserved.
@@ -36,7 +36,7 @@
.\" see ``http://www.isc.org/isc''. To learn more about Vixie
.\" Enterprises, see ``http://www.vix.com''.
.\"
-.Dd $Mdocdate: May 31 2007 $
+.Dd $Mdocdate: September 3 2009 $
.Dt DHCRELAY 8
.Os
.Sh NAME
@@ -44,7 +44,7 @@
.Nd Dynamic Host Configuration Protocol Relay Agent
.Sh SYNOPSIS
.Nm
-.Op Fl d
+.Op Fl do
.Fl i Ar interface
.Ar server1 Op Ar ... serverN
.Sh DESCRIPTION
@@ -69,6 +69,14 @@ as well as the name of the network interface that
should attempt to configure,
must be specified on the command line.
.Pp
+.Nm
+supports relaying of DHCP traffic to configure IPsec tunnel mode
+clients when listening on the
+.Xr enc 4
+interface.
+The DHCP server has to support RFC 3046 to echo back the relay agent
+information to allow stateless DHCP reply to IPsec tunnel mapping.
+.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl d
@@ -82,12 +90,18 @@ to always run as a foreground process.
The name of the network interface that
.Nm
should attempt to configure.
+At least one IPv4 address has to be configured on this interface.
+.It Fl o
+Add the relay agent information option.
+By default, this is only enabled for the
+.Xr enc 4
+interface.
.El
.Sh SEE ALSO
.Xr dhclient 8 ,
.Xr dhcpd 8
.Pp
-RFC 2132, RFC 2131.
+RFC 2132, RFC 2131, RFC 3046, RFC 3456
.Sh AUTHORS
.An -nosplit
.Nm
diff --git a/usr.sbin/dhcrelay/dhcrelay.c b/usr.sbin/dhcrelay/dhcrelay.c
index 1f5f872b998..b80a28494b9 100644
--- a/usr.sbin/dhcrelay/dhcrelay.c
+++ b/usr.sbin/dhcrelay/dhcrelay.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: dhcrelay.c,v 1.31 2008/07/09 20:08:13 sobrado Exp $ */
+/* $OpenBSD: dhcrelay.c,v 1.32 2009/09/03 11:56:49 reyk Exp $ */
/*
* Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org>
@@ -47,6 +47,9 @@ void relay(struct interface_info *, struct dhcp_packet *, int,
char *print_hw_addr(int, int, unsigned char *);
void got_response(struct protocol *);
+ssize_t relay_agentinfo(struct interface_info *, struct dhcp_packet *,
+ size_t, struct in_addr *, struct in_addr *);
+
time_t cur_time;
int log_perror = 1;
@@ -55,6 +58,8 @@ u_int16_t server_port;
u_int16_t client_port;
int log_priority;
struct interface_info *interfaces = NULL;
+int server_fd;
+int oflag;
struct server_list {
struct server_list *next;
@@ -75,7 +80,7 @@ main(int argc, char *argv[])
openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
setlogmask(LOG_UPTO(LOG_INFO));
- while ((ch = getopt(argc, argv, "di:")) != -1) {
+ while ((ch = getopt(argc, argv, "adi:o")) != -1) {
switch (ch) {
case 'd':
no_daemon = 1;
@@ -89,6 +94,11 @@ main(int argc, char *argv[])
strlcpy(interfaces->name, optarg,
sizeof(interfaces->name));
break;
+ case 'o':
+ /* add the relay agent information option */
+ oflag++;
+ break;
+
default:
usage();
/* not reached */
@@ -125,7 +135,8 @@ main(int argc, char *argv[])
argv++;
}
- log_perror = 0;
+ if (!no_daemon)
+ log_perror = 0;
if (interfaces == NULL)
error("no interface given");
@@ -140,6 +151,10 @@ main(int argc, char *argv[])
discover_interfaces(interfaces);
+ /* 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;
@@ -165,6 +180,21 @@ main(int argc, char *argv[])
add_protocol("server", sp->fd, got_response, sp);
}
+ /* Socket used to forward packets to the DHCP server */
+ 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)
+ error("socket: %m");
+ opt = 1;
+ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
+ &opt, sizeof(opt)) == -1)
+ error("setsockopt: %m");
+ if (bind(server_fd, (struct sockaddr *)&laddr,
+ sizeof(laddr)) == -1)
+ error("bind: %m");
+ }
+
tzset();
time(&cur_time);
@@ -222,6 +252,13 @@ relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
memcpy(hto.haddr, packet->chaddr, hto.hlen);
hto.htype = packet->htype;
+ if ((length = relay_agentinfo(interfaces,
+ packet, length, NULL, &to.sin_addr)) == -1) {
+ note("ingnoring BOOTREPLY with invalid "
+ "relay agent information");
+ return;
+ }
+
if (send_packet(interfaces, packet, length,
interfaces->primary_address, &to, &hto) != -1)
debug("forwarded BOOTREPLY for %s to %s",
@@ -248,6 +285,13 @@ relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
correct net. */
packet->giaddr = ip->primary_address;
+ if ((length = relay_agentinfo(ip, packet, length,
+ (struct in_addr *)from.iabuf, NULL)) == -1) {
+ note("ingnoring 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) {
@@ -265,7 +309,7 @@ usage(void)
{
extern char *__progname;
- fprintf(stderr, "usage: %s [-d] -i interface server1 [... serverN]\n",
+ fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n",
__progname);
exit(1);
}
@@ -311,6 +355,7 @@ got_response(struct protocol *l)
} 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) {
/*
@@ -340,3 +385,97 @@ got_response(struct protocol *l)
sp->to.sin_port, ifrom, NULL);
}
}
+
+ssize_t
+relay_agentinfo(struct interface_info *info, struct dhcp_packet *packet,
+ size_t length, struct in_addr *from, struct in_addr *to)
+{
+ u_int8_t *p;
+ u_int i, j, railen;
+ ssize_t optlen, maxlen, grow;
+
+ if (!oflag)
+ return (length);
+
+ /* Buffer length vs. received packet length */
+ maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
+ optlen = length - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
+ if (maxlen < 1 || optlen < 1)
+ return (length);
+
+ if (memcmp(packet->options, DHCP_OPTIONS_COOKIE,
+ DHCP_OPTIONS_COOKIE_LEN) != 0)
+ return (length);
+ p = packet->options + DHCP_OPTIONS_COOKIE_LEN;
+
+ for (i = 0; i < (u_int)optlen && *p != DHO_END;) {
+ if (*p == DHO_PAD)
+ j = 1;
+ else
+ j = p[1] + 2;
+
+ if ((i + j) > (u_int)optlen) {
+ warning("truncated dhcp options");
+ break;
+ }
+
+ /* Revert any other relay agent information */
+ if (*p == DHO_RELAY_AGENT_INFORMATION) {
+ if (to != NULL) {
+ /* Check the relay agent information */
+ railen = 8 + sizeof(struct in_addr);
+ if (j >= railen &&
+ p[1] == (railen - 2) &&
+ p[2] == RAI_CIRCUIT_ID &&
+ p[3] == 2 &&
+ p[4] == (u_int8_t)(info->index << 8) &&
+ p[5] == (info->index & 0xff) &&
+ p[6] == RAI_REMOTE_ID &&
+ p[7] == sizeof(*to))
+ memcpy(to, p + 8, sizeof(*to));
+
+ /* It should be the last option */
+ memset(p, 0, j);
+ *p = DHO_END;
+ } else {
+ /* Discard invalid option from a client */
+ if (!packet->giaddr.s_addr)
+ return (-1);
+ }
+ return (length);
+ }
+
+ p += j;
+ i += j;
+
+ if (from != NULL && (*p == DHO_END || (i >= optlen))) {
+ j = 8 + sizeof(*from);
+ if ((i + j) > (u_int)maxlen) {
+ warning("skipping agent information");
+ break;
+ }
+
+ /* Append the relay agent information if it fits */
+ p[0] = DHO_RELAY_AGENT_INFORMATION;
+ p[1] = j - 2;
+ p[2] = RAI_CIRCUIT_ID;
+ p[3] = 2;
+ p[4] = info->index << 8;
+ p[5] = info->index & 0xff;
+ p[6] = RAI_REMOTE_ID;
+ p[7] = sizeof(*from);
+ memcpy(p + 8, from, sizeof(*from));
+
+ /* Do we need to increase the packet length? */
+ grow = j + 1 - (optlen - i);
+ if (grow > 0)
+ length += grow;
+ p += j;
+
+ *p = DHO_END;
+ break;
+ }
+ }
+
+ return (length);
+}
diff --git a/usr.sbin/dhcrelay/dispatch.c b/usr.sbin/dhcrelay/dispatch.c
index cec8edd4760..19155f9b6a8 100644
--- a/usr.sbin/dhcrelay/dispatch.c
+++ b/usr.sbin/dhcrelay/dispatch.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: dispatch.c,v 1.8 2008/09/15 20:39:21 claudio Exp $ */
+/* $OpenBSD: dispatch.c,v 1.9 2009/09/03 11:56:49 reyk Exp $ */
/*
* Copyright 2004 Henning Brauer <henning@openbsd.org>
@@ -44,6 +44,8 @@
#include <sys/ioctl.h>
#include <net/if_media.h>
+#include <net/if_types.h>
+
#include <ifaddrs.h>
#include <poll.h>
@@ -90,10 +92,15 @@ discover_interfaces(struct interface_info *iface)
if (ifa->ifa_addr->sa_family == AF_LINK) {
struct sockaddr_dl *foo =
(struct sockaddr_dl *)ifa->ifa_addr;
+ struct if_data *ifi =
+ (struct if_data *)ifa->ifa_data;
iface->index = foo->sdl_index;
iface->hw_address.hlen = foo->sdl_alen;
- iface->hw_address.htype = HTYPE_ETHER; /* XXX */
+ if (ifi->ifi_type == IFT_ENC)
+ iface->hw_address.htype = HTYPE_IPSEC_TUNNEL;
+ else
+ iface->hw_address.htype = HTYPE_ETHER; /* XXX */
memcpy(iface->hw_address.haddr,
LLADDR(foo), foo->sdl_alen);
} else if (ifa->ifa_addr->sa_family == AF_INET) {
diff --git a/usr.sbin/dhcrelay/packet.c b/usr.sbin/dhcrelay/packet.c
index 7ba4acb6649..3c1a62f6b6f 100644
--- a/usr.sbin/dhcrelay/packet.c
+++ b/usr.sbin/dhcrelay/packet.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.c,v 1.2 2004/04/20 20:56:47 canacar Exp $ */
+/* $OpenBSD: packet.c,v 1.3 2009/09/03 11:56:49 reyk Exp $ */
/* Packet assembly code, originally contributed by Archie Cobbs. */
@@ -42,6 +42,7 @@
#include "dhcpd.h"
+#include <net/if_enc.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
@@ -148,14 +149,32 @@ decode_hw_header(struct interface_info *interface, unsigned char *buf,
int bufix, struct hardware *from)
{
struct ether_header eh;
+ size_t offset = 0;
- memcpy(&eh, buf + bufix, ETHER_HEADER_SIZE);
+ if (interface->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
+ u_int32_t ip_len;
+ struct ip *ip;
+
+ bufix += ENC_HDRLEN;
+ ip_len = (buf[bufix] & 0xf) << 2;
+ ip = (struct ip *)(buf + bufix);
+
+ /* Encapsulated IP */
+ if (ip->ip_p != IPPROTO_IPIP)
+ return (-1);
+
+ bzero(&eh, sizeof(eh));
+ offset = ENC_HDRLEN + ip_len;
+ } else {
+ memcpy(&eh, buf + bufix, ETHER_HEADER_SIZE);
+ offset = sizeof(eh);
+ }
memcpy(from->haddr, eh.ether_shost, sizeof(eh.ether_shost));
from->htype = ARPHRD_ETHER;
from->hlen = sizeof(eh.ether_shost);
- return (sizeof(eh));
+ return (offset);
}
ssize_t