On Thu, Dec 08, 2016 at 08:43:20PM +0100, Rafael Zalamena wrote:
> This diff implements layer 2 relaying support for dhcrelay with further
> support for Relay Agent Info (RFC 3046). This feature is mostly used by
> switched networks that might not be using IP addresses when in the edge
> with the customer.
>
> Basically this diff allows you to run dhcrelay on interfaces without
> addresses and doesn't require you to specify an DHCP server address.
> Instead you just need to specify the output port.
>
> I also updated the man page to show the new options for layer 2 relaying
> Relay Agent Info knobs, since you might want to let the remote DHCP
> server know where the DHCP packet is coming from.
I forgot to add the man page in the last diff, here is a new one with
the man page modifications.
ok?
Index: bpf.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/bpf.c,v
retrieving revision 1.13
diff -u -p -r1.13 bpf.c
--- bpf.c 8 Dec 2016 19:18:15 -0000 1.13
+++ bpf.c 9 Dec 2016 09:03:44 -0000
@@ -93,6 +93,38 @@ if_register_send(struct interface_info *
}
/*
+ * Packet filter program: 'ip and udp and dst port CLIENT_PORT'
+ */
+struct bpf_insn dhcp_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_IP, 0, 8),
+
+ /* Make sure it's a UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+ 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, 20),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+ /* Make sure it's to the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_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_sfilter_len = sizeof(dhcp_bpf_sfilter) / sizeof(struct bpf_insn);
+
+/*
* Packet filter program: 'ip and udp and dst port SERVER_PORT'
*/
struct bpf_insn dhcp_bpf_filter[] = {
@@ -161,6 +193,38 @@ struct bpf_insn dhcp_bpf_efilter[] = {
int dhcp_bpf_efilter_len = sizeof(dhcp_bpf_efilter) / sizeof(struct bpf_insn);
/*
+ * Packet write filter program: 'ip and udp and src port CLIENT_PORT'
+ */
+struct bpf_insn dhcp_bpf_swfilter[] = {
+ /* 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_IP, 0, 8),
+
+ /* Make sure it's a UDP packet... */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+ 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, 20),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ /* Get the IP header length... */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+ /* Make sure it's from the right port... */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_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_swfilter_len = sizeof(dhcp_bpf_swfilter) / sizeof(struct
bpf_insn);
+
+/*
* Packet write filter program: 'ip and udp and src port SERVER_PORT'
*/
struct bpf_insn dhcp_bpf_wfilter[] = {
@@ -193,7 +257,7 @@ struct bpf_insn dhcp_bpf_wfilter[] = {
int dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
void
-if_register_receive(struct interface_info *info)
+if_register_receive(struct interface_info *info, int isserver)
{
struct bpf_version v;
struct bpf_program p;
@@ -234,7 +298,10 @@ if_register_receive(struct interface_inf
info->rbuf_len = 0;
/* Set up the bpf filter program structure. */
- if (info->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
+ if (isserver) {
+ p.bf_len = dhcp_bpf_sfilter_len;
+ p.bf_insns = dhcp_bpf_sfilter;
+ } else if (info->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
p.bf_len = dhcp_bpf_efilter_len;
p.bf_insns = dhcp_bpf_efilter;
} else {
@@ -245,8 +312,13 @@ if_register_receive(struct interface_inf
error("Can't install packet filter program: %m");
/* Set up the bpf write filter program structure. */
- p.bf_len = dhcp_bpf_wfilter_len;
- p.bf_insns = dhcp_bpf_wfilter;
+ if (isserver) {
+ p.bf_len = dhcp_bpf_swfilter_len;
+ p.bf_insns = dhcp_bpf_swfilter;
+ } else {
+ p.bf_len = dhcp_bpf_wfilter_len;
+ p.bf_insns = dhcp_bpf_wfilter;
+ }
if (ioctl(info->rfdesc, BIOCSETWF, &p) == -1)
error("Can't install write filter program: %m");
Index: dhcpd.h
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcpd.h,v
retrieving revision 1.17
diff -u -p -r1.17 dhcpd.h
--- dhcpd.h 8 Dec 2016 19:18:15 -0000 1.17
+++ dhcpd.h 9 Dec 2016 09:03:44 -0000
@@ -39,6 +39,12 @@
* Enterprises, see ``http://www.vix.com''.
*/
+enum dhcp_relay_mode {
+ DRM_UNKNOWN,
+ DRM_LAYER2,
+ DRM_LAYER3,
+};
+
#define SERVER_PORT 67
#define CLIENT_PORT 68
@@ -123,7 +129,7 @@ int debug(char *, ...) __attribute__ ((_
/* bpf.c */
int if_register_bpf(struct interface_info *);
void if_register_send(struct interface_info *);
-void if_register_receive(struct interface_info *);
+void if_register_receive(struct interface_info *, int);
ssize_t send_packet(struct interface_info *,
struct dhcp_packet *, size_t, struct packet_ctx *);
ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
@@ -133,7 +139,7 @@ ssize_t receive_packet(struct interface_
extern void (*bootp_packet_handler)(struct interface_info *,
struct dhcp_packet *, int, struct packet_ctx *);
struct interface_info *get_interface(const char *,
- void (*)(struct protocol *));
+ void (*)(struct protocol *), int isserver);
void dispatch(void);
void got_one(struct protocol *);
void add_protocol(char *, int, void (*)(struct protocol *), void *);
Index: dhcrelay.8
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcrelay.8,v
retrieving revision 1.12
diff -u -p -r1.12 dhcrelay.8
--- dhcrelay.8 16 Jul 2013 11:13:33 -0000 1.12
+++ dhcrelay.8 9 Dec 2016 09:05:13 -0000
@@ -45,6 +45,8 @@
.Sh SYNOPSIS
.Nm
.Op Fl do
+.Op Fl C Ar circuit-id
+.Op Fl R Ar remote-id
.Fl i Ar interface
.Ar server1 Op Ar ... serverN
.Sh DESCRIPTION
@@ -62,6 +64,11 @@ forwards it to the list of DHCP servers
When a reply is received, it is broadcast or unicast on the network from
whence the original request came.
.Pp
+The server might be a name, address or interface.
+.Nm
+will operate in layer 2 mode when the specified servers are interfaces,
+otherwise it will operate in layer 3 mode.
+.Pp
The name of at least one DHCP server to which DHCP and BOOTP requests
should be relayed,
as well as the name of the network interface that
@@ -73,7 +80,7 @@ must be specified on the command line.
supports relaying of DHCP traffic to configure IPsec tunnel mode
clients when listening on the
.Xr enc 4
-interface.
+interface using layer 3 mode only.
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
Index: dhcrelay.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcrelay.c,v
retrieving revision 1.49
diff -u -p -r1.49 dhcrelay.c
--- dhcrelay.c 8 Dec 2016 19:18:15 -0000 1.49
+++ dhcrelay.c 9 Dec 2016 09:03:44 -0000
@@ -66,6 +66,8 @@ 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 *);
@@ -84,7 +86,12 @@ struct interface_info *interfaces = NULL
int server_fd;
int oflag;
+enum dhcp_relay_mode drm = DRM_UNKNOWN;
+const char *rai_circuit = NULL;
+const char *rai_remote = NULL;
+
struct server_list {
+ struct interface_info *intf;
struct server_list *next;
struct sockaddr_in to;
int fd;
@@ -98,6 +105,7 @@ main(int argc, char *argv[])
struct server_list *sp = NULL;
struct passwd *pw;
struct sockaddr_in laddr;
+ int optslen;
daemonize = 1;
@@ -105,8 +113,11 @@ main(int argc, char *argv[])
openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
setlogmask(LOG_UPTO(LOG_INFO));
- while ((ch = getopt(argc, argv, "adi:o")) != -1) {
+ while ((ch = getopt(argc, argv, "aC:di:oR:")) != -1) {
switch (ch) {
+ case 'C':
+ rai_circuit = optarg;
+ break;
case 'd':
daemonize = 0;
break;
@@ -114,7 +125,7 @@ main(int argc, char *argv[])
if (interfaces != NULL)
usage();
- interfaces = get_interface(optarg, got_one);
+ interfaces = get_interface(optarg, got_one, 0);
if (interfaces == NULL)
error("interface '%s' not found", optarg);
break;
@@ -122,6 +133,9 @@ main(int argc, char *argv[])
/* add the relay agent information option */
oflag++;
break;
+ case 'R':
+ rai_remote = optarg;
+ break;
default:
usage();
@@ -135,10 +149,42 @@ main(int argc, char *argv[])
if (argc < 1)
usage();
+ if (rai_remote != NULL && rai_circuit == NULL)
+ error("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 > 255)
+ error("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)
+ error("calloc");
+
+ if ((sp->intf = get_interface(argv[0], got_one, 1)) != NULL) {
+ if (drm == DRM_LAYER3)
+ error("don't mix interfaces with hosts");
+
+ if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL)
+ error("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 {
@@ -149,12 +195,16 @@ main(int argc, char *argv[])
iap = ((struct in_addr *)he->h_addr_list[0]);
}
if (iap) {
- if ((sp = calloc(1, sizeof *sp)) == NULL)
- error("calloc");
+ if (drm == DRM_LAYER2)
+ error("don't mix interfaces with hosts");
+
+ drm = DRM_LAYER3;
sp->next = servers;
servers = sp;
memcpy(&sp->to.sin_addr, iap, sizeof *iap);
- }
+ } else
+ free(sp);
+
argc--;
argv++;
}
@@ -167,7 +217,9 @@ main(int argc, char *argv[])
if (interfaces == NULL)
error("no interface given");
- if (interfaces->primary_address.s_addr == 0)
+ /* We need an address for running layer 3 mode. */
+ if (drm == DRM_LAYER3 &&
+ interfaces->primary_address.s_addr == 0)
error("interface '%s' does not have an address",
interfaces->name);
@@ -192,6 +244,9 @@ main(int argc, char *argv[])
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;
+
sp->to.sin_port = server_port;
sp->to.sin_family = AF_INET;
sp->to.sin_len = sizeof sp->to;
@@ -234,7 +289,10 @@ main(int argc, char *argv[])
tzset();
time(&cur_time);
- bootp_packet_handler = relay;
+ if (drm == DRM_LAYER3)
+ bootp_packet_handler = relay;
+ else
+ bootp_packet_handler = l2relay;
if ((pw = getpwnam("_dhcp")) == NULL)
error("user \"_dhcp\" not found");
@@ -374,7 +432,8 @@ usage(void)
{
extern char *__progname;
- fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n",
+ fprintf(stderr, "usage: %s [-CdoR] "
+ "-i interface server1 [... serverN]\n",
__progname);
exit(1);
}
@@ -598,4 +657,259 @@ get_rdomain(char *name)
close(s);
return rv;
+}
+
+ssize_t
+relayagentinfo_append(struct dhcp_packet *dp, size_t dplen)
+{
+ uint8_t *p;
+ ssize_t newtotal = dplen;
+ size_t opttotal;
+ int optlen, i, hasinfo = 0;
+ int circuitlen, remotelen;
+
+ if (rai_circuit == NULL)
+ return (dplen);
+
+ p = (uint8_t *)&dp->options;
+ if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
+ note("invalid dhcp options cookie");
+ return (-1);
+ }
+
+ p += DHCP_OPTIONS_COOKIE_LEN;
+ opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
+
+ for (i = 0; i < opttotal && *p != DHO_END;) {
+ if (*p == DHO_PAD)
+ optlen = 1;
+ else
+ optlen = p[1] + 2;
+
+ if ((i + optlen) > opttotal) {
+ note("truncated dhcp options");
+ return (-1);
+ }
+
+ if (*p == DHO_RELAY_AGENT_INFORMATION) {
+ hasinfo = 1;
+ continue;
+ }
+
+ p += optlen;
+ i += optlen;
+
+ /* We reached the end, append the relay agent info. */
+ if (*p == DHO_END || i >= opttotal) {
+ /* We already have the Relay Agent Info, skip it. */
+ if (hasinfo)
+ continue;
+
+ circuitlen = strlen(rai_circuit);
+ if (rai_remote != NULL)
+ remotelen = strlen(rai_remote);
+ else
+ remotelen = 0;
+
+ *p++ = DHO_RELAY_AGENT_INFORMATION;
+ if (rai_remote != NULL)
+ *p++ = (2 + circuitlen) + (2 + remotelen);
+ else
+ *p++ = (2 + circuitlen);
+
+ newtotal += 2;
+
+ *p++ = RAI_CIRCUIT_ID;
+ *p++ = circuitlen;
+ memcpy(p, rai_circuit, circuitlen);
+ p += circuitlen;
+ newtotal += 2 + circuitlen;
+
+ if (rai_remote != NULL) {
+ *p++ = RAI_REMOTE_ID;
+ *p++ = remotelen;
+ memcpy(p, rai_remote, remotelen);
+ p += remotelen;
+ newtotal += 2 + remotelen;
+ }
+
+ *p = DHO_END;
+ }
+ }
+
+ return (newtotal);
+}
+
+int
+relayagentinfo_cmp(uint8_t *p, int plen)
+{
+ int len;
+ char buf[256];
+
+ switch (*p) {
+ case RAI_CIRCUIT_ID:
+ len = *(p + 1);
+ memcpy(buf, p + 2, len);
+ buf[len + 1] = 0;
+
+ return (strcmp(rai_circuit, buf));
+
+ case RAI_REMOTE_ID:
+ len = *(p + 1);
+ memcpy(buf, p + 2, len);
+ buf[len + 1] = 0;
+
+ return (strcmp(rai_circuit, buf));
+
+ default:
+ /* Unmatched type */
+ note("unmatched relay info %d", *p);
+ return (0);
+ }
+}
+
+ssize_t
+relayagentinfo_remove(struct dhcp_packet *dp, size_t dplen)
+{
+ uint8_t *p, *np, *startp, *endp;
+ size_t opttotal, optleft;
+ int suboptlen, optlen, i;
+ int remaining, matched = 0;
+
+ startp = (uint8_t *)dp;
+ p = (uint8_t *)&dp->options;
+ if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
+ note("invalid dhcp options cookie");
+ return (-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] + 2;
+
+ if ((i + optlen) > opttotal) {
+ note("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;
+ return (endp - startp);
+ }
+
+ remaining = optlen;
+ while (remaining > 0) {
+ suboptlen = *(p + 1);
+ remaining -= 2 + suboptlen;
+
+ matched = 1;
+ if (relayagentinfo_cmp(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);
+
+ return (endp - p);
+ }
+
+ p += optlen;
+ i += optlen;
+ optleft -= optlen;
+ }
+
+ return (endp - startp);
+}
+
+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)) {
+ note("Discarding packet with invalid hlen.");
+ return;
+ }
+
+ switch (dp->op) {
+ case BOOTREQUEST:
+ /* Add the relay agent info asked by the user. */
+ if ((dplen = relayagentinfo_append(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;
+
+ 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) {
+ 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 = relayagentinfo_remove(dp, length)) == -1)
+ 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.
+ */
+ dp->giaddr.s_addr = 0x0;
+
+ if (ip != interfaces) {
+ 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:
+ debug("invalid operation type '%d'", dp->op);
+ return;
+ }
}
Index: dispatch.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dispatch.c,v
retrieving revision 1.14
diff -u -p -r1.14 dispatch.c
--- dispatch.c 8 Dec 2016 19:18:15 -0000 1.14
+++ dispatch.c 9 Dec 2016 09:03:44 -0000
@@ -74,7 +74,8 @@ void (*bootp_packet_handler)(struct inte
static int interface_status(struct interface_info *ifinfo);
struct interface_info *
-get_interface(const char *ifname, void (*handler)(struct protocol *))
+get_interface(const char *ifname, void (*handler)(struct protocol *),
+ int isserver)
{
struct interface_info *iface;
struct ifaddrs *ifap, *ifa;
@@ -145,7 +146,7 @@ get_interface(const char *ifname, void (
error("interface name '%s' too long", ifname);
/* Register the interface... */
- if_register_receive(iface);
+ if_register_receive(iface, isserver);
if_register_send(iface);
add_protocol(iface->name, iface->rfdesc, handler, iface);