]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
bpf: ARP and BOOTP filter improvements
authorRoy Marples <roy@marples.name>
Sun, 5 Mar 2017 21:05:24 +0000 (21:05 +0000)
committerRoy Marples <roy@marples.name>
Sun, 5 Mar 2017 21:05:24 +0000 (21:05 +0000)
The ARP filter now checks hardware and protocol length matches
the interface, it's not from the interface itself and either the
source ip or target ip is one of our addresses of interest.

The BOOTP filter now checks for BOOTREPLY and matching xid.
If the interface hardware address fits inside chaddr then
that is checked as well.

arp.c
bpf-filter.h [deleted file]
bpf.c [new file with mode: 0644]
configure
dhcp.c
dhcp.h
if-bsd.c
if-linux.c
if.h

diff --git a/arp.c b/arp.c
index 7b8ebbd52ba04113887ba6bfe3a1692598676c26..aaff37ece488fe360125dac861591a71789c1ec5 100644 (file)
--- a/arp.c
+++ b/arp.c
@@ -228,7 +228,7 @@ arp_open(struct interface *ifp)
 
        state = ARP_STATE(ifp);
        if (state->fd == -1) {
-               state->fd = if_openraw(ifp, ETHERTYPE_ARP);
+               state->fd = if_openraw(ifp, ETHERTYPE_ARP, bpf_arp);
                if (state->fd == -1) {
                        logger(ifp->ctx, LOG_ERR, "%s: %s: %m",
                            __func__, ifp->name);
@@ -417,6 +417,9 @@ arp_new(struct interface *ifp, const struct in_addr *addr)
                astate->addr = *addr;
        state = ARP_STATE(ifp);
        TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
+
+       bpf_arp(ifp, state->fd);
+
        return astate;
 }
 
@@ -449,7 +452,8 @@ arp_free(struct arp_state *astate)
                arp_close(ifp);
                free(state);
                ifp->if_data[IF_DATA_ARP] = NULL;
-       }
+       } else
+               bpf_arp(ifp, state->fd);
 }
 
 static void
diff --git a/bpf-filter.h b/bpf-filter.h
deleted file mode 100644 (file)
index 8cb696b..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2017 Roy Marples <roy@marples.name>
- *
- * 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.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
- */
-
-#ifndef BPF_ETHCOOK
-# define BPF_ETHCOOK 0
-#endif
-#ifndef BPF_WHOLEPACKET
-# define BPF_WHOLEPACKET ~0U
-#endif
-
-static const struct bpf_insn arp_bpf_filter [] = {
-#ifndef BPF_SKIPTYPE
-       /* Make sure this is an ARP packet... */
-       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 1, 0),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-#endif
-       /* Make sure this is for IP ... */
-       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 16 + BPF_ETHCOOK),
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-       /* Make sure this is an ARP REQUEST... */
-       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK),
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),
-       /* or ARP REPLY... */
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 1),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-       /* Pass back the whole packet. */
-       BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
-};
-#define arp_bpf_filter_len __arraycount(arp_bpf_filter)
-
-static const struct bpf_insn bootp_bpf_filter [] = {
-#ifndef BPF_SKIPTYPE
-       /* 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, 1, 0),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-#endif
-       /* Make sure it's a UDP packet... */
-       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK),
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-       /* Make sure this isn't a fragment... */
-       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK),
-       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-       /* Get the IP header length... */
-       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 + BPF_ETHCOOK),
-       /* Make sure it's to the right port... */
-       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK),
-       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTPC, 1, 0),
-       BPF_STMT(BPF_RET + BPF_K, 0),
-       /* Pass back the whole packet. */
-       BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
-};
-#define bootp_bpf_filter_len __arraycount(bootp_bpf_filter)
diff --git a/bpf.c b/bpf.c
new file mode 100644 (file)
index 0000000..6f898fc
--- /dev/null
+++ b/bpf.c
@@ -0,0 +1,362 @@
+/*
+ * dhcpcd: BPF arp and bootp functions
+ * Copyright (c) 2006-2017 Roy Marples <roy@marples.name>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#ifdef __linux__
+#include <linux/filter.h>
+#else
+#include <net/bpf.h>
+#endif
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "common.h"
+#include "arp.h"
+#include "dhcp.h"
+#include "if.h"
+
+#define        ARP_ADDRS_MAX   3
+
+/* BPF helper macros */
+#ifdef __linux__
+#define        BPF_L2L                 0
+#define        BPF_L2I                 0
+#define        BPF_WHOLEPACKET         0x0fffffff /* work around buggy LPF filters */
+#else
+#define        BPF_L2L                 ETHER_ADDR_LEN + ETHER_ADDR_LEN + 2
+#define        BPF_L2I                 3
+#define        BPF_WHOLEPACKET         ~0U
+#endif
+
+/* Macros to update the BPF structure */
+#define        BPF_SET_STMT(insn, c, v) {                              \
+       (insn)->code = (c);                                     \
+       (insn)->jt = 0;                                         \
+       (insn)->jf = 0;                                         \
+       (insn)->k = (uint32_t)(v);                              \
+};
+
+#define        BPF_SET_JUMP(insn, c, v, t, f) {                        \
+       (insn)->code = (c);                                     \
+       (insn)->jt = (t);                                       \
+       (insn)->jf = (f);                                       \
+       (insn)->k = (uint32_t)(v);                              \
+};
+
+static unsigned int
+bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off,
+    bool equal, uint8_t *hwaddr, size_t hwaddr_len)
+{
+       struct bpf_insn *bp;
+       size_t maclen, nlft, njmps;
+       uint32_t mac32;
+       uint16_t mac16;
+       uint8_t jt, jf;
+
+       /* Calc the number of jumps */
+       if ((hwaddr_len / 4) >= 128) {
+               errno = EINVAL;
+               return 0;
+       }
+       njmps = (hwaddr_len / 4) * 2; /* 2 instructions per check */
+       /* We jump after the 1st check. */
+       if (njmps)
+               njmps -= 2;
+       nlft = hwaddr_len % 4;
+       if (nlft) {
+               njmps += (nlft / 2) * 2;
+               nlft = nlft % 2;
+               if (nlft)
+                       njmps += 2;
+
+       }
+
+       /* Skip to positive finish. */
+       njmps++;
+       jt = equal ? (uint8_t)njmps : 0;
+       jf = equal ? 0 : (uint8_t)njmps;
+
+       bp = bpf;
+       for (; hwaddr_len > 0;
+            hwaddr += maclen, hwaddr_len -= maclen, off += maclen)
+       {
+               if (bpf_len < 3) {
+                       errno = ENOBUFS;
+                       return 0;
+               }
+               bpf_len -= 3;
+
+               if (hwaddr_len >= 4) {
+                       maclen = sizeof(mac32);
+                       memcpy(&mac32, hwaddr, maclen);
+                       BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_ABS,
+                                    BPF_L2L + off);
+                       bp++;
+                       BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                                    htonl(mac32), jt, jf);
+               } else if (hwaddr_len >= 2) {
+                       maclen = sizeof(mac16);
+                       memcpy(&mac16, hwaddr, maclen);
+                       BPF_SET_STMT(bp, BPF_LD + BPF_H + BPF_ABS,
+                                    BPF_L2L + off);
+                       bp++;
+                       BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                                    htons(mac16), jt, jf);
+               } else {
+                       maclen = sizeof(*hwaddr);
+                       BPF_SET_STMT(bp, BPF_LD + BPF_B + BPF_ABS,
+                                    BPF_L2L + off);
+                       bp++;
+                       BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                                    *hwaddr, jt, jf);
+               }
+               if (jt)
+                       jt = (uint8_t)(jt - 2);
+               if (jf)
+                       jf = (uint8_t)(jf - 2);
+               bp++;
+       }
+
+       /* Last step is always return failure.
+        * Next step is a positive finish. */
+       BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+       bp++;
+
+       return (unsigned int)(bp - bpf);
+}
+
+#ifdef ARP
+static const struct bpf_insn arp_bpf_filter [] = {
+       /* Ensure packet is at least correct size. */
+       BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),
+       BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K,
+                BPF_L2L + sizeof(struct arphdr)
+                + (ETHER_ADDR_LEN * 2)
+                + (sizeof(in_addr_t) * 2), 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+#if BPF_L2L > 0
+       /* Make sure this is an ARP packet. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                offsetof(struct ether_header, ether_type)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+#endif
+       /* Make sure this is for IP. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                BPF_L2L + offsetof(struct arphdr, ar_pro)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure this is an ARP REQUEST. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                BPF_L2L + offsetof(struct arphdr, ar_op)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),
+       /* or ARP REPLY. */
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 1),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure the hardware length matches. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+                BPF_L2L + offsetof(struct arphdr, ar_hln)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHER_ADDR_LEN, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure the protocol length matches. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+                BPF_L2L + offsetof(struct arphdr, ar_pln)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(in_addr_t), 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Pass back the whole packet. */
+       BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
+};
+#define arp_bpf_filter_len     __arraycount(arp_bpf_filter)
+#define arp_bpf_extra          ((ARP_ADDRS_MAX * 2) * 2) + 2
+
+int
+bpf_arp(struct interface *ifp, int s)
+{
+       size_t bpf_hw = ((((size_t)ifp->hwlen / 4) + 2) * 2) + 1;
+       struct bpf_insn bpf[arp_bpf_filter_len + bpf_hw + arp_bpf_extra];
+       struct bpf_insn *bp;
+       struct iarp_state *state;
+
+       if (s == -1)
+               return 0;
+       memcpy(bpf, arp_bpf_filter, sizeof(arp_bpf_filter));
+       bp = &bpf[arp_bpf_filter_len];
+
+       /* Ensure it's not from us. */
+       bp--;
+       bp += bpf_cmp_hwaddr(bp, bpf_hw, sizeof(struct arphdr),
+                            false, ifp->hwaddr, ifp->hwlen);
+
+       state = ARP_STATE(ifp);
+       if (TAILQ_FIRST(&state->arp_states)) {
+               struct arp_state *astate;
+               size_t naddrs;
+
+               /* Match sender protocol address */
+               BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_ABS,
+                            BPF_L2L + sizeof(struct arphdr) + ifp->hwlen);
+               bp++;
+               naddrs = 0;
+               TAILQ_FOREACH(astate, &state->arp_states, next) {
+                       if (++naddrs > ARP_ADDRS_MAX) {
+                               errno = ENOBUFS;
+                               logger(ifp->ctx, LOG_ERR, "%s: %m", __func__);
+                               break;
+                       }
+                       BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                                    htonl(astate->addr.s_addr), 0, 1);
+                       bp++;
+                       BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
+                       bp++;
+               }
+
+               /* Match target protocol address */
+               BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_ABS,
+                            BPF_L2L + sizeof(struct arphdr) + ifp->hwlen);
+               bp++;
+               naddrs = 0;
+               TAILQ_FOREACH(astate, &state->arp_states, next) {
+                       if (++naddrs > ARP_ADDRS_MAX) {
+                               /* Already logged error above. */
+                               break;
+                       }
+                       BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                                    htonl(astate->addr.s_addr), 0, 1);
+                       bp++;
+                       BPF_SET_STMT(bp, BPF_RET + BPF_K,
+                                    BPF_WHOLEPACKET);
+                       bp++;
+               }
+
+               /* Return nothing, no protocol address match. */
+               BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+               bp++;
+       }
+
+       /* Replace ETHER_ADDR_LEN for Infiniband if needed. */
+       if (ifp->hwlen != ETHER_ADDR_LEN) {
+               bpf[1].k += (uint32_t)(ifp->hwlen - ETHER_ADDR_LEN) * 2;
+               bpf[BPF_L2I + 11].k = ifp->hwlen;
+       }
+
+       return if_bpf_attach(s, bpf, (unsigned int)(bp - bpf));
+}
+#endif
+
+static const struct bpf_insn bootp_bpf_filter[] = {
+       /* Ensure packet is at least correct size. */
+       BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),
+       BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K,
+                BPF_L2L + sizeof(struct ip) + sizeof(struct udphdr)
+                + offsetof(struct bootp, vend), 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+#if BPF_L2L
+       /* Make sure this is an IP packet. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                offsetof(struct ether_header, ether_type)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+#endif
+       /* Make sure it's a UDP packet. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+                BPF_L2L + offsetof(struct bootp_pkt, ip.ip_p)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure this isn't a fragment. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                BPF_L2L + offsetof(struct bootp_pkt, ip.ip_off)),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure it's to the right port. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+                BPF_L2L + offsetof(struct bootp_pkt, udp.uh_dport)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTPC, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Make sure it's BOOTREPLY. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+                BPF_L2L + offsetof(struct bootp_pkt, bootp.op)),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0),
+       BPF_STMT(BPF_RET + BPF_K, 0),
+       /* Pass back the whole packet. */
+       BPF_STMT(BPF_RET + BPF_K, BPF_WHOLEPACKET),
+};
+#define bootp_bpf_filter_len   __arraycount(bootp_bpf_filter)
+#define        bootp_bpf_extra         3 + ((BOOTP_CHADDR_LEN / 4) * 3)
+
+int
+bpf_bootp(struct interface *ifp, int fd)
+{
+       const struct dhcp_state *state = D_CSTATE(ifp);
+       struct bpf_insn bpf[bootp_bpf_filter_len + bootp_bpf_extra];
+       struct bpf_insn *bp;
+       unsigned int bpf_len = bootp_bpf_extra;
+
+       if (fd == -1)
+               return 0;
+
+       memcpy(bpf, bootp_bpf_filter, sizeof(bootp_bpf_filter));
+       bp = &bpf[bootp_bpf_filter_len];
+
+       if (state->state != DHS_BOUND ||
+           ifp->hwlen <= sizeof(((struct bootp *)0)->chaddr))
+               bp--;
+
+       if (state->state != DHS_BOUND) {
+               /* Make sure the BOOTP packet is for us. */
+               BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_ABS,
+                            BPF_L2L + offsetof(struct bootp_pkt, bootp.xid));
+               bp++;
+               BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K,
+                            state->xid, 1, 0);
+               bp++;
+               BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
+               bp++;
+               bpf_len -= 3;
+       }
+
+       if (ifp->hwlen <= sizeof(((struct bootp *)0)->chaddr))
+               bp += bpf_cmp_hwaddr(bp, bpf_len,
+                               offsetof(struct bootp_pkt, bootp.chaddr),
+                               true, ifp->hwaddr, ifp->hwlen);
+
+       if (state->state != DHS_BOUND ||
+           ifp->hwlen <= sizeof(((struct bootp *)0)->chaddr))
+       {
+               BPF_SET_STMT(bp, BPF_RET + BPF_K,
+                            BPF_WHOLEPACKET);
+               bp++;
+       }
+
+       return if_bpf_attach(fd, bpf, (unsigned int)(bp - bpf));
+}
index 59b795937135f4a95d3397b8e3889e014f032f23..236bf281a3113232b19962c980a28950daeb468b 100755 (executable)
--- a/configure
+++ b/configure
@@ -457,7 +457,7 @@ esac
 if [ -z "$INET" -o "$INET" = yes ]; then
        echo "Enabling INET support"
        echo "CPPFLAGS+=        -DINET" >>$CONFIG_MK
-       echo "DHCPCD_SRCS+=     dhcp.c ipv4.c" >>$CONFIG_MK
+       echo "DHCPCD_SRCS+=     dhcp.c ipv4.c bpf.c" >>$CONFIG_MK
        if [ -z "$ARP" -o "$ARP" = yes ]; then
                echo "Enabling ARP support"
                echo "CPPFLAGS+=        -DARP" >>$CONFIG_MK
diff --git a/dhcp.c b/dhcp.c
index f2ee50442c4152fca9bb46a76dcfdaa9ffff0aec..324bf84a7deaa06b794967071f3915f2cb50c87d 100644 (file)
--- a/dhcp.c
+++ b/dhcp.c
@@ -116,13 +116,6 @@ static const char * const dhcp_params[] = {
        NULL
 };
 
-struct udp_bootp_packet
-{
-       struct ip ip;
-       struct udphdr udp;
-       uint8_t bootp[];
-};
-
 static int dhcp_open(struct interface *);
 #ifdef ARP
 static void dhcp_arp_conflicted(struct arp_state *, const struct arp_msg *);
@@ -1528,20 +1521,24 @@ dhcp_fallback(void *arg)
        dhcpcd_startinterface(iface);
 }
 
-static uint32_t
-dhcp_xid(const struct interface *ifp)
+static void
+dhcp_new_xid(struct interface *ifp)
 {
-       uint32_t xid;
+       struct dhcp_state *state;
 
+       state = D_STATE(ifp);
        if (ifp->options->options & DHCPCD_XID_HWADDR &&
-           ifp->hwlen >= sizeof(xid))
+           ifp->hwlen >= sizeof(state->xid))
                /* The lower bits are probably more unique on the network */
-               memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid),
-                   sizeof(xid));
+               memcpy(&state->xid,
+                   (ifp->hwaddr + ifp->hwlen) - sizeof(state->xid),
+                   sizeof(state->xid));
        else
-               xid = arc4random();
+               state->xid = arc4random();
 
-       return xid;
+       /* As the XID changes, re-apply the filter. */
+       if (state->raw_fd != -1)
+               bpf_bootp(ifp, state->raw_fd);
 }
 
 void
@@ -1626,11 +1623,11 @@ checksum(const void *data, size_t len)
        return (uint16_t)~htons((uint16_t)sum);
 }
 
-static struct udp_bootp_packet *
+static struct bootp_pkt *
 dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length,
        struct in_addr source, struct in_addr dest)
 {
-       struct udp_bootp_packet *udpp;
+       struct bootp_pkt *udpp;
        struct ip *ip;
        struct udphdr *udp;
 
@@ -1681,7 +1678,7 @@ send_message(struct interface *ifp, uint8_t type,
        struct dhcp_state *state = D_STATE(ifp);
        struct if_options *ifo = ifp->options;
        struct bootp *bootp;
-       struct udp_bootp_packet *udp;
+       struct bootp_pkt *udp;
        size_t len;
        ssize_t r;
        struct in_addr from, to;
@@ -1862,7 +1859,7 @@ dhcp_discover(void *arg)
        struct if_options *ifo = ifp->options;
 
        state->state = DHS_DISCOVER;
-       state->xid = dhcp_xid(ifp);
+       dhcp_new_xid(ifp);
        eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
        if (ifo->fallback)
                eloop_timeout_add_sec(ifp->ctx->eloop,
@@ -1980,7 +1977,7 @@ dhcp_startrenew(void *arg)
        logger(ifp->ctx, LOG_DEBUG, "%s: renewing lease of %s",
            ifp->name, inet_ntoa(lease->addr));
        state->state = DHS_RENEW;
-       state->xid = dhcp_xid(ifp);
+       dhcp_new_xid(ifp);
        state->interval = 0;
        send_renew(ifp);
 }
@@ -2291,6 +2288,8 @@ dhcp_bind(struct interface *ifp)
                    ifp->name, lease->renewaltime, lease->rebindtime);
        }
        state->state = DHS_BOUND;
+       /* Re-apply the filter because we need to accept any XID anymore. */
+       bpf_bootp(ifp, state->raw_fd);
        if (!state->lease.frominfo &&
            !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))
                if (write_lease(ifp, state->new, state->new_len) == -1)
@@ -2503,7 +2502,7 @@ dhcp_inform(struct interface *ifp)
        state->offer_len = dhcp_message_new(&state->offer,
            &ia->addr, &ia->mask);
        if (state->offer_len) {
-               state->xid = dhcp_xid(ifp);
+               dhcp_new_xid(ifp);
                get_lease(ifp, &state->lease, state->offer, state->offer_len);
                send_inform(ifp);
        }
@@ -2564,7 +2563,7 @@ dhcp_reboot(struct interface *ifp)
 
        logger(ifp->ctx, LOG_INFO, "%s: rebinding lease of %s",
            ifp->name, inet_ntoa(state->lease.addr));
-       state->xid = dhcp_xid(ifp);
+       dhcp_new_xid(ifp);
        state->lease.server.s_addr = 0;
        eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
 
@@ -2622,7 +2621,7 @@ dhcp_drop(struct interface *ifp, const char *reason)
                {
                        logger(ifp->ctx, LOG_INFO, "%s: releasing lease of %s",
                            ifp->name, inet_ntoa(state->lease.addr));
-                       state->xid = dhcp_xid(ifp);
+                       dhcp_new_xid(ifp);
                        send_message(ifp, DHCP_RELEASE, NULL);
 #ifdef RELEASE_SLOW
                        /* Give the packet a chance to go */
@@ -2775,13 +2774,14 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
 #define LOGDHCP(l, m) \
        log_dhcp((l), (m), ifp, bootp, bootp_len, from, 1)
 
+       /* Handled in our BPF filter. */
+#if 0
        if (bootp->op != BOOTREPLY) {
                logger(ifp->ctx, LOG_DEBUG, "%s: op (%d) is not BOOTREPLY",
                    ifp->name, bootp->op);
                return;
        }
 
-       /* Ensure packet is for us */
        if (ifp->hwlen <= sizeof(bootp->chaddr) &&
            memcmp(bootp->chaddr, ifp->hwaddr, ifp->hwlen))
        {
@@ -2793,6 +2793,7 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
                    buf, sizeof(buf)));
                return;
        }
+#endif
 
        /* We may have found a BOOTP server */
        if (get_option_uint8(ifp->ctx, &type,
@@ -2875,6 +2876,8 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
                return;
        }
 
+       /* Handled in our BPF filter. */
+#if 0
        /* Ensure it's the right transaction */
        if (state->xid != ntohl(bootp->xid)) {
                logger(ifp->ctx, LOG_DEBUG,
@@ -2883,6 +2886,7 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
                    inet_ntoa(*from));
                return;
        }
+#endif
 
        if (state->state == DHS_PROBE) {
                /* Ignore any DHCP messages whilst probing a lease to bind. */
@@ -3149,18 +3153,18 @@ rapidcommit:
 static void *
 get_udp_data(void *udp, size_t *len)
 {
-       struct udp_bootp_packet *p;
+       struct bootp_pkt *p;
 
-       p = (struct udp_bootp_packet *)udp;
+       p = (struct bootp_pkt *)udp;
        *len = ntohs(p->ip.ip_len) - sizeof(p->ip) - sizeof(p->udp);
-       return (char *)udp + offsetof(struct udp_bootp_packet, bootp);
+       return (char *)udp + offsetof(struct bootp_pkt, bootp);
 }
 
 static int
 valid_udp_packet(void *data, size_t data_len, struct in_addr *from,
     int noudpcsum)
 {
-       struct udp_bootp_packet *p;
+       struct bootp_pkt *p;
        uint16_t bytes;
 
        if (data_len < sizeof(p->ip) + sizeof(p->udp)) {
@@ -3169,7 +3173,7 @@ valid_udp_packet(void *data, size_t data_len, struct in_addr *from,
                errno = EINVAL;
                return -1;
        }
-       p = (struct udp_bootp_packet *)data;
+       p = (struct bootp_pkt *)data;
        if (from)
                from->s_addr = p->ip.ip_src.s_addr;
        if (checksum(&p->ip, sizeof(p->ip)) != 0) {
@@ -3314,6 +3318,7 @@ dhcp_handleudp(void *arg)
        }
 }
 
+
 static int
 dhcp_open(struct interface *ifp)
 {
@@ -3321,7 +3326,7 @@ dhcp_open(struct interface *ifp)
 
        state = D_STATE(ifp);
        if (state->raw_fd == -1) {
-               state->raw_fd = if_openraw(ifp, ETHERTYPE_IP);
+               state->raw_fd = if_openraw(ifp, ETHERTYPE_IP, bpf_bootp);
                if (state->raw_fd == -1) {
                        if (errno == ENOENT) {
                                logger(ifp->ctx, LOG_ERR,
@@ -3816,7 +3821,7 @@ dhcp_handleifa(int cmd, struct ipv4_addr *ia)
        script_runreason(ifp, state->reason);
        if (ifo->options & DHCPCD_INFORM) {
                state->state = DHS_INFORM;
-               state->xid = dhcp_xid(ifp);
+               dhcp_new_xid(ifp);
                state->lease.server.s_addr = INADDR_ANY;
                state->addr = ia;
                dhcp_inform(ifp);
diff --git a/dhcp.h b/dhcp.h
index a0836d2dd5511b417e1f029188dd0c908d721557..b64e6454a1e5c16c7c6e3d305aa2870095bed6f8 100644 (file)
--- a/dhcp.h
+++ b/dhcp.h
 #include <arpa/inet.h>
 #include <netinet/in.h>
 
+#include <netinet/ip.h>
+#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */
+#include <netinet/udp.h>
+#undef __FAVOR_BSD
+
 #include <limits.h>
 #include <stdint.h>
 
@@ -158,6 +163,13 @@ struct bootp {
        /* DHCP allows a variable length vendor area */
 };
 
+struct bootp_pkt
+{
+       struct ip ip;
+       struct udphdr udp;
+       struct bootp bootp;
+};
+
 struct dhcp_lease {
        struct in_addr addr;
        struct in_addr mask;
index 44e09d9e65d2f0323835fb17d11cce794ceb9817..b3dec083d24185112af9898f72776f6a43145f93 100644 (file)
--- a/if-bsd.c
+++ b/if-bsd.c
@@ -86,8 +86,6 @@
 #include "route.h"
 #include "sa.h"
 
-#include "bpf-filter.h"
-
 #ifndef RT_ROUNDUP
 #define RT_ROUNDUP(a)                                                        \
        ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
@@ -621,7 +619,8 @@ if_closeraw(__unused struct interface *ifp, int fd)
 }
 
 int
-if_openraw(struct interface *ifp, uint16_t protocol)
+if_openraw(struct interface *ifp, __unused uint16_t protocol,
+    int (*filter)(struct interface *, int))
 {
        struct ipv4_state *state;
        int fd = -1;
@@ -629,7 +628,6 @@ if_openraw(struct interface *ifp, uint16_t protocol)
        int ibuf_len = 0;
        size_t buf_len;
        struct bpf_version pv;
-       struct bpf_program pf;
 #ifdef BIOCIMMEDIATE
        int flags;
 #endif
@@ -677,6 +675,9 @@ if_openraw(struct interface *ifp, uint16_t protocol)
                goto eexit;
        }
 
+       if (filter(ifp, fd) != 0)
+               goto eexit;
+
        memset(&ifr, 0, sizeof(ifr));
        strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
        if (ioctl(fd, BIOCSETIF, &ifr) == -1)
@@ -701,18 +702,6 @@ if_openraw(struct interface *ifp, uint16_t protocol)
                goto eexit;
 #endif
 
-       /* Install the filter. */
-       memset(&pf, 0, sizeof(pf));
-       if (protocol == ETHERTYPE_ARP) {
-               pf.bf_insns = UNCONST(arp_bpf_filter);
-               pf.bf_len = arp_bpf_filter_len;
-       } else {
-               pf.bf_insns = UNCONST(bootp_bpf_filter);
-               pf.bf_len = bootp_bpf_filter_len;
-       }
-       if (ioctl(fd, BIOCSETF, &pf) == -1)
-               goto eexit;
-
        return fd;
 
 eexit:
@@ -785,6 +774,18 @@ next:
        }
 }
 
+int
+if_bpf_attach(int s, struct bpf_insn *filter, unsigned int filter_len)
+{
+       struct bpf_program pf;
+
+       /* Install the filter. */
+       memset(&pf, 0, sizeof(pf));
+       pf.bf_insns = filter;
+       pf.bf_len = filter_len;
+       return ioctl(s, BIOCSETF, &pf);
+}
+
 int
 if_address(unsigned char cmd, const struct ipv4_addr *ia)
 {
index 26f4d3ca0bd806a30cafa606e8ad155b475831cc..51006f013368d54e80447c18bf2c7c3fda115519 100644 (file)
@@ -93,8 +93,6 @@ int if_getssid_wext(const char *ifname, uint8_t *ssid);
 #define BPF_ETHCOOK            -ETH_HLEN
 #define BPF_WHOLEPACKET        0x0fffffff /* work around buggy LPF filters */
 
-#include "bpf-filter.h"
-
 struct priv {
        int route_fd;
        uint32_t route_pid;
@@ -1298,7 +1296,8 @@ if_closeraw(__unused struct interface *ifp, int fd)
 }
 
 int
-if_openraw(struct interface *ifp, uint16_t protocol)
+if_openraw(struct interface *ifp, uint16_t protocol,
+    int (*filter)(struct interface *, int))
 {
        int s;
        union sockunion {
@@ -1306,7 +1305,6 @@ if_openraw(struct interface *ifp, uint16_t protocol)
                struct sockaddr_ll sll;
                struct sockaddr_storage ss;
        } su;
-       struct sock_fprog pf;
 #ifdef PACKET_AUXDATA
        int n;
 #endif
@@ -1316,17 +1314,9 @@ if_openraw(struct interface *ifp, uint16_t protocol)
                return -1;
 #undef SF
 
-       /* Install the filter. */
-       memset(&pf, 0, sizeof(pf));
-       if (protocol == ETHERTYPE_ARP) {
-               pf.filter = UNCONST(arp_bpf_filter);
-               pf.len = arp_bpf_filter_len;
-       } else {
-               pf.filter = UNCONST(bootp_bpf_filter);
-               pf.len = bootp_bpf_filter_len;
-       }
-       if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) != 0)
+       if (filter(ifp, s) != 0)
                goto eexit;
+
 #ifdef PACKET_AUXDATA
        n = 1;
        if (setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &n, sizeof(n)) != 0) {
@@ -1425,6 +1415,18 @@ if_readraw(__unused struct interface *ifp, int fd,
        return bytes;
 }
 
+int
+if_bpf_attach(int s, struct bpf_insn *filter, unsigned int filter_len)
+{
+       struct sock_fprog pf;
+
+       /* Install the filter. */
+       memset(&pf, 0, sizeof(pf));
+       pf.filter = filter;
+       pf.len = (unsigned short)filter_len;
+       return setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf));
+}
+
 int
 if_address(unsigned char cmd, const struct ipv4_addr *addr)
 {
diff --git a/if.h b/if.h
index 4e13ef8feb370577cf66fe0b452d723fc4278402..ed536a4058b273d4e8662063d74892845307cfa9 100644 (file)
--- a/if.h
+++ b/if.h
@@ -171,12 +171,19 @@ int if_handlelink(struct dhcpcd_ctx *);
 #endif
 
 #ifdef INET
+#ifdef __linux__
+#define        bpf_insn        sock_filter
+#endif
+struct bpf_insn;
 extern const char *if_pfname;
-int if_openraw(struct interface *, uint16_t);
+int if_openraw(struct interface *, uint16_t, int (*)(struct interface *, int));
 ssize_t if_sendraw(const struct interface *, int, uint16_t,
     const void *, size_t);
 ssize_t if_readraw(struct interface *, int, void *, size_t, int *);
 void if_closeraw(struct interface *, int);
+int if_bpf_attach(int, struct bpf_insn *, unsigned int);
+int bpf_arp(struct interface *, int);
+int bpf_bootp(struct interface *, int);
 
 int if_address(unsigned char, const struct ipv4_addr *);
 int if_addrflags(const struct interface *, const struct in_addr *,