From: Roy Marples Date: Sun, 5 Mar 2017 21:05:24 +0000 (+0000) Subject: bpf: ARP and BOOTP filter improvements X-Git-Tag: v7.0.0-beta1~68 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=334e3e70165eea3033f2ef6ffaf60997bef2c18a;p=thirdparty%2Fdhcpcd.git bpf: ARP and BOOTP filter improvements 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. --- diff --git a/arp.c b/arp.c index 7b8ebbd5..aaff37ec 100644 --- 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 index 8cb696b1..00000000 --- a/bpf-filter.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2017 Roy Marples - * - * 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 index 00000000..6f898fc2 --- /dev/null +++ b/bpf.c @@ -0,0 +1,362 @@ +/* + * dhcpcd: BPF arp and bootp functions + * Copyright (c) 2006-2017 Roy Marples + * 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 + +#include +#include +#include + +#ifdef __linux__ +#include +#else +#include +#endif + +#include +#include +#include + +#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)); +} diff --git a/configure b/configure index 59b79593..236bf281 100755 --- 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 f2ee5044..324bf84a 100644 --- 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 a0836d2d..b64e6454 100644 --- a/dhcp.h +++ b/dhcp.h @@ -31,6 +31,11 @@ #include #include +#include +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include +#undef __FAVOR_BSD + #include #include @@ -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; diff --git a/if-bsd.c b/if-bsd.c index 44e09d9e..b3dec083 100644 --- 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) { diff --git a/if-linux.c b/if-linux.c index 26f4d3ca..51006f01 100644 --- a/if-linux.c +++ b/if-linux.c @@ -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 4e13ef8f..ed536a40 100644 --- 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 *,