Changeset View
Changeset View
Standalone View
Standalone View
contrib/dhcpcd/src/bpf.c
- This file was added.
/* SPDX-License-Identifier: BSD-2-Clause */ | |||||
/* | |||||
* dhcpcd: BPF arp and bootp filtering | |||||
* Copyright (c) 2006-2021 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 <sys/ioctl.h> | |||||
#include <sys/socket.h> | |||||
#include <arpa/inet.h> | |||||
#include <net/if.h> | |||||
#include <netinet/in.h> | |||||
#include <netinet/if_ether.h> | |||||
#ifdef __linux__ | |||||
/* Special BPF snowflake. */ | |||||
#include <linux/filter.h> | |||||
#define bpf_insn sock_filter | |||||
#else | |||||
#include <net/bpf.h> | |||||
#endif | |||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <paths.h> | |||||
#include <stddef.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "common.h" | |||||
#include "arp.h" | |||||
#include "bpf.h" | |||||
#include "dhcp.h" | |||||
#include "if.h" | |||||
#include "logerr.h" | |||||
/* BPF helper macros */ | |||||
#ifdef __linux__ | |||||
#define BPF_WHOLEPACKET 0x7fffffff /* work around buggy LPF filters */ | |||||
#else | |||||
#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); \ | |||||
} | |||||
size_t | |||||
bpf_frame_header_len(const struct interface *ifp) | |||||
{ | |||||
switch (ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
return sizeof(struct ether_header); | |||||
default: | |||||
return 0; | |||||
} | |||||
} | |||||
void * | |||||
bpf_frame_header_src(const struct interface *ifp, void *fh, size_t *len) | |||||
{ | |||||
uint8_t *f = fh; | |||||
switch (ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
*len = sizeof(((struct ether_header *)0)->ether_shost); | |||||
return f + offsetof(struct ether_header, ether_shost); | |||||
default: | |||||
*len = 0; | |||||
errno = ENOTSUP; | |||||
return NULL; | |||||
} | |||||
} | |||||
void * | |||||
bpf_frame_header_dst(const struct interface *ifp, void *fh, size_t *len) | |||||
{ | |||||
uint8_t *f = fh; | |||||
switch (ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
*len = sizeof(((struct ether_header *)0)->ether_dhost); | |||||
return f + offsetof(struct ether_header, ether_dhost); | |||||
default: | |||||
*len = 0; | |||||
errno = ENOTSUP; | |||||
return NULL; | |||||
} | |||||
} | |||||
static const uint8_t etherbcastaddr[] = | |||||
{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; | |||||
int | |||||
bpf_frame_bcast(const struct interface *ifp, const void *frame) | |||||
{ | |||||
switch (ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
return memcmp((const char *)frame + | |||||
offsetof(struct ether_header, ether_dhost), | |||||
etherbcastaddr, sizeof(etherbcastaddr)); | |||||
default: | |||||
return -1; | |||||
} | |||||
} | |||||
#ifndef __linux__ | |||||
/* Linux is a special snowflake for opening, attaching and reading BPF. | |||||
* See if-linux.c for the Linux specific BPF functions. */ | |||||
const char *bpf_name = "Berkley Packet Filter"; | |||||
struct bpf * | |||||
bpf_open(const struct interface *ifp, | |||||
int (*filter)(const struct bpf *, const struct in_addr *), | |||||
const struct in_addr *ia) | |||||
{ | |||||
struct bpf *bpf; | |||||
struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 }; | |||||
struct ifreq ifr = { .ifr_flags = 0 }; | |||||
int ibuf_len = 0; | |||||
#ifdef BIOCIMMEDIATE | |||||
unsigned int flags; | |||||
#endif | |||||
#ifndef O_CLOEXEC | |||||
int fd_opts; | |||||
#endif | |||||
bpf = calloc(1, sizeof(*bpf)); | |||||
if (bpf == NULL) | |||||
return NULL; | |||||
bpf->bpf_ifp = ifp; | |||||
#ifdef _PATH_BPF | |||||
bpf->bpf_fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK | |||||
#ifdef O_CLOEXEC | |||||
| O_CLOEXEC | |||||
#endif | |||||
); | |||||
#else | |||||
char device[32]; | |||||
int n = 0; | |||||
do { | |||||
snprintf(device, sizeof(device), "/dev/bpf%d", n++); | |||||
bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK | |||||
#ifdef O_CLOEXEC | |||||
| O_CLOEXEC | |||||
#endif | |||||
); | |||||
} while (bpf->bpf_fd == -1 && errno == EBUSY); | |||||
#endif | |||||
if (bpf->bpf_fd == -1) | |||||
goto eexit; | |||||
#ifndef O_CLOEXEC | |||||
if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 || | |||||
fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) | |||||
goto eexit; | |||||
#endif | |||||
if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1) | |||||
goto eexit; | |||||
if (pv.bv_major != BPF_MAJOR_VERSION || | |||||
pv.bv_minor < BPF_MINOR_VERSION) { | |||||
logerrx("BPF version mismatch - recompile"); | |||||
goto eexit; | |||||
} | |||||
strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); | |||||
if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1) | |||||
goto eexit; | |||||
#ifdef BIOCIMMEDIATE | |||||
flags = 1; | |||||
if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1) | |||||
goto eexit; | |||||
#endif | |||||
if (filter(bpf, ia) != 0) | |||||
goto eexit; | |||||
/* Get the required BPF buffer length from the kernel. */ | |||||
if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1) | |||||
goto eexit; | |||||
bpf->bpf_size = (size_t)ibuf_len; | |||||
bpf->bpf_buffer = malloc(bpf->bpf_size); | |||||
if (bpf->bpf_buffer == NULL) | |||||
goto eexit; | |||||
return bpf; | |||||
eexit: | |||||
if (bpf->bpf_fd != -1) | |||||
close(bpf->bpf_fd); | |||||
free(bpf); | |||||
return NULL; | |||||
} | |||||
/* BPF requires that we read the entire buffer. | |||||
* So we pass the buffer in the API so we can loop on >1 packet. */ | |||||
ssize_t | |||||
bpf_read(struct bpf *bpf, void *data, size_t len) | |||||
{ | |||||
ssize_t bytes; | |||||
struct bpf_hdr packet; | |||||
const char *payload; | |||||
bpf->bpf_flags &= ~BPF_EOF; | |||||
for (;;) { | |||||
if (bpf->bpf_len == 0) { | |||||
bytes = read(bpf->bpf_fd, bpf->bpf_buffer, | |||||
bpf->bpf_size); | |||||
#if defined(__sun) | |||||
/* After 2^31 bytes, the kernel offset overflows. | |||||
* To work around this bug, lseek 0. */ | |||||
if (bytes == -1 && errno == EINVAL) { | |||||
lseek(bpf->bpf_fd, 0, SEEK_SET); | |||||
continue; | |||||
} | |||||
#endif | |||||
if (bytes == -1 || bytes == 0) | |||||
return bytes; | |||||
bpf->bpf_len = (size_t)bytes; | |||||
bpf->bpf_pos = 0; | |||||
} | |||||
bytes = -1; | |||||
payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos; | |||||
memcpy(&packet, payload, sizeof(packet)); | |||||
if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen > | |||||
bpf->bpf_len) | |||||
goto next; /* Packet beyond buffer, drop. */ | |||||
payload += packet.bh_hdrlen; | |||||
if (packet.bh_caplen > len) | |||||
bytes = (ssize_t)len; | |||||
else | |||||
bytes = (ssize_t)packet.bh_caplen; | |||||
if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0) | |||||
bpf->bpf_flags |= BPF_BCAST; | |||||
else | |||||
bpf->bpf_flags &= ~BPF_BCAST; | |||||
memcpy(data, payload, (size_t)bytes); | |||||
next: | |||||
bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen + | |||||
packet.bh_caplen); | |||||
if (bpf->bpf_pos >= bpf->bpf_len) { | |||||
bpf->bpf_len = bpf->bpf_pos = 0; | |||||
bpf->bpf_flags |= BPF_EOF; | |||||
} | |||||
if (bytes != -1) | |||||
return bytes; | |||||
} | |||||
/* NOTREACHED */ | |||||
} | |||||
int | |||||
bpf_attach(int fd, void *filter, unsigned int filter_len) | |||||
{ | |||||
struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; | |||||
/* Install the filter. */ | |||||
return ioctl(fd, BIOCSETF, &pf); | |||||
} | |||||
#ifdef BIOCSETWF | |||||
static int | |||||
bpf_wattach(int fd, void *filter, unsigned int filter_len) | |||||
{ | |||||
struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; | |||||
/* Install the filter. */ | |||||
return ioctl(fd, BIOCSETWF, &pf); | |||||
} | |||||
#endif | |||||
#endif | |||||
#ifndef __sun | |||||
/* SunOS is special too - sending via BPF goes nowhere. */ | |||||
ssize_t | |||||
bpf_send(const struct bpf *bpf, uint16_t protocol, | |||||
const void *data, size_t len) | |||||
{ | |||||
struct iovec iov[2]; | |||||
struct ether_header eh; | |||||
switch(bpf->bpf_ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); | |||||
memcpy(&eh.ether_shost, bpf->bpf_ifp->hwaddr, | |||||
sizeof(eh.ether_shost)); | |||||
eh.ether_type = htons(protocol); | |||||
iov[0].iov_base = &eh; | |||||
iov[0].iov_len = sizeof(eh); | |||||
break; | |||||
default: | |||||
iov[0].iov_base = NULL; | |||||
iov[0].iov_len = 0; | |||||
break; | |||||
} | |||||
iov[1].iov_base = UNCONST(data); | |||||
iov[1].iov_len = len; | |||||
return writev(bpf->bpf_fd, iov, 2); | |||||
} | |||||
#endif | |||||
void | |||||
bpf_close(struct bpf *bpf) | |||||
{ | |||||
close(bpf->bpf_fd); | |||||
free(bpf->bpf_buffer); | |||||
free(bpf); | |||||
} | |||||
#ifdef ARP | |||||
#define BPF_CMP_HWADDR_LEN ((((HWADDR_LEN / 4) + 2) * 2) + 1) | |||||
static unsigned int | |||||
bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off, | |||||
bool equal, const 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++; | |||||
if (equal) { | |||||
jt = (uint8_t)njmps; | |||||
jf = 0; | |||||
} else { | |||||
jt = 0; | |||||
jf = (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_IND, 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_IND, 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_IND, 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); | |||||
} | |||||
#endif | |||||
#ifdef ARP | |||||
static const struct bpf_insn bpf_arp_ether [] = { | |||||
/* Check 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), | |||||
/* Load frame header length into X */ | |||||
BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)), | |||||
/* Make sure the hardware type matches. */ | |||||
BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_hrd)), | |||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
/* Make sure the hardware length matches. */ | |||||
BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_hln)), | |||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, | |||||
sizeof(((struct ether_arp *)0)->arp_sha), 1, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
}; | |||||
#define BPF_ARP_ETHER_LEN __arraycount(bpf_arp_ether) | |||||
static const struct bpf_insn bpf_arp_filter [] = { | |||||
/* Make sure this is for IP. */ | |||||
BPF_STMT(BPF_LD + BPF_H + BPF_IND, 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_IND, 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, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
/* Make sure the protocol length matches. */ | |||||
BPF_STMT(BPF_LD + BPF_B + BPF_IND, 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), | |||||
}; | |||||
#define BPF_ARP_FILTER_LEN __arraycount(bpf_arp_filter) | |||||
/* One address is two checks of two statements. */ | |||||
#define BPF_NADDRS 1 | |||||
#define BPF_ARP_ADDRS_LEN 5 + ((BPF_NADDRS * 2) * 2) | |||||
#define BPF_ARP_LEN BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \ | |||||
BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN | |||||
static int | |||||
bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv) | |||||
{ | |||||
const struct interface *ifp = bpf->bpf_ifp; | |||||
struct bpf_insn buf[BPF_ARP_LEN + 1]; | |||||
struct bpf_insn *bp; | |||||
uint16_t arp_len; | |||||
bp = buf; | |||||
/* Check frame header. */ | |||||
switch(ifp->hwtype) { | |||||
case ARPHRD_ETHER: | |||||
memcpy(bp, bpf_arp_ether, sizeof(bpf_arp_ether)); | |||||
bp += BPF_ARP_ETHER_LEN; | |||||
arp_len = sizeof(struct ether_header)+sizeof(struct ether_arp); | |||||
break; | |||||
default: | |||||
errno = EINVAL; | |||||
return -1; | |||||
} | |||||
/* Copy in the main filter. */ | |||||
memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter)); | |||||
bp += BPF_ARP_FILTER_LEN; | |||||
/* Ensure it's not from us. */ | |||||
bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr), | |||||
!recv, ifp->hwaddr, ifp->hwlen); | |||||
/* Match sender protocol address */ | |||||
BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, | |||||
sizeof(struct arphdr) + ifp->hwlen); | |||||
bp++; | |||||
BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); | |||||
bp++; | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); | |||||
bp++; | |||||
/* If we didn't match sender, then we're only interested in | |||||
* ARP probes to us, so check the null host sender. */ | |||||
BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0); | |||||
bp++; | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); | |||||
bp++; | |||||
/* Match target protocol address */ | |||||
BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, (sizeof(struct arphdr) + | |||||
(size_t)(ifp->hwlen * 2) + sizeof(in_addr_t))); | |||||
bp++; | |||||
BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); | |||||
bp++; | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); | |||||
bp++; | |||||
/* No match, drop it */ | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); | |||||
bp++; | |||||
#ifdef BIOCSETWF | |||||
if (!recv) | |||||
return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); | |||||
#endif | |||||
return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); | |||||
} | |||||
int | |||||
bpf_arp(const struct bpf *bpf, const struct in_addr *ia) | |||||
{ | |||||
#ifdef BIOCSETWF | |||||
if (bpf_arp_rw(bpf, ia, true) == -1 || | |||||
bpf_arp_rw(bpf, ia, false) == -1 || | |||||
ioctl(bpf->bpf_fd, BIOCLOCK) == -1) | |||||
return -1; | |||||
return 0; | |||||
#else | |||||
return bpf_arp_rw(bpf, ia, true); | |||||
#endif | |||||
} | |||||
#endif | |||||
#ifdef ARPHRD_NONE | |||||
static const struct bpf_insn bpf_bootp_none[] = { | |||||
}; | |||||
#define BPF_BOOTP_NONE_LEN __arraycount(bpf_bootp_none) | |||||
#endif | |||||
static const struct bpf_insn bpf_bootp_ether[] = { | |||||
/* 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), | |||||
/* Advance to the IP header. */ | |||||
BPF_STMT(BPF_LDX + BPF_K, sizeof(struct ether_header)), | |||||
}; | |||||
#define BPF_BOOTP_ETHER_LEN __arraycount(bpf_bootp_ether) | |||||
static const struct bpf_insn bpf_bootp_base[] = { | |||||
/* Make sure it's an IPv4 packet. */ | |||||
BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), | |||||
BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0), | |||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x40, 1, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
/* Make sure it's a UDP packet. */ | |||||
BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct 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_IND, offsetof(struct ip, ip_off)), | |||||
BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
/* Advance to the UDP header. */ | |||||
BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), | |||||
BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x0f), | |||||
BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4), | |||||
BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), | |||||
BPF_STMT(BPF_MISC + BPF_TAX, 0), | |||||
}; | |||||
#define BPF_BOOTP_BASE_LEN __arraycount(bpf_bootp_base) | |||||
static const struct bpf_insn bpf_bootp_read[] = { | |||||
/* Make sure it's from and to the right port. */ | |||||
BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), | |||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
}; | |||||
#define BPF_BOOTP_READ_LEN __arraycount(bpf_bootp_read) | |||||
#ifdef BIOCSETWF | |||||
static const struct bpf_insn bpf_bootp_write[] = { | |||||
/* Make sure it's from and to the right port. */ | |||||
BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), | |||||
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0), | |||||
BPF_STMT(BPF_RET + BPF_K, 0), | |||||
}; | |||||
#define BPF_BOOTP_WRITE_LEN __arraycount(bpf_bootp_write) | |||||
#endif | |||||
#define BPF_BOOTP_CHADDR_LEN ((BOOTP_CHADDR_LEN / 4) * 3) | |||||
#define BPF_BOOTP_XID_LEN 4 /* BOUND check is 4 instructions */ | |||||
#define BPF_BOOTP_LEN BPF_BOOTP_ETHER_LEN + \ | |||||
BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \ | |||||
BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4 | |||||
static int | |||||
bpf_bootp_rw(const struct bpf *bpf, bool read) | |||||
{ | |||||
struct bpf_insn buf[BPF_BOOTP_LEN + 1]; | |||||
struct bpf_insn *bp; | |||||
bp = buf; | |||||
/* Check frame header. */ | |||||
switch(bpf->bpf_ifp->hwtype) { | |||||
#ifdef ARPHRD_NONE | |||||
case ARPHRD_NONE: | |||||
memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none)); | |||||
bp += BPF_BOOTP_NONE_LEN; | |||||
break; | |||||
#endif | |||||
case ARPHRD_ETHER: | |||||
memcpy(bp, bpf_bootp_ether, sizeof(bpf_bootp_ether)); | |||||
bp += BPF_BOOTP_ETHER_LEN; | |||||
break; | |||||
default: | |||||
errno = EINVAL; | |||||
return -1; | |||||
} | |||||
/* Copy in the main filter. */ | |||||
memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base)); | |||||
bp += BPF_BOOTP_BASE_LEN; | |||||
#ifdef BIOCSETWF | |||||
if (!read) { | |||||
memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write)); | |||||
bp += BPF_BOOTP_WRITE_LEN; | |||||
/* All passed, return the packet. */ | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); | |||||
bp++; | |||||
return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); | |||||
} | |||||
#else | |||||
UNUSED(read); | |||||
#endif | |||||
memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read)); | |||||
bp += BPF_BOOTP_READ_LEN; | |||||
/* All passed, return the packet. */ | |||||
BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); | |||||
bp++; | |||||
return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); | |||||
} | |||||
int | |||||
bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia) | |||||
{ | |||||
#ifdef BIOCSETWF | |||||
if (bpf_bootp_rw(bpf, true) == -1 || | |||||
bpf_bootp_rw(bpf, false) == -1 || | |||||
ioctl(bpf->bpf_fd, BIOCLOCK) == -1) | |||||
return -1; | |||||
return 0; | |||||
#else | |||||
#ifdef PRIVSEP | |||||
#if defined(__sun) /* Solaris cannot send via BPF. */ | |||||
#elif defined(BIOCSETF) | |||||
#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket | |||||
#else | |||||
#warning A compromised PF_PACKET socket can be used as a raw socket | |||||
#endif | |||||
#endif | |||||
return bpf_bootp_rw(bpf, true); | |||||
#endif | |||||
} |