2651 lines
68 KiB
C
2651 lines
68 KiB
C
/* Tests for BPF devices (LWIP) - by D.C. van Moolenbroek */
|
|
/* This test needs to be run as root: opening BPF devices is root-only. */
|
|
/*
|
|
* We do not attempt to test the BPF filter code here. Such a test is better
|
|
* done through standardized tests and with direct use of the filter code.
|
|
* The current BPF filter implementation has been run through the FreeBSD
|
|
* BPF filter regression tests (from their tools/regression/bpf/bpf_filter), of
|
|
* which only the last test (0084 - "Check very long BPF program") failed due
|
|
* to our lower and strictly enforced BPF_MAXINSNS value. Future modifications
|
|
* of the BPF filter code should be tested against at least that test set.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/wait.h>
|
|
#include <net/bpf.h>
|
|
#include <net/bpfdesc.h>
|
|
#include <net/if.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_ether.h>
|
|
#include <net/if_dl.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/udp.h>
|
|
#include <ifaddrs.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <paths.h>
|
|
#include <pwd.h>
|
|
|
|
#include "common.h"
|
|
|
|
#define ITERATIONS 2
|
|
|
|
#define LOOPBACK_IFNAME "lo0"
|
|
|
|
#define TEST_PORT_A 12345
|
|
#define TEST_PORT_B 12346
|
|
|
|
#define SLEEP_TIME 250000 /* (us) - increases may require code changes */
|
|
|
|
#define NONROOT_USER "bin" /* name of any unprivileged user */
|
|
|
|
#ifdef NO_INET6
|
|
const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
|
|
#endif /* NO_INET6 */
|
|
|
|
static unsigned int got_signal;
|
|
|
|
/*
|
|
* Signal handler.
|
|
*/
|
|
static void
|
|
test94_signal(int sig)
|
|
{
|
|
|
|
if (sig != SIGUSR1) e(0);
|
|
|
|
got_signal++;
|
|
}
|
|
|
|
/*
|
|
* Send UDP packets on the given socket 'fd' so as to fill up a BPF store
|
|
* buffer of size 'size' exactly. The provided buffer 'buf' may be used for
|
|
* packet generation and is at least of 'size' bytes. Return the number of
|
|
* packets sent.
|
|
*/
|
|
static uint32_t
|
|
test94_fill_exact(int fd, uint8_t * buf, size_t size, uint32_t seq)
|
|
{
|
|
size_t hdrlen, len;
|
|
|
|
hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)) + sizeof(struct ip) +
|
|
sizeof(struct udphdr) + sizeof(seq);
|
|
|
|
for (len = 16; len <= hdrlen; len <<= 1);
|
|
if (len > size) e(0);
|
|
|
|
hdrlen = BPF_WORDALIGN(hdrlen - sizeof(seq));
|
|
|
|
for (; size > 0; seq++) {
|
|
memset(buf, 'Y', len - hdrlen);
|
|
if (len - hdrlen > sizeof(seq))
|
|
buf[sizeof(seq)] = 'X';
|
|
buf[len - hdrlen - 1] = 'Z';
|
|
memcpy(buf, &seq, sizeof(seq));
|
|
|
|
if (write(fd, buf, len - hdrlen) != len - hdrlen) e(0);
|
|
|
|
size -= len;
|
|
}
|
|
|
|
return seq;
|
|
}
|
|
|
|
/*
|
|
* Send UDP packets on the given socket 'fd' so as to fill up at least a BPF
|
|
* store buffer of size 'size', with at least one more packet being sent. The
|
|
* provided buffer 'buf' may be used for packet generation and is at least of
|
|
* 'size' bytes.
|
|
*/
|
|
static void
|
|
test94_fill_random(int fd, uint8_t * buf, size_t size)
|
|
{
|
|
size_t hdrlen, len;
|
|
ssize_t left;
|
|
uint32_t seq;
|
|
|
|
hdrlen = BPF_WORDALIGN(BPF_WORDALIGN(sizeof(struct bpf_hdr)) +
|
|
sizeof(struct ip) + sizeof(struct udphdr));
|
|
|
|
/* Even if we fill the buffer exactly, we send one more packet. */
|
|
for (left = (ssize_t)size, seq = 1; left >= 0; seq++) {
|
|
len = hdrlen + sizeof(seq) + lrand48() % (size / 10);
|
|
|
|
memset(buf, 'Y', len - hdrlen);
|
|
if (len - hdrlen > sizeof(seq))
|
|
buf[sizeof(seq)] = 'X';
|
|
buf[len - hdrlen - 1] = 'Z';
|
|
memcpy(buf, &seq, sizeof(seq));
|
|
|
|
if (write(fd, buf, len - hdrlen) != len - hdrlen) e(0);
|
|
|
|
left -= BPF_WORDALIGN(len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send a UDP packet with a specific size of 'size' bytes and sequence number
|
|
* 'seq' on socket 'fd', using 'buf' as scratch buffer.
|
|
*/
|
|
static void
|
|
test94_add_specific(int fd, uint8_t * buf, size_t size, uint32_t seq)
|
|
{
|
|
|
|
size += sizeof(seq);
|
|
|
|
memset(buf, 'Y', size);
|
|
if (size > sizeof(seq))
|
|
buf[sizeof(seq)] = 'X';
|
|
buf[size - 1] = 'Z';
|
|
memcpy(buf, &seq, sizeof(seq));
|
|
|
|
if (write(fd, buf, size) != size) e(0);
|
|
}
|
|
|
|
/*
|
|
* Send a randomly sized, relatively small UDP packet on the given socket 'fd',
|
|
* using sequence number 'seq'. The buffer 'buf' may be used as scratch buffer
|
|
* which is at most 'size' bytes--the same size as the total BPF buffer.
|
|
*/
|
|
static void
|
|
test94_add_random(int fd, uint8_t * buf, size_t size, uint32_t seq)
|
|
{
|
|
|
|
test94_add_specific(fd, buf, lrand48() % (size / 10), seq);
|
|
}
|
|
|
|
/*
|
|
* Check whether the packet in 'buf' of 'caplen' captured bytes out of
|
|
* 'datalen' data bytes is one we sent. If so, return an offset to the packet
|
|
* data. If not, return a negative value.
|
|
*/
|
|
static ssize_t
|
|
test94_check_pkt(uint8_t * buf, ssize_t caplen, ssize_t datalen)
|
|
{
|
|
struct ip ip;
|
|
struct udphdr uh;
|
|
|
|
if (caplen < sizeof(ip))
|
|
return -1;
|
|
|
|
memcpy(&ip, buf, sizeof(ip));
|
|
|
|
if (ip.ip_v != IPVERSION)
|
|
return -1;
|
|
if (ip.ip_hl != sizeof(ip) >> 2)
|
|
return -1;
|
|
if (ip.ip_p != IPPROTO_UDP)
|
|
return -1;
|
|
|
|
if (caplen - sizeof(ip) < sizeof(uh))
|
|
return -1;
|
|
|
|
memcpy(&uh, buf + sizeof(ip), sizeof(uh));
|
|
|
|
if (uh.uh_sport != htons(TEST_PORT_A))
|
|
return -1;
|
|
if (uh.uh_dport != htons(TEST_PORT_B))
|
|
return -1;
|
|
|
|
if (datalen - sizeof(ip) != ntohs(uh.uh_ulen)) e(0);
|
|
|
|
return sizeof(ip) + sizeof(uh);
|
|
}
|
|
|
|
/*
|
|
* Check whether the capture in 'buf' of 'len' bytes looks like a valid set of
|
|
* captured packets. The valid packets start from sequence number 'seq'; the
|
|
* next expected sequence number is returned. If 'filtered' is set, there
|
|
* should be no other packets in the capture; otherwise, other packets are
|
|
* ignored.
|
|
*/
|
|
static uint32_t
|
|
test94_check(uint8_t * buf, ssize_t len, uint32_t seq, int filtered,
|
|
uint32_t * caplen, uint32_t * datalen)
|
|
{
|
|
struct bpf_hdr bh;
|
|
ssize_t off;
|
|
uint32_t nseq;
|
|
|
|
while (len > 0) {
|
|
/*
|
|
* We rely on the assumption that the last packet in the buffer
|
|
* is padded to alignment as well; if not, this check fails.
|
|
*/
|
|
if (len < BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
|
|
memcpy(&bh, buf, sizeof(bh));
|
|
|
|
/*
|
|
* The timestamp fields should be filled in. The tests that
|
|
* use this function do not set a capture length below the
|
|
* packet length. The header must be exactly as large as we
|
|
* expect: no small-size tricks (as NetBSD uses) and no
|
|
* unexpected extra padding.
|
|
*/
|
|
if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0)
|
|
e(0);
|
|
if (caplen != NULL) {
|
|
if (bh.bh_caplen != *caplen) e(0);
|
|
if (bh.bh_datalen != *datalen) e(0);
|
|
|
|
caplen++;
|
|
datalen++;
|
|
} else
|
|
if (bh.bh_datalen != bh.bh_caplen) e(0);
|
|
if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
|
|
if (bh.bh_hdrlen + BPF_WORDALIGN(bh.bh_caplen) > len) e(0);
|
|
|
|
buf += bh.bh_hdrlen;
|
|
len -= bh.bh_hdrlen;
|
|
|
|
if ((off = test94_check_pkt(buf, bh.bh_caplen,
|
|
bh.bh_datalen)) < 0) {
|
|
if (filtered) e(0);
|
|
|
|
buf += BPF_WORDALIGN(bh.bh_caplen);
|
|
len -= BPF_WORDALIGN(bh.bh_caplen);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (bh.bh_caplen < off + sizeof(seq)) e(0);
|
|
|
|
memcpy(&nseq, &buf[off], sizeof(nseq));
|
|
|
|
if (nseq != seq++) e(0);
|
|
|
|
off += sizeof(seq);
|
|
if (off < bh.bh_caplen) {
|
|
/* If there is just one byte, it is 'Z'. */
|
|
if (off < bh.bh_caplen && off < bh.bh_datalen - 1) {
|
|
if (buf[off] != 'X') e(0);
|
|
|
|
for (off++; off < bh.bh_caplen &&
|
|
off < bh.bh_datalen - 1; off++)
|
|
if (buf[off] != 'Y') e(0);
|
|
}
|
|
if (off < bh.bh_caplen && off == bh.bh_datalen - 1 &&
|
|
buf[off] != 'Z') e(0);
|
|
}
|
|
|
|
buf += BPF_WORDALIGN(bh.bh_caplen);
|
|
len -= BPF_WORDALIGN(bh.bh_caplen);
|
|
}
|
|
|
|
return seq;
|
|
}
|
|
|
|
/*
|
|
* Filter program to ensure that the given (datalink-headerless) packet is an
|
|
* IPv4 UDP packet from port 12345 to port 12346. Important: the 'k' value of
|
|
* the last instruction must be the accepted packet size, and is modified by
|
|
* some of the tests further down!
|
|
*/
|
|
static struct bpf_insn test94_filter[] = {
|
|
{ BPF_LD+BPF_B+BPF_ABS, 0, 0, 0 }, /* is this an IPv4 header? */
|
|
{ BPF_ALU+BPF_RSH+BPF_K, 0, 0, 4 },
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 7, 4 },
|
|
{ BPF_LD+BPF_B+BPF_ABS, 0, 0, 9 }, /* is this a UDP packet? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 5, IPPROTO_UDP },
|
|
{ BPF_LDX+BPF_B+BPF_MSH, 0, 0, 0 },
|
|
{ BPF_LD+BPF_H+BPF_IND, 0, 0, 0 }, /* source port 12345? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 2, TEST_PORT_A },
|
|
{ BPF_LD+BPF_H+BPF_IND, 0, 0, 2 }, /* destination port 12346? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 1, 0, TEST_PORT_B },
|
|
{ BPF_RET+BPF_K, 0, 0, 0 }, /* reject the packet */
|
|
{ BPF_RET+BPF_K, 0, 0, (uint32_t)-1 }, /* accept the (whole) packet */
|
|
};
|
|
|
|
/*
|
|
* Set up a BPF device, a pair of sockets of which traffic will be captured on
|
|
* the BPF device, a buffer for capturing packets, and optionally a filter.
|
|
* If the given size is non-zero, use that as buffer size. Return the BPF
|
|
* device's actual buffer size, which is also the size of 'buf'.
|
|
*/
|
|
static size_t
|
|
test94_setup(int * fd, int * fd2, int * fd3, uint8_t ** buf, unsigned int size,
|
|
int set_filter)
|
|
{
|
|
struct sockaddr_in sinA, sinB;
|
|
struct ifreq ifr;
|
|
struct bpf_program bf;
|
|
unsigned int dlt;
|
|
|
|
if ((*fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
if (size != 0 && ioctl(*fd, BIOCSBLEN, &size) != 0) e(0);
|
|
|
|
if (ioctl(*fd, BIOCGBLEN, &size) != 0) e(0);
|
|
if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
|
|
|
|
if ((*buf = malloc(size)) == NULL) e(0);
|
|
|
|
if (set_filter) {
|
|
/*
|
|
* Install a filter to improve predictability for the tests.
|
|
*/
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = __arraycount(test94_filter);
|
|
bf.bf_insns = test94_filter;
|
|
if (ioctl(*fd, BIOCSETF, &bf) != 0) e(0);
|
|
}
|
|
|
|
/* Bind to the loopback device. */
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
|
|
if (ioctl(*fd, BIOCSETIF, &ifr) != 0) e(0);
|
|
|
|
/*
|
|
* If the loopback device's data link type is not DLT_RAW, our filter
|
|
* and size calculations will not work.
|
|
*/
|
|
if (ioctl(*fd, BIOCGDLT, &dlt) != 0) e(0);
|
|
if (dlt != DLT_RAW) e(0);
|
|
|
|
/* We use UDP traffic for our test packets. */
|
|
if ((*fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
memset(&sinA, 0, sizeof(sinA));
|
|
sinA.sin_family = AF_INET;
|
|
sinA.sin_port = htons(TEST_PORT_A);
|
|
sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
if (bind(*fd2, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
|
|
|
|
memcpy(&sinB, &sinA, sizeof(sinB));
|
|
sinB.sin_port = htons(TEST_PORT_B);
|
|
if (connect(*fd2, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
|
|
|
|
if ((*fd3 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
if (bind(*fd3, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
|
|
|
|
if (connect(*fd3, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
|
|
|
|
return size;
|
|
}
|
|
|
|
/*
|
|
* Clean up resources allocated by test94_setup().
|
|
*/
|
|
static void
|
|
test94_cleanup(int fd, int fd2, int fd3, uint8_t * buf)
|
|
{
|
|
|
|
if (close(fd3) != 0) e(0);
|
|
|
|
if (close(fd2) != 0) e(0);
|
|
|
|
free(buf);
|
|
|
|
if (close(fd) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test reading packets from a BPF device, using regular mode.
|
|
*/
|
|
static void
|
|
test94a(void)
|
|
{
|
|
struct bpf_program bf;
|
|
struct timeval tv;
|
|
fd_set fds;
|
|
uint8_t *buf;
|
|
pid_t pid;
|
|
size_t size;
|
|
ssize_t len;
|
|
uint32_t seq;
|
|
int fd, fd2, fd3, status, bytes, fl;
|
|
|
|
subtest = 1;
|
|
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
0 /*set_filter*/);
|
|
|
|
/*
|
|
* Test that a filled-up store buffer will be returned to a pending
|
|
* read call. Perform this first test without a filter, to ensure that
|
|
* the default behavior is to accept all packets. The side effect is
|
|
* that we may receive other loopback traffic as part of our capture.
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_fill_random(fd2, buf, size);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (len < size * 3/4) e(0);
|
|
if (len > size) e(0);
|
|
test94_check(buf, len, 1 /*seq*/, 0 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/* Only the exact buffer size may be used in read calls. */
|
|
if (read(fd, buf, size - 1) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
if (read(fd, buf, size + 1) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
if (read(fd, buf, sizeof(struct bpf_hdr)) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
/*
|
|
* Install a filter to improve predictability for the remaining tests.
|
|
*/
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = __arraycount(test94_filter);
|
|
bf.bf_insns = test94_filter;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
/*
|
|
* Next we want to test that an already filled-up buffer will be
|
|
* returned to a read call immediately. We take the opportunity to
|
|
* test that filling the buffer will also wake up a blocked select
|
|
* call. In addition, we test ioctl(FIONREAD).
|
|
*/
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
|
|
if (FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != 0) e(0);
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_fill_random(fd2, buf, size);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (len < size * 3/4) e(0);
|
|
if (len > size) e(0);
|
|
seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
|
|
NULL /*caplen*/, NULL /*datalen*/);
|
|
|
|
if (len != bytes) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/* There is one more packet in the store buffer at this point. */
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
|
|
if (FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != 0) e(0);
|
|
|
|
/*
|
|
* Next, we test whether read timeouts work, first checking that a
|
|
* timed-out read call returns any packets currently in the buffer.
|
|
* We use sleep and a signal as a crude way to test that the call was
|
|
* actually blocked until the timeout occurred.
|
|
*/
|
|
got_signal = 0;
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
signal(SIGUSR1, test94_signal);
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_add_random(fd2, buf, size, seq + 1);
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
if (got_signal != 0) e(0);
|
|
pause();
|
|
if (got_signal != 1) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = SLEEP_TIME * 3;
|
|
if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* two packets < 3/4 of the size */
|
|
if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 2) e(0);
|
|
|
|
if (kill(pid, SIGUSR1) != 0) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* Next, see if a timed-out read will all buffers empty yields EAGAIN.
|
|
*/
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = SLEEP_TIME;
|
|
if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
/*
|
|
* Verify that resetting the timeout to zero makes the call block
|
|
* forever (for short test values of "forever" anyway), because
|
|
* otherwise this may create a false illusion of correctness in the
|
|
* next test, for non-blocking calls. As a side effect, this tests
|
|
* read call signal interruption, and ensures no partial results are
|
|
* returned in that case.
|
|
*/
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
signal(SIGUSR1, test94_signal);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EINTR) e(0);
|
|
|
|
if (got_signal != 1) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
usleep(SLEEP_TIME * 2);
|
|
|
|
if (kill(pid, SIGUSR1) != 0) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* Repeat the same test with a non-full, non-empty buffer, to ensure
|
|
* that interrupted reads do not return partial results.
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
signal(SIGUSR1, test94_signal);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EINTR) e(0);
|
|
|
|
if (got_signal != 1) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_add_random(fd2, buf, size, 2);
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
if (kill(pid, SIGUSR1) != 0) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* Test non-blocking reads with empty, full, and non-empty buffers.
|
|
* Against common sense, the last case should return whatever is in
|
|
* the buffer rather than EAGAIN, like immediate-mode reads would.
|
|
*/
|
|
if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
|
|
if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
seq = test94_check(buf, len, 2 /*seq*/, 1 /*filtered*/,
|
|
NULL /*caplen*/, NULL /*datalen*/);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
test94_fill_random(fd2, buf, size);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len < size * 3/4) e(0);
|
|
if (len > size) e(0);
|
|
seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
|
|
NULL /*caplen*/, NULL /*datalen*/);
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 1) e(0);
|
|
|
|
if (fcntl(fd, F_SETFL, fl) != 0) e(0);
|
|
|
|
/*
|
|
* Test two remaining aspects of select(2): single-packet arrivals do
|
|
* not cause a wake-up, and the read timer has no effect. The latter
|
|
* is a deliberate implementation choice where we diverge from NetBSD,
|
|
* because it requires keeping state in a way that violates the
|
|
* principle of system call independence.
|
|
*/
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = SLEEP_TIME * 2;
|
|
if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_add_random(fd2, buf, size, 1);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Test reading packets from a BPF device, using immediate mode.
|
|
*/
|
|
static void
|
|
test94b(void)
|
|
{
|
|
struct timeval tv;
|
|
fd_set fds;
|
|
uint8_t *buf;
|
|
unsigned int val;
|
|
size_t size;
|
|
ssize_t len;
|
|
uint32_t seq;
|
|
pid_t pid;
|
|
int fd, fd2, fd3, bytes, status, fl;
|
|
|
|
subtest = 2;
|
|
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
1 /*set_filter*/);
|
|
|
|
val = 1;
|
|
if (ioctl(fd, BIOCIMMEDIATE, &val) != 0) e(0);
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != 0) e(0);
|
|
|
|
/*
|
|
* Ensure that if the hold buffer is full, an immediate-mode read
|
|
* returns the content of the hold buffer, even if the store buffer is
|
|
* not empty.
|
|
*/
|
|
test94_fill_random(fd2, buf, size);
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len < size * 3/4) e(0);
|
|
if (len > size) e(0);
|
|
seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
|
|
NULL /*caplen*/, NULL /*datalen*/);
|
|
|
|
if (len != bytes) e(0);
|
|
|
|
/*
|
|
* There is one packet left in the buffer. In immediate mode, this
|
|
* packet should be returned immediately.
|
|
*/
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 1) e(0);
|
|
|
|
if (len != bytes) e(0);
|
|
|
|
/* The buffer is now empty again. */
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != 0) e(0);
|
|
|
|
/*
|
|
* Immediate-mode reads may return multiple packets from the store
|
|
* buffer.
|
|
*/
|
|
test94_add_random(fd2, buf, size, seq + 1);
|
|
test94_add_random(fd2, buf, size, seq + 2);
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* two packets < 3/4 of the size */
|
|
if (test94_check(buf, len, seq + 1, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 3) e(0);
|
|
|
|
if (len != bytes) e(0);
|
|
|
|
/*
|
|
* Now test waking up suspended calls, read(2) first.
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_add_random(fd2, buf, size, seq + 3);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, seq + 3, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 4) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* Then select(2).
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_add_random(fd2, buf, size, seq + 4);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, seq + 4, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 5) e(0);
|
|
|
|
if (len != bytes) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* Non-blocking reads should behave just as with regular mode.
|
|
*/
|
|
if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
|
|
if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
test94_fill_random(fd2, buf, size);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len < size * 3/4) e(0);
|
|
if (len > size) e(0);
|
|
seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
|
|
NULL /*caplen*/, NULL /*datalen*/);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq + 1) e(0);
|
|
|
|
if (fcntl(fd, F_SETFL, fl) != 0) e(0);
|
|
|
|
/*
|
|
* Timeouts should work with immediate mode.
|
|
*/
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = SLEEP_TIME;
|
|
if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Test reading packets from a BPF device, with an exactly filled buffer. The
|
|
* idea is that normally the store buffer is considered "full" if the next
|
|
* packet does not fit in it, but if no more bytes are left in it, it can be
|
|
* rotated immediately. This is a practically useless edge case, but we
|
|
* support it, so we might as well test it. Also, some of the code for this
|
|
* case is shared with other rare cases that we cannot test here (interfaces
|
|
* disappearing, to be specific), and exactly filling up the buffers does test
|
|
* some other bounds checks so all that might make this worth it anyway. While
|
|
* we are exercising full control over our buffers, also check statistics.
|
|
*/
|
|
static void
|
|
test94c(void)
|
|
{
|
|
struct bpf_stat bs;
|
|
fd_set fds;
|
|
uint8_t *buf;
|
|
size_t size;
|
|
pid_t pid;
|
|
uint32_t count, seq;
|
|
int fd, fd2, fd3, bytes, status, fl;
|
|
|
|
subtest = 3;
|
|
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
1 /*set_filter*/);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != 0) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
|
|
/*
|
|
* Test read, select, and ioctl(FIONREAD) on an exactly filled buffer.
|
|
*/
|
|
count = test94_fill_exact(fd2, buf, size, 0);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != count) e(0);
|
|
if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
|
|
if (bs.bs_drop != 0) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != size) e(0);
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
test94_check(buf, size, 0 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/);
|
|
|
|
/*
|
|
* If the store buffer is full, the buffers should be swapped after
|
|
* emptying the hold buffer.
|
|
*/
|
|
seq = test94_fill_exact(fd2, buf, size, 1);
|
|
test94_fill_exact(fd2, buf, size, seq);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != count * 3) e(0);
|
|
if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
|
|
if (bs.bs_drop != 0) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 0); /* this one will get dropped */
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != count * 3 + 1) e(0);
|
|
if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
|
|
if (bs.bs_drop != 1) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 0); /* this one will get dropped */
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != count * 3 + 2) e(0);
|
|
if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
|
|
if (bs.bs_drop != 2) e(0);
|
|
|
|
if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
|
|
if (bytes != size) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
if (test94_check(buf, size, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != seq) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
if (test94_check(buf, size, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != count * 2 + 1) e(0);
|
|
|
|
/*
|
|
* See if an exactly filled buffer resumes reads...
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_fill_exact(fd2, buf, size, 1);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
test94_check(buf, size, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
/*
|
|
* ...and selects.
|
|
*/
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
usleep(SLEEP_TIME);
|
|
|
|
test94_fill_exact(fd2, buf, size, seq);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
|
|
if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
test94_check(buf, size, seq, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != count * 5 + 2) e(0);
|
|
if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
|
|
if (bs.bs_drop != 2) e(0);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Test receipt of large packets on BPF devices. Large packets should be
|
|
* truncated to the size of the buffer, but unless the filter specifies a
|
|
* smaller capture size, no more than that.
|
|
*/
|
|
static void
|
|
test94d(void)
|
|
{
|
|
struct bpf_hdr bh;
|
|
uint8_t *buf, *buf2;
|
|
size_t size;
|
|
ssize_t len;
|
|
int fd, fd2, fd3, datalen;
|
|
|
|
subtest = 4;
|
|
|
|
/*
|
|
* Specify a size smaller than the largest packet we can send on the
|
|
* loopback device. The size we specify here is currently the default
|
|
* size already anyway, but that might change in the future.
|
|
*/
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 32768 /*size*/,
|
|
1 /*set_filter*/);
|
|
if (size != 32768) e(0);
|
|
|
|
datalen = 65000;
|
|
if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &datalen,
|
|
sizeof(datalen)) != 0) e(0);
|
|
|
|
if ((buf2 = malloc(datalen)) == NULL) e(0);
|
|
|
|
memset(buf2, 'Y', datalen);
|
|
buf2[0] = 'X';
|
|
buf2[size - sizeof(struct udphdr) - sizeof(struct ip) -
|
|
BPF_WORDALIGN(sizeof(bh)) - 1] = 'Z';
|
|
|
|
if (write(fd2, buf2, datalen) != datalen) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
|
|
memcpy(&bh, buf, sizeof(bh));
|
|
|
|
if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
if (bh.bh_caplen != size - BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
if (bh.bh_datalen !=
|
|
sizeof(struct ip) + sizeof(struct udphdr) + datalen) e(0);
|
|
|
|
if (buf[BPF_WORDALIGN(sizeof(bh)) + sizeof(struct ip) +
|
|
sizeof(struct udphdr)] != 'X') e(0);
|
|
if (buf[size - 2] != 'Y') e(0);
|
|
if (buf[size - 1] != 'Z') e(0);
|
|
|
|
/*
|
|
* Add a smaller packet in between, to ensure that 1) the large packet
|
|
* is not split across buffers, and 2) the packet is truncated to the
|
|
* size of the buffer, not the available part of the buffer. Note how
|
|
* forced rotation and our exact-fill policy preclude us from having to
|
|
* use immediate mode for any of this.
|
|
*/
|
|
test94_add_random(fd2, buf, size, 1 /*seq*/);
|
|
|
|
if (write(fd2, buf2, datalen) != datalen) e(0);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
if (len >= size * 3/4) e(0); /* one packet < 3/4 of the size */
|
|
if (test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
|
|
NULL /*datalen*/) != 2) e(0);
|
|
|
|
if (read(fd, buf, size) != size) e(0);
|
|
|
|
memcpy(&bh, buf, sizeof(bh));
|
|
|
|
if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
if (bh.bh_caplen != size - BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
if (bh.bh_datalen !=
|
|
sizeof(struct ip) + sizeof(struct udphdr) + datalen) e(0);
|
|
|
|
if (buf[BPF_WORDALIGN(sizeof(bh)) + sizeof(struct ip) +
|
|
sizeof(struct udphdr)] != 'X') e(0);
|
|
if (buf[size - 2] != 'Y') e(0);
|
|
if (buf[size - 1] != 'Z') e(0);
|
|
|
|
free(buf2);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Test whether our filter is active through two-way communication and a
|
|
* subsequent check on the BPF statistics. We do not actually look through the
|
|
* captured packets, because who knows what else is active on the loopback
|
|
* device (e.g., X11) and the extra code specifically to extract our packets in
|
|
* the other direction is simply not worth it.
|
|
*/
|
|
static void
|
|
test94_comm(int fd, int fd2, int fd3, int filtered)
|
|
{
|
|
struct bpf_stat bs;
|
|
char c;
|
|
|
|
if (write(fd2, "A", 1) != 1) e(0);
|
|
|
|
if (read(fd3, &c, 1) != 1) e(0);
|
|
if (c != 'A') e(0);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv == 0) e(0);
|
|
if (bs.bs_capt == 0) e(0);
|
|
|
|
if (ioctl(fd, BIOCFLUSH) != 0) e(0);
|
|
|
|
if (write(fd3, "B", 1) != 1) e(0);
|
|
|
|
if (read(fd2, &c, 1) != 1) e(0);
|
|
if (c != 'B') e(0);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv == 0) e(0);
|
|
|
|
if (filtered) {
|
|
if (bs.bs_capt != 0) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
} else
|
|
if (bs.bs_capt == 0) e(0);
|
|
|
|
if (ioctl(fd, BIOCFLUSH) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test filter installation and mechanics.
|
|
*/
|
|
static void
|
|
test94e(void)
|
|
{
|
|
struct bpf_program bf;
|
|
struct bpf_stat bs;
|
|
struct bpf_hdr bh;
|
|
uint8_t *buf;
|
|
size_t size, len, plen, alen, off;
|
|
uint32_t seq, caplen[4], datalen[4];
|
|
int i, fd, fd2, fd3, val;
|
|
|
|
subtest = 5;
|
|
|
|
/*
|
|
* We have already tested installing a filter both before and after
|
|
* attaching to an interface by now, so we do not repeat that here.
|
|
*/
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
0 /*set_filter*/);
|
|
|
|
val = 1;
|
|
if (ioctl(fd, BIOCIMMEDIATE, &val) != 0) e(0);
|
|
|
|
/*
|
|
* A filter that is too large is rejected. Unfortunately, due to
|
|
* necessary IOCTL rewriting, this tests libc, not the service.
|
|
*/
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = BPF_MAXINSNS + 1;
|
|
bf.bf_insns = NULL;
|
|
if (ioctl(fd, BIOCSETF, &bf) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
/*
|
|
* An invalid filter is rejected. In this test case, the truncated
|
|
* filter has a jump target beyond the end of the filter program.
|
|
*/
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = __arraycount(test94_filter) - 1;
|
|
bf.bf_insns = test94_filter;
|
|
if (ioctl(fd, BIOCSETF, &bf) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
test94_comm(fd, fd2, fd3, 0 /*filtered*/);
|
|
|
|
bf.bf_len++;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_comm(fd, fd2, fd3, 1 /*filtered*/);
|
|
|
|
/*
|
|
* Installing a zero-length filter clears the current filter, if any.
|
|
*/
|
|
memset(&bf, 0, sizeof(bf));
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_comm(fd, fd2, fd3, 0 /*filtered*/);
|
|
|
|
/* Test this twice to trip over unconditional filter deallocation. */
|
|
memset(&bf, 0, sizeof(bf));
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_comm(fd, fd2, fd3, 0 /*filtered*/);
|
|
|
|
/*
|
|
* Test both aligned and unaligned capture sizes. For each, test
|
|
* sizes larger than, equal to, and smaller than the capture size.
|
|
* In both cases, aggregate the packets into a single buffer and only
|
|
* then go through them, to see whether alignment was done correctly.
|
|
* We cannot do everything in one go as BIOCSETF implies a BIOCFLUSH.
|
|
*/
|
|
plen = sizeof(struct ip) + sizeof(struct udphdr) + sizeof(seq);
|
|
if (BPF_WORDALIGN(plen) != plen) e(0);
|
|
alen = BPF_WORDALIGN(plen + 1);
|
|
if (alen - 2 <= plen + 1) e(0);
|
|
|
|
/* First the aligned cases. */
|
|
test94_filter[__arraycount(test94_filter) - 1].k = alen;
|
|
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = __arraycount(test94_filter);
|
|
bf.bf_insns = test94_filter;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_comm(fd, fd2, fd3, 1 /*filtered*/);
|
|
|
|
test94_add_specific(fd2, buf, alen + 1 - plen, 1);
|
|
caplen[0] = alen;
|
|
datalen[0] = alen + 1;
|
|
|
|
test94_add_specific(fd2, buf, alen - plen, 2);
|
|
caplen[1] = alen;
|
|
datalen[1] = alen;
|
|
|
|
test94_add_specific(fd2, buf, alen + 3 - plen, 3);
|
|
caplen[2] = alen;
|
|
datalen[2] = alen + 3;
|
|
|
|
test94_add_specific(fd2, buf, alen - 1 - plen, 4);
|
|
caplen[3] = alen - 1;
|
|
datalen[3] = alen - 1;
|
|
|
|
memset(buf, 0, size);
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/, caplen,
|
|
datalen) != 5) e(0);
|
|
|
|
/* Then the unaligned cases. */
|
|
test94_filter[__arraycount(test94_filter) - 1].k = alen + 1;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_add_specific(fd2, buf, alen + 2 - plen, 5);
|
|
caplen[0] = alen + 1;
|
|
datalen[0] = alen + 2;
|
|
|
|
test94_add_specific(fd2, buf, alen + 1 - plen, 6);
|
|
caplen[1] = alen + 1;
|
|
datalen[1] = alen + 1;
|
|
|
|
test94_add_specific(fd2, buf, alen + 9 - plen, 7);
|
|
caplen[2] = alen + 1;
|
|
datalen[2] = alen + 9;
|
|
|
|
test94_add_specific(fd2, buf, alen - plen, 8);
|
|
caplen[3] = alen;
|
|
datalen[3] = alen;
|
|
|
|
memset(buf, 0, size);
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (test94_check(buf, len, 5 /*seq*/, 1 /*filtered*/, caplen,
|
|
datalen) != 9) e(0);
|
|
|
|
/*
|
|
* Check that capturing only one byte from packets is possible. Not
|
|
* that that would be particularly useful.
|
|
*/
|
|
test94_filter[__arraycount(test94_filter) - 1].k = 1;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 9);
|
|
test94_add_random(fd2, buf, size, 10);
|
|
test94_add_random(fd2, buf, size, 11);
|
|
|
|
memset(buf, 0, size);
|
|
|
|
len = read(fd, buf, size);
|
|
if (len <= 0) e(0);
|
|
|
|
off = 0;
|
|
for (i = 0; i < 3; i++) {
|
|
if (len - off < sizeof(bh)) e(0);
|
|
memcpy(&bh, &buf[off], sizeof(bh));
|
|
|
|
if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0)
|
|
e(0);
|
|
if (bh.bh_caplen != 1) e(0);
|
|
if (bh.bh_datalen < plen) e(0);
|
|
if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
|
|
off += bh.bh_hdrlen;
|
|
|
|
if (buf[off] != 0x45) e(0);
|
|
|
|
off += BPF_WORDALIGN(bh.bh_caplen);
|
|
}
|
|
if (off != len) e(0);
|
|
|
|
/*
|
|
* Finally, a zero capture size should result in rejected packets only.
|
|
*/
|
|
test94_filter[__arraycount(test94_filter) - 1].k = 0;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 12);
|
|
test94_add_random(fd2, buf, size, 13);
|
|
test94_add_random(fd2, buf, size, 14);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv < 3) e(0);
|
|
if (bs.bs_capt != 0) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
|
|
/* Restore the capture limit of the filter to its original state. */
|
|
test94_filter[__arraycount(test94_filter) - 1].k = (uint32_t)-1;
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Compute an IP checksum.
|
|
*/
|
|
static uint16_t
|
|
test94_cksum(uint8_t * buf, size_t len)
|
|
{
|
|
uint32_t sum, word;
|
|
|
|
/* This is a really dumb implementation but *shrug*. */
|
|
for (sum = 0; len > 0; sum += word) {
|
|
if (len > 1) {
|
|
word = buf[0] << 8 | buf[1];
|
|
buf += 2;
|
|
len -= 2;
|
|
} else {
|
|
word = buf[0] << 8;
|
|
len--;
|
|
}
|
|
}
|
|
|
|
while (sum > UINT16_MAX)
|
|
sum = (sum & UINT16_MAX) + (sum >> 16);
|
|
|
|
return ~(uint16_t)sum;
|
|
}
|
|
|
|
/*
|
|
* Set up UDP headers for a packet. The packet uses IPv4 unless 'v6' is set,
|
|
* in which case IPv6 is used. The given buffer must be large enough to
|
|
* contain the headers and the (to be appended) data. The function returns the
|
|
* offset into the buffer to the data portion of the packet.
|
|
*/
|
|
static size_t
|
|
test94_make_pkt(uint8_t * buf, size_t len, int v6)
|
|
{
|
|
struct ip ip;
|
|
struct ip6_hdr ip6;
|
|
struct udphdr uh;
|
|
size_t off;
|
|
|
|
if (!v6) {
|
|
memset(&ip, 0, sizeof(ip));
|
|
ip.ip_v = IPVERSION;
|
|
ip.ip_hl = sizeof(ip) >> 2;
|
|
ip.ip_len = htons(sizeof(ip) + sizeof(uh) + len);
|
|
ip.ip_ttl = 255;
|
|
ip.ip_p = IPPROTO_UDP;
|
|
ip.ip_sum = 0;
|
|
ip.ip_src.s_addr = htonl(INADDR_LOOPBACK);
|
|
ip.ip_dst.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
memcpy(buf, &ip, sizeof(ip));
|
|
ip.ip_sum = htons(test94_cksum(buf, sizeof(ip)));
|
|
memcpy(buf, &ip, sizeof(ip));
|
|
if (test94_cksum(buf, sizeof(ip)) != 0) e(0);
|
|
|
|
off = sizeof(ip);
|
|
} else {
|
|
memset(&ip6, 0, sizeof(ip6));
|
|
ip6.ip6_vfc = IPV6_VERSION;
|
|
ip6.ip6_plen = htons(sizeof(uh) + len);
|
|
ip6.ip6_nxt = IPPROTO_UDP;
|
|
ip6.ip6_hlim = 255;
|
|
memcpy(&ip6.ip6_src, &in6addr_loopback, sizeof(ip6.ip6_src));
|
|
memcpy(&ip6.ip6_dst, &in6addr_loopback, sizeof(ip6.ip6_dst));
|
|
|
|
memcpy(buf, &ip6, sizeof(ip6));
|
|
|
|
off = sizeof(ip6);
|
|
}
|
|
|
|
memset(&uh, 0, sizeof(uh));
|
|
uh.uh_sport = htons(TEST_PORT_A);
|
|
uh.uh_dport = htons(TEST_PORT_B);
|
|
uh.uh_ulen = htons(sizeof(uh) + len);
|
|
uh.uh_sum = 0; /* lazy but we also don't have the data yet */
|
|
|
|
memcpy(buf + off, &uh, sizeof(uh));
|
|
|
|
return off + sizeof(uh);
|
|
}
|
|
|
|
/*
|
|
* Test sending packets by writing to a BPF device.
|
|
*/
|
|
static void
|
|
test94f(void)
|
|
{
|
|
struct bpf_stat bs;
|
|
struct ifreq ifr;
|
|
fd_set fds;
|
|
uint8_t *buf;
|
|
size_t off;
|
|
unsigned int i, uval, mtu;
|
|
int fd, fd2, fd3;
|
|
|
|
subtest = 6;
|
|
|
|
(void)test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
1 /*set_filter*/);
|
|
|
|
/*
|
|
* Select queries should always indicate that the device is writable.
|
|
*/
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
/*
|
|
* Test packet size limits. For loopback devices, the maximum data
|
|
* link layer level maximum transmission unit should be 65535-4 =
|
|
* 65531 bytes. Obtain the actual value anyway; it might have changed.
|
|
*/
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
|
|
|
|
if (ioctl(fd2, SIOCGIFMTU, &ifr) != 0) e(0);
|
|
mtu = ifr.ifr_mtu;
|
|
|
|
if ((buf = realloc(buf, UINT16_MAX + 1)) == NULL) e(0);
|
|
|
|
memset(buf, 0, UINT16_MAX + 1);
|
|
|
|
for (i = UINT16_MAX + 1; i > mtu; i--) {
|
|
if (write(fd, buf, i) != -1) e(0);
|
|
if (errno != EMSGSIZE) e(0);
|
|
}
|
|
|
|
/* This packet will be discarded as completely crap. That's fine. */
|
|
if (write(fd, buf, mtu) != mtu) e(0);
|
|
|
|
/*
|
|
* Zero-sized writes are accepted but do not do anything.
|
|
*/
|
|
if (write(fd, buf, 0) != 0) e(0);
|
|
|
|
/*
|
|
* Send an actual packet, and see if it arrives.
|
|
*/
|
|
off = test94_make_pkt(buf, 6, 0 /*v6*/);
|
|
memcpy(buf + off, "Hello!", 6);
|
|
|
|
if (write(fd, buf, off + 6) != off + 6) e(0);
|
|
|
|
memset(buf, 0, mtu);
|
|
if (read(fd3, buf, mtu) != 6) e(0);
|
|
if (memcmp(buf, "Hello!", 6) != 0) e(0);
|
|
|
|
/*
|
|
* Enable feedback mode to test that the packet now arrives twice.
|
|
* Send a somewhat larger packet to test that data copy-in handles
|
|
* offsets correctly.
|
|
*/
|
|
uval = 1;
|
|
if (ioctl(fd, BIOCSFEEDBACK, &uval) != 0) e(0);
|
|
|
|
off = test94_make_pkt(buf, 12345, 0 /*v6*/);
|
|
for (i = 0; i < 12345; i++)
|
|
buf[off + i] = 1 + (i % 251); /* the largest prime < 255 */
|
|
|
|
if (write(fd, buf, off + 12345) != off + 12345) e(0);
|
|
|
|
/* We need a default UDP SO_RCVBUF >= 12345 * 2 for this. */
|
|
memset(buf, 0, UINT16_MAX);
|
|
if (recv(fd3, buf, UINT16_MAX, 0) != 12345) e(0);
|
|
for (i = 0; i < 12345; i++)
|
|
if (buf[i] != 1 + (i % 251)) e(0);
|
|
|
|
memset(buf, 0, UINT16_MAX);
|
|
if (recv(fd3, buf, UINT16_MAX, MSG_DONTWAIT) != 12345) e(0);
|
|
for (i = 0; i < 12345; i++)
|
|
if (buf[i] != 1 + (i % 251)) e(0);
|
|
|
|
if (recv(fd3, buf, UINT16_MAX, MSG_DONTWAIT) != -1) e(0);
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
|
|
/*
|
|
* The two valid packets we sent will have been captured by our BPF
|
|
* device as well, because SEESENT is enabled by default and also
|
|
* applies to packets written to a BPF device. The reason for that is
|
|
* that it allows tcpdump(8) to see what DHCP clients are sending, for
|
|
* example. The packets we sent are accepted by the installed filter.
|
|
*/
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_capt != 2) e(0);
|
|
|
|
/* Now that we've written data, test select once more. */
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0);
|
|
if (!FD_ISSET(fd, &fds)) e(0);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
}
|
|
|
|
/*
|
|
* Test read, write, and select operations on unconfigured devices.
|
|
*/
|
|
static void
|
|
test94g(void)
|
|
{
|
|
fd_set rfds, wfds;
|
|
uint8_t *buf;
|
|
unsigned int size;
|
|
int fd;
|
|
|
|
subtest = 7;
|
|
|
|
if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
if (ioctl(fd, BIOCGBLEN, &size) != 0) e(0);
|
|
if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
|
|
|
|
if ((buf = malloc(size)) == NULL) e(0);
|
|
|
|
if (read(fd, buf, size) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
if (write(fd, buf, size) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
FD_ZERO(&wfds);
|
|
FD_SET(fd, &wfds);
|
|
|
|
if (select(fd + 1, &rfds, &wfds, NULL, NULL) != 2) e(0);
|
|
|
|
if (!FD_ISSET(fd, &rfds)) e(0);
|
|
if (!FD_ISSET(fd, &wfds)) e(0);
|
|
|
|
free(buf);
|
|
|
|
if (close(fd) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test various IOCTL calls. Several of these tests are rather superficial,
|
|
* because we would need a real interface, rather than the loopback device, to
|
|
* test their functionality properly. Also note that we skip various checks
|
|
* performed as part of the earlier subtests.
|
|
*/
|
|
static void
|
|
test94h(void)
|
|
{
|
|
struct bpf_stat bs;
|
|
struct bpf_version bv;
|
|
struct bpf_dltlist bfl;
|
|
struct ifreq ifr;
|
|
struct timeval tv;
|
|
uint8_t *buf;
|
|
size_t size;
|
|
unsigned int uval, list[2];
|
|
int cfd, ufd, fd2, fd3, val;
|
|
|
|
subtest = 8;
|
|
|
|
/*
|
|
* Many IOCTLs work only on configured or only on unconfigured BPF
|
|
* devices, so for convenience we create a file descriptor for each.
|
|
*/
|
|
size = test94_setup(&cfd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
1 /*set_filter*/);
|
|
|
|
if ((ufd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
/*
|
|
* The BIOCSBLEN value is silently corrected to fall within a valid
|
|
* range, and BIOCGBLEN can be used to obtain the corrected value. We
|
|
* do not know the valid range, so we use fairly extreme test values.
|
|
*/
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
|
|
if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
|
|
|
|
uval = (unsigned int)-1;
|
|
if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
|
|
if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
|
|
if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
|
|
|
|
uval = 1024; /* ..a value that should be acceptable but small */
|
|
if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
|
|
if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
|
|
if (uval != 1024) e(0);
|
|
|
|
/*
|
|
* For configured devices, it is not possible to adjust the buffer size
|
|
* but it is possible to obtain its size.
|
|
*/
|
|
if (ioctl(cfd, BIOCSBLEN, &uval) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
if (ioctl(cfd, BIOCGBLEN, &uval) != 0) e(0);
|
|
if (uval != size) e(0);
|
|
|
|
/*
|
|
* BIOCFLUSH resets both buffer contents and statistics.
|
|
*/
|
|
uval = 1;
|
|
if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
test94_fill_exact(fd2, buf, size, 1 /*seq*/);
|
|
test94_fill_exact(fd2, buf, size, 1 /*seq*/);
|
|
test94_fill_exact(fd2, buf, size, 1 /*seq*/);
|
|
|
|
if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv == 0) e(0);
|
|
if (bs.bs_drop == 0) e(0);
|
|
if (bs.bs_capt == 0) e(0);
|
|
|
|
/* Do make sure that statistics are not cleared on retrieval.. */
|
|
if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv == 0) e(0);
|
|
if (bs.bs_drop == 0) e(0);
|
|
if (bs.bs_capt == 0) e(0);
|
|
|
|
if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
|
|
if (val == 0) e(0);
|
|
|
|
if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
|
|
|
|
/* There is a race condition for bs_recv here, so we cannot test it. */
|
|
if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
if (bs.bs_capt != 0) e(0);
|
|
|
|
if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
|
|
if (val != 0) e(0);
|
|
|
|
/*
|
|
* Although practically useless, BIOCFLUSH works on unconfigured
|
|
* devices. So does BIOCGSTATS.
|
|
*/
|
|
if (ioctl(ufd, BIOCFLUSH) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv != 0) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
if (bs.bs_capt != 0) e(0);
|
|
|
|
/*
|
|
* BIOCPROMISC works on configured devices only. On loopback devices
|
|
* it has no observable effect though.
|
|
*/
|
|
if (ioctl(ufd, BIOCPROMISC) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
if (ioctl(cfd, BIOCPROMISC) != 0) e(0);
|
|
|
|
/*
|
|
* BIOCGDLT does not work on unconfigured devices.
|
|
*/
|
|
if (ioctl(ufd, BIOCGDLT, &uval) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
/*
|
|
* BIOCGETIF works only on configured devices, where it returns the
|
|
* associated device name.
|
|
*/
|
|
if (ioctl(ufd, BIOCGETIF, &ifr) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
memset(&ifr, 'X', sizeof(ifr));
|
|
if (ioctl(cfd, BIOCGETIF, &ifr) != 0) e(0);
|
|
if (strcmp(ifr.ifr_name, LOOPBACK_IFNAME) != 0) e(0);
|
|
|
|
/*
|
|
* BIOCSETIF works only on unconfigured devices, and accepts only valid
|
|
* valid interface names. The name is forced to be null terminated.
|
|
*/
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
|
|
if (ioctl(cfd, BIOCSETIF, &ifr) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
memset(ifr.ifr_name, 'x', sizeof(ifr.ifr_name));
|
|
if (ioctl(ufd, BIOCSETIF, &ifr) != -1) e(0);
|
|
if (errno != ENXIO) e(0);
|
|
|
|
/* Anyone that has ten loopback devices is simply insane. */
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
|
|
ifr.ifr_name[strlen(ifr.ifr_name) - 1] += 9;
|
|
if (ioctl(ufd, BIOCSETIF, &ifr) != -1) e(0);
|
|
if (errno != ENXIO) e(0);
|
|
|
|
/*
|
|
* It is possible to turn BIOCIMMEDIATE on and off. We already enabled
|
|
* it a bit higher up. Note that our implementation does not support
|
|
* toggling the setting while a read call is no progress, and toggling
|
|
* the setting will have no effect while a select call is in progress;
|
|
* similar restrictions apply to effectively all relevant settings.
|
|
* Either way we do not test that here either.
|
|
*/
|
|
test94_add_random(fd2, buf, size, 1 /*seq*/);
|
|
|
|
if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
|
|
if (val == 0) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
|
|
if (val != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
|
|
if (val == 0) e(0);
|
|
|
|
if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
|
|
|
|
/*
|
|
* BIOCIMMEDIATE also works on unconfigured devices.
|
|
*/
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
/*
|
|
* BIOCVERSION should return the current BPF interface version.
|
|
*/
|
|
if (ioctl(ufd, BIOCVERSION, &bv) != 0) e(0);
|
|
if (bv.bv_major != BPF_MAJOR_VERSION) e(0);
|
|
if (bv.bv_minor != BPF_MINOR_VERSION) e(0);
|
|
|
|
/*
|
|
* BIOCSHDRCMPLT makes sense only for devices with data link headers,
|
|
* which rules out loopback devices. Check the default and test
|
|
* toggling it, and stop there.
|
|
*/
|
|
/* The default value is off. */
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
|
|
if (uval != 0) e(0);
|
|
|
|
uval = 2;
|
|
if (ioctl(ufd, BIOCSHDRCMPLT, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
|
|
if (uval != 1) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCSHDRCMPLT, &uval) != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
|
|
if (uval != 0) e(0);
|
|
|
|
/*
|
|
* BIOCSDLT works on configured devices. For loopback devices, it can
|
|
* only set the data link type to its current value, which on MINIX3
|
|
* for loopback devices is DLT_RAW (i.e., no headers at all).
|
|
*/
|
|
uval = DLT_RAW;
|
|
if (ioctl(ufd, BIOCSDLT, &uval) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
uval = DLT_RAW;
|
|
if (ioctl(cfd, BIOCSDLT, &uval) != 0) e(0);
|
|
|
|
uval = DLT_NULL;
|
|
if (ioctl(cfd, BIOCSDLT, &uval) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
if (ioctl(cfd, BIOCGDLT, &uval) != 0) e(0);
|
|
if (uval != DLT_RAW) e(0);
|
|
|
|
/*
|
|
* BIOCGDLTLIST works on configured devices only, and may be used to
|
|
* both query the size of the list and obtain the list. On MINIX3,
|
|
* loopback devices will only ever return DLT_RAW. Unfortunately,
|
|
* much of the handling for this IOCTL is in libc for us, which is also
|
|
* why we do not test bad pointers and stuff like that.
|
|
*/
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
if (ioctl(ufd, BIOCGDLTLIST, &bfl) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
|
|
if (bfl.bfl_len != 1) e(0);
|
|
if (bfl.bfl_list != NULL) e(0);
|
|
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
bfl.bfl_len = 2; /* should be ignored */
|
|
if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
|
|
if (bfl.bfl_len != 1) e(0);
|
|
if (bfl.bfl_list != NULL) e(0);
|
|
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
memset(list, 0, sizeof(list));
|
|
bfl.bfl_list = list;
|
|
if (ioctl(cfd, BIOCGDLTLIST, &bfl) != -1) e(0);
|
|
if (errno != ENOMEM) e(0);
|
|
if (list[0] != 0) e(0);
|
|
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
bfl.bfl_len = 1;
|
|
bfl.bfl_list = list;
|
|
if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
|
|
if (bfl.bfl_len != 1) e(0);
|
|
if (bfl.bfl_list != list) e(0);
|
|
if (list[0] != DLT_RAW) e(0);
|
|
if (list[1] != 0) e(0);
|
|
|
|
memset(&bfl, 0, sizeof(bfl));
|
|
memset(list, 0, sizeof(list));
|
|
bfl.bfl_len = 2;
|
|
bfl.bfl_list = list;
|
|
if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
|
|
if (bfl.bfl_len != 1) e(0);
|
|
if (bfl.bfl_list != list) e(0);
|
|
if (list[0] != DLT_RAW) e(0);
|
|
if (list[1] != 0) e(0);
|
|
|
|
/*
|
|
* For loopback devices, BIOCSSEESENT is a bit weird: packets are
|
|
* captured on output to get a complete view of loopback traffic, and
|
|
* not also on input because that would then duplicate the traffic. As
|
|
* a result, turning off BIOCSSEESENT for a loopback device means that
|
|
* no packets will be captured at all anymore. First test the default
|
|
* and toggling on the unconfigured device, then reproduce the above on
|
|
* the configured device.
|
|
*/
|
|
/* The default value is on. */
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
|
|
if (uval != 1) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCSSEESENT, &uval) != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
|
|
if (uval != 0) e(0);
|
|
|
|
uval = 2;
|
|
if (ioctl(ufd, BIOCSSEESENT, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
|
|
if (uval != 1) e(0);
|
|
|
|
if (ioctl(cfd, BIOCGSEESENT, &uval) != 0) e(0);
|
|
if (uval != 1) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(cfd, BIOCSSEESENT, &uval) != 0) e(0);
|
|
|
|
if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 1 /*seq*/);
|
|
|
|
if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(cfd, BIOCSSEESENT, &uval) != 0) e(0);
|
|
|
|
if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
|
|
|
|
test94_add_random(fd2, buf, size, 1 /*seq*/);
|
|
|
|
if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv == 0) e(0);
|
|
|
|
/*
|
|
* The BIOCSRTIMEOUT values are rounded up to clock granularity.
|
|
* Invalid timeout values are rejected.
|
|
*/
|
|
/* The default value is zero. */
|
|
tv.tv_sec = 99;
|
|
if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
|
|
if (tv.tv_sec != 0) e(0);
|
|
if (tv.tv_usec != 0) e(0);
|
|
|
|
tv.tv_usec = 1000000;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
tv.tv_usec = -1;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
tv.tv_sec = -1;
|
|
tv.tv_usec = 0;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
|
|
if (errno != EINVAL) e(0);
|
|
|
|
tv.tv_sec = INT_MAX;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
|
|
if (errno != EDOM) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
|
|
if (tv.tv_sec != 0) e(0);
|
|
if (tv.tv_usec != 0) e(0);
|
|
|
|
tv.tv_sec = 123;
|
|
tv.tv_usec = 1;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
|
|
if (tv.tv_sec != 123) e(0);
|
|
if (tv.tv_usec == 0) e(0); /* rounding should be up */
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
|
|
if (tv.tv_sec != 0) e(0);
|
|
if (tv.tv_usec != 0) e(0);
|
|
|
|
/*
|
|
* BIOCSFEEDBACK is another weird setting for which we only test
|
|
* default and toggling here.
|
|
*/
|
|
/* The default value is off. */
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
|
|
if (uval != 0) e(0);
|
|
|
|
uval = 2;
|
|
if (ioctl(ufd, BIOCSFEEDBACK, &uval) != 0) e(0);
|
|
|
|
if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
|
|
if (uval != 1) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(ufd, BIOCSFEEDBACK, &uval) != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
|
|
if (uval != 0) e(0);
|
|
|
|
/* Clean up. */
|
|
if (close(ufd) != 0) e(0);
|
|
|
|
test94_cleanup(cfd, fd2, fd3, buf);
|
|
}
|
|
|
|
/* IPv6 version of our filter. */
|
|
static struct bpf_insn test94_filter6[] = {
|
|
{ BPF_LD+BPF_B+BPF_ABS, 0, 0, 0 }, /* is this an IPv6 header? */
|
|
{ BPF_ALU+BPF_RSH+BPF_K, 0, 0, 4 },
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 6, 6 },
|
|
{ BPF_LD+BPF_B+BPF_ABS, 0, 0, 6 }, /* is this a UDP packet? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 4, IPPROTO_UDP },
|
|
{ BPF_LD+BPF_H+BPF_ABS, 0, 0, 40 }, /* source port 12345? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 0, 2, TEST_PORT_A },
|
|
{ BPF_LD+BPF_H+BPF_ABS, 0, 0, 42 }, /* destination port 12346? */
|
|
{ BPF_JMP+BPF_JEQ+BPF_K, 1, 0, TEST_PORT_B },
|
|
{ BPF_RET+BPF_K, 0, 0, 0 }, /* reject the packet */
|
|
{ BPF_RET+BPF_K, 0, 0, (uint32_t)-1 }, /* accept the (whole) packet */
|
|
};
|
|
|
|
/*
|
|
* Test receipt of IPv6 packets, because it was getting a bit messy to
|
|
* integrate that into the previous subtests. We just want to make sure that
|
|
* IPv6 packets are properly filtered and captured at all. The rest of the
|
|
* code is entirely version agnostic anyway.
|
|
*/
|
|
static void
|
|
test94i(void)
|
|
{
|
|
struct sockaddr_in6 sin6A, sin6B;
|
|
struct bpf_program bf;
|
|
struct bpf_stat bs;
|
|
struct bpf_hdr bh;
|
|
struct ifreq ifr;
|
|
struct ip6_hdr ip6;
|
|
struct udphdr uh;
|
|
uint8_t *buf, c;
|
|
socklen_t socklen;
|
|
ssize_t len;
|
|
size_t off;
|
|
unsigned int uval, size, dlt;
|
|
int fd, fd2, fd3;
|
|
|
|
subtest = 9;
|
|
|
|
if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
if (ioctl(fd, BIOCGBLEN, &size) != 0) e(0);
|
|
if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
|
|
|
|
if ((buf = malloc(size)) == NULL) e(0);
|
|
|
|
/* Install the filter. */
|
|
memset(&bf, 0, sizeof(bf));
|
|
bf.bf_len = __arraycount(test94_filter6);
|
|
bf.bf_insns = test94_filter6;
|
|
if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
|
|
|
|
uval = 1;
|
|
if (ioctl(fd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
|
|
/* Bind to the loopback device. */
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
|
|
if (ioctl(fd, BIOCSETIF, &ifr) != 0) e(0);
|
|
|
|
/*
|
|
* If the loopback device's data link type is not DLT_RAW, our filter
|
|
* and size calculations will not work.
|
|
*/
|
|
if (ioctl(fd, BIOCGDLT, &dlt) != 0) e(0);
|
|
if (dlt != DLT_RAW) e(0);
|
|
|
|
/* We use UDP traffic for our test packets. */
|
|
if ((fd2 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
memset(&sin6A, 0, sizeof(sin6A));
|
|
sin6A.sin6_family = AF_INET6;
|
|
sin6A.sin6_port = htons(TEST_PORT_A);
|
|
memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr));
|
|
if (bind(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
|
|
|
|
memcpy(&sin6B, &sin6A, sizeof(sin6B));
|
|
sin6B.sin6_port = htons(TEST_PORT_B);
|
|
if (connect(fd2, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
|
|
|
|
if ((fd3 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
if (bind(fd3, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
|
|
|
|
if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
|
|
|
|
if (write(fd2, "A", 1) != 1) e(0);
|
|
|
|
if (read(fd3, &c, 1) != 1) e(0);
|
|
if (c != 'A') e(0);
|
|
|
|
if (write(fd3, "B", 1) != 1) e(0);
|
|
|
|
if (read(fd2, &c, 1) != 1) e(0);
|
|
if (c != 'B') e(0);
|
|
|
|
if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
|
|
if (bs.bs_recv < 2) e(0);
|
|
if (bs.bs_capt != 1) e(0);
|
|
if (bs.bs_drop != 0) e(0);
|
|
|
|
memset(buf, 0, size);
|
|
|
|
len = read(fd, buf, size);
|
|
|
|
if (len != BPF_WORDALIGN(sizeof(bh)) +
|
|
BPF_WORDALIGN(sizeof(ip6) + sizeof(uh) + 1)) e(0);
|
|
|
|
memcpy(&bh, buf, sizeof(bh));
|
|
|
|
if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0) e(0);
|
|
if (bh.bh_caplen != sizeof(ip6) + sizeof(uh) + 1) e(0);
|
|
if (bh.bh_datalen != bh.bh_caplen) e(0);
|
|
if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
|
|
|
|
if (buf[bh.bh_hdrlen + sizeof(ip6) + sizeof(uh)] != 'A') e(0);
|
|
|
|
/*
|
|
* Finally, do a quick test to see if we can send IPv6 packets by
|
|
* writing to the BPF device. We rely on such packets being generated
|
|
* properly in a later test.
|
|
*/
|
|
off = test94_make_pkt(buf, 6, 1 /*v6*/);
|
|
memcpy(buf + off, "Hello!", 6);
|
|
|
|
if (write(fd, buf, off + 6) != off + 6) e(0);
|
|
|
|
socklen = sizeof(sin6A);
|
|
if (recvfrom(fd3, buf, size, 0, (struct sockaddr *)&sin6A,
|
|
&socklen) != 6) e(0);
|
|
|
|
if (memcmp(buf, "Hello!", 6) != 0) e(0);
|
|
if (socklen != sizeof(sin6A)) e(0);
|
|
if (sin6A.sin6_family != AF_INET6) e(0);
|
|
if (sin6A.sin6_port != htons(TEST_PORT_A)) e(0);
|
|
if (memcmp(&sin6A.sin6_addr, &in6addr_loopback,
|
|
sizeof(sin6A.sin6_addr)) != 0) e(0);
|
|
|
|
free(buf);
|
|
|
|
if (close(fd3) != 0) e(0);
|
|
|
|
if (close(fd2) != 0) e(0);
|
|
|
|
if (close(fd) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test the BPF sysctl(7) interface at a basic level.
|
|
*/
|
|
static void
|
|
test94j(void)
|
|
{
|
|
struct bpf_stat bs1, bs2;
|
|
struct bpf_d_ext *bde;
|
|
uint8_t *buf;
|
|
unsigned int slot, count, uval;
|
|
size_t len, oldlen, size, bdesize;
|
|
int fd, fd2, fd3, val, mib[5], smib[3], found;
|
|
|
|
subtest = 10;
|
|
|
|
/*
|
|
* Obtain the maximum buffer size. The value must be sane.
|
|
*/
|
|
memset(mib, 0, sizeof(mib));
|
|
len = __arraycount(mib);
|
|
if (sysctlnametomib("net.bpf.maxbufsize", mib, &len) != 0) e(0);
|
|
if (len != 3) e(0);
|
|
|
|
oldlen = sizeof(val);
|
|
if (sysctl(mib, len, &val, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen != sizeof(val)) e(0);
|
|
|
|
if (val < 1024 || val > INT_MAX / 2) e(0);
|
|
|
|
/*
|
|
* Attempt to set the maximum buffer size. This is not (yet) supported
|
|
* so for now we want to make sure that it really does not work.
|
|
*/
|
|
if (sysctl(mib, len, NULL, NULL, &val, sizeof(val)) != -1) e(0);
|
|
if (errno != EPERM) e(0);
|
|
|
|
/*
|
|
* Obtain global statistics. We check the actual statistics later on.
|
|
*/
|
|
memset(smib, 0, sizeof(smib));
|
|
len = __arraycount(smib);
|
|
if (sysctlnametomib("net.bpf.stats", smib, &len) != 0) e(0);
|
|
if (len != 3) e(0);
|
|
|
|
oldlen = sizeof(bs1);
|
|
if (sysctl(smib, len, &bs1, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen != sizeof(bs1)) e(0);
|
|
|
|
/*
|
|
* Set up a BPF descriptor, and retrieve the list of BPF peers. We
|
|
* should be able to find our BPF peer.
|
|
*/
|
|
memset(mib, 0, sizeof(mib));
|
|
len = __arraycount(mib);
|
|
if (sysctlnametomib("net.bpf.peers", mib, &len) != 0) e(0);
|
|
if (len != 3) e(0);
|
|
mib[len++] = sizeof(*bde); /* size of each element */
|
|
mib[len++] = INT_MAX; /* limit on elements to return */
|
|
|
|
size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
|
|
1 /*set_filter*/);
|
|
|
|
/* Generate some traffic to bump the statistics. */
|
|
count = test94_fill_exact(fd2, buf, size, 0);
|
|
test94_fill_exact(fd2, buf, size, 0);
|
|
test94_fill_exact(fd2, buf, size, 0);
|
|
|
|
if (write(fd3, "X", 1) != 1) e(0);
|
|
|
|
if (sysctl(mib, len, NULL, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen == 0) e(0);
|
|
|
|
/* Add some slack space ourselves to prevent problems with churn. */
|
|
bdesize = oldlen + sizeof(*bde) * 8;
|
|
if ((bde = malloc(bdesize)) == NULL) e(0);
|
|
|
|
oldlen = bdesize;
|
|
if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen % sizeof(*bde)) e(0);
|
|
|
|
found = 0;
|
|
for (slot = 0; slot < oldlen / sizeof(*bde); slot++) {
|
|
if (bde[slot].bde_pid != getpid())
|
|
continue;
|
|
|
|
if (bde[slot].bde_bufsize != size) e(0);
|
|
if (bde[slot].bde_promisc != 0) e(0);
|
|
if (bde[slot].bde_state != BPF_IDLE) e(0);
|
|
if (bde[slot].bde_immediate != 0) e(0);
|
|
if (bde[slot].bde_hdrcmplt != 0) e(0);
|
|
if (bde[slot].bde_seesent != 1) e(0);
|
|
if (bde[slot].bde_rcount < count * 3 + 1) e(0);
|
|
if (bde[slot].bde_dcount != count) e(0);
|
|
if (bde[slot].bde_ccount != count * 3) e(0);
|
|
if (strcmp(bde[slot].bde_ifname, LOOPBACK_IFNAME) != 0) e(0);
|
|
|
|
found++;
|
|
}
|
|
if (found != 1) e(0);
|
|
|
|
/*
|
|
* If global statistics are an accumulation of individual devices'
|
|
* statistics (they currently are not) then such a scheme should take
|
|
* into account device flushes.
|
|
*/
|
|
if (ioctl(fd, BIOCFLUSH) != 0) e(0);
|
|
|
|
test94_cleanup(fd, fd2, fd3, buf);
|
|
|
|
/*
|
|
* Now see if the global statistics have indeed changed correctly.
|
|
*/
|
|
oldlen = sizeof(bs2);
|
|
if (sysctl(smib, __arraycount(smib), &bs2, &oldlen, NULL, 0) != 0)
|
|
e(0);
|
|
if (oldlen != sizeof(bs2)) e(0);
|
|
|
|
if (bs2.bs_recv < bs1.bs_recv + count * 3 + 1) e(0);
|
|
if (bs2.bs_drop != bs1.bs_drop + count) e(0);
|
|
if (bs2.bs_capt != bs1.bs_capt + count * 3) e(0);
|
|
|
|
/*
|
|
* Check an unconfigured BPF device as well.
|
|
*/
|
|
if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
/*
|
|
* Toggle some flags. It is too much effort to test them all
|
|
* individually (which, in the light of copy-paste mistakes, would be
|
|
* the right thing to do) but at least we'll know something gets set.
|
|
*/
|
|
uval = 1;
|
|
if (ioctl(fd, BIOCIMMEDIATE, &uval) != 0) e(0);
|
|
if (ioctl(fd, BIOCSHDRCMPLT, &uval) != 0) e(0);
|
|
|
|
uval = 0;
|
|
if (ioctl(fd, BIOCSSEESENT, &uval) != 0) e(0);
|
|
|
|
oldlen = bdesize;
|
|
if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen % sizeof(*bde)) e(0);
|
|
|
|
found = 0;
|
|
for (slot = 0; slot < oldlen / sizeof(*bde); slot++) {
|
|
if (bde[slot].bde_pid != getpid())
|
|
continue;
|
|
|
|
if (bde[slot].bde_bufsize != size) e(0);
|
|
if (bde[slot].bde_promisc != 0) e(0);
|
|
if (bde[slot].bde_state != BPF_IDLE) e(0);
|
|
if (bde[slot].bde_immediate != 1) e(0);
|
|
if (bde[slot].bde_hdrcmplt != 1) e(0);
|
|
if (bde[slot].bde_seesent != 0) e(0);
|
|
if (bde[slot].bde_rcount != 0) e(0);
|
|
if (bde[slot].bde_dcount != 0) e(0);
|
|
if (bde[slot].bde_ccount != 0) e(0);
|
|
if (bde[slot].bde_ifname[0] != '\0') e(0);
|
|
|
|
found++;
|
|
}
|
|
if (found != 1) e(0);
|
|
|
|
close(fd);
|
|
|
|
/*
|
|
* At this point there should be no BPF device left for our PID.
|
|
*/
|
|
oldlen = bdesize;
|
|
if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
|
|
if (oldlen % sizeof(*bde)) e(0);
|
|
|
|
for (slot = 0; slot < oldlen / sizeof(*bde); slot++)
|
|
if (bde[slot].bde_pid == getpid()) e(0);
|
|
found++;
|
|
|
|
free(bde);
|
|
}
|
|
|
|
/*
|
|
* Test privileged operations as an unprivileged caller.
|
|
*/
|
|
static void
|
|
test94k(void)
|
|
{
|
|
struct passwd *pw;
|
|
pid_t pid;
|
|
size_t len, oldlen;
|
|
int mib[5], status;
|
|
|
|
subtest = 11;
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0);
|
|
|
|
if (setuid(pw->pw_uid) != 0) e(0);
|
|
|
|
/*
|
|
* Opening /dev/bpf must fail. Note that this is a system
|
|
* configuration issue rather than a LWIP service issue.
|
|
*/
|
|
if (open(_PATH_BPF, O_RDWR) != -1) e(0);
|
|
if (errno != EACCES) e(0);
|
|
|
|
/*
|
|
* Retrieving the net.bpf.peers list must fail, too.
|
|
*/
|
|
memset(mib, 0, sizeof(mib));
|
|
len = __arraycount(mib);
|
|
if (sysctlnametomib("net.bpf.peers", mib, &len) != 0) e(0);
|
|
if (len != 3) e(0);
|
|
mib[len++] = sizeof(struct bpf_d_ext);
|
|
mib[len++] = INT_MAX;
|
|
|
|
if (sysctl(mib, len, NULL, &oldlen, NULL, 0) != -1) e(0);
|
|
if (errno != EPERM) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test that traffic directed to loopback addresses be dropped on non-loopback
|
|
* interfaces. In particular, inbound traffic to 127.0.0.1 and ::1 should not
|
|
* be accepted on any interface that does not own those addresses. This test
|
|
* is here because BPF feedback mode is (currently) the only way in which we
|
|
* can generate inbound traffic the ethernet level, and even then only as a
|
|
* side effect of sending outbound traffic. That is: this test sends the same
|
|
* test packets to the local network! As such it must be performed only when
|
|
* USENETWORK=yes and therefore at the user's risk.
|
|
*/
|
|
static void
|
|
test94l(void)
|
|
{
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
struct sockaddr_dl sdl;
|
|
struct ifreq ifr;
|
|
struct ifaddrs *ifa, *ifp;
|
|
struct if_data *ifdata;
|
|
uint8_t buf[sizeof(struct ether_header) + MAX(sizeof(struct ip),
|
|
sizeof(struct ip6_hdr)) + sizeof(struct udphdr) + 6];
|
|
struct ether_header ether;
|
|
const uint8_t ether_src[ETHER_ADDR_LEN] =
|
|
{ 0x02, 0x00, 0x01, 0x12, 0x34, 0x56 };
|
|
unsigned int val;
|
|
size_t off;
|
|
int bfd, sfd;
|
|
|
|
subtest = 12;
|
|
|
|
if (!get_setting_use_network())
|
|
return;
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
memset(ðer, 0, sizeof(ether));
|
|
|
|
/*
|
|
* Start by finding a suitable ethernet interface that is up and of
|
|
* which the link is not down. Without one, we cannot perform this
|
|
* test. Save the interface name and the ethernet address.
|
|
*/
|
|
if (getifaddrs(&ifa) != 0) e(0);
|
|
|
|
for (ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) {
|
|
if (!(ifp->ifa_flags & IFF_UP) || ifp->ifa_addr == NULL ||
|
|
ifp->ifa_addr->sa_family != AF_LINK)
|
|
continue;
|
|
|
|
ifdata = (struct if_data *)ifp->ifa_data;
|
|
if (ifdata != NULL && ifdata->ifi_type == IFT_ETHER &&
|
|
ifdata->ifi_link_state != LINK_STATE_DOWN) {
|
|
strlcpy(ifr.ifr_name, ifp->ifa_name,
|
|
sizeof(ifr.ifr_name));
|
|
|
|
memcpy(&sdl, (struct sockaddr_dl *)ifp->ifa_addr,
|
|
offsetof(struct sockaddr_dl, sdl_data));
|
|
if (sdl.sdl_alen != sizeof(ether.ether_dhost)) e(0);
|
|
memcpy(ether.ether_dhost,
|
|
((struct sockaddr_dl *)ifp->ifa_addr)->sdl_data +
|
|
sdl.sdl_nlen, sdl.sdl_alen);
|
|
break;
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifa);
|
|
|
|
if (ifp == NULL)
|
|
return;
|
|
|
|
/* Open a BPF device and bind it to the ethernet interface we found. */
|
|
if ((bfd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
|
|
|
|
if (ioctl(bfd, BIOCSETIF, &ifr) != 0) e(0);
|
|
|
|
if (ioctl(bfd, BIOCGDLT, &val) != 0) e(0);
|
|
if (val != DLT_EN10MB) e(0);
|
|
|
|
val = 1;
|
|
if (ioctl(bfd, BIOCSFEEDBACK, &val) != 0) e(0);
|
|
|
|
/* We use UDP traffic for our test packets, IPv4 first. */
|
|
if ((sfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(TEST_PORT_B);
|
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
|
|
|
|
/*
|
|
* Construct and send a packet. We already filled in the ethernet
|
|
* destination address. Put in a source address that is locally
|
|
* administered but valid (and as such no reason for packet rejection).
|
|
*/
|
|
memcpy(ether.ether_shost, ether_src, sizeof(ether.ether_shost));
|
|
ether.ether_type = htons(ETHERTYPE_IP);
|
|
|
|
memcpy(buf, ðer, sizeof(ether));
|
|
off = sizeof(ether);
|
|
off += test94_make_pkt(buf + off, 6, 0 /*v6*/);
|
|
if (off + 6 > sizeof(buf)) e(0);
|
|
memcpy(buf + off, "Hello!", 6);
|
|
|
|
if (write(bfd, buf, off + 6) != off + 6) e(0);
|
|
|
|
/* The packet MUST NOT arrive. */
|
|
if (recv(sfd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
|
|
if (close(sfd) != 0) e(0);
|
|
|
|
/* Try the same thing, but now with an IPv6 packet. */
|
|
if ((sfd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
|
|
|
|
memset(&sin6, 0, sizeof(sin6));
|
|
sin6.sin6_family = AF_INET6;
|
|
sin6.sin6_port = htons(TEST_PORT_B);
|
|
memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
|
|
if (bind(sfd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
|
|
|
|
ether.ether_type = htons(ETHERTYPE_IPV6);
|
|
|
|
memcpy(buf, ðer, sizeof(ether));
|
|
off = sizeof(ether);
|
|
off += test94_make_pkt(buf + off, 6, 1 /*v6*/);
|
|
if (off + 6 > sizeof(buf)) e(0);
|
|
memcpy(buf + off, "Hello!", 6);
|
|
|
|
if (write(bfd, buf, off + 6) != off + 6) e(0);
|
|
|
|
if (recv(sfd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
|
|
if (close(sfd) != 0) e(0);
|
|
if (close(bfd) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test program for LWIP BPF.
|
|
*/
|
|
int
|
|
main(int argc, char ** argv)
|
|
{
|
|
int i, m;
|
|
|
|
start(94);
|
|
|
|
srand48(time(NULL));
|
|
|
|
if (argc == 2)
|
|
m = atoi(argv[1]);
|
|
else
|
|
m = 0xFFF;
|
|
|
|
for (i = 0; i < ITERATIONS; i++) {
|
|
if (m & 0x001) test94a();
|
|
if (m & 0x002) test94b();
|
|
if (m & 0x004) test94c();
|
|
if (m & 0x008) test94d();
|
|
if (m & 0x010) test94e();
|
|
if (m & 0x020) test94f();
|
|
if (m & 0x040) test94g();
|
|
if (m & 0x080) test94h();
|
|
if (m & 0x100) test94i();
|
|
if (m & 0x200) test94j();
|
|
if (m & 0x400) test94k();
|
|
if (m & 0x800) test94l();
|
|
}
|
|
|
|
quit();
|
|
/* NOTREACHED */
|
|
}
|