bpf
Berkeley Packet Filter
Name:
/dev/bpfDescription:
The bpf device represents the Berkley Packet Filter mechanism ported to QNX OS from FreeBSD. For more information,
see Packet Filtering
in
the High-Performance Networking Stack User's Guide, and the FreeBSD documentation (https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD+13.3-RELEASE+and+Ports).
For more information on QNX OS packet filtering, go to
Packet Filtering
in the High-Performance Networking Stack
User's Guide.
Sending multiple packets on BPF
QNX has extended bpf to support multi-packet writes, which you
can configure with the ioctl() commands
BIOCSMMWRITE and BIOCGMMWRITE (go to the
BIOCSMMWRITE
and BIOCGMMWRITE
entries in the QNX OS Devctl and Ioctl Commands reference).
When you use BIOCSMMWRITE to send multiple packets in a single write, you must prepend a bpf_whdr structure to each packet. For example:
#include <net/bpf.h>
/* used for multi-packet sends */
struct bpf_whdr {
uint32_t bh_hdrlen;
uint32_t bh_datalen; /* original length of packet */
};
Packets to be sent should be padded the same way as when they are read, with padding immediately after bpf_whdr.
The following example code constructs the packet as expected by bpf with multi-write enabled. It is constructed the same way as for a regular bpf write, except it is an IOV array and has the required prepend of a bpf_whdr. The get_whdr() function constructs the header and adds it to the IOV array. This action includes setting both the bh_hdrlen argument of the bpf_whdr to the size of the header plus added alignment padding, and the length of the actual packet. The main() function constructs multiple packets as part of an IOV array, then calls a single write with writev().
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
/* Convenience macros */
#define FAIL_EQ(ret, eq, fmt) \
if (ret == eq) { \
printf("%s : %s\n", fmt, strerror(errno)); \
return -1; \
}
#define FAIL_LT(ret, eq, fmt) \
if (ret < eq) { \
printf("%s : %s\n", fmt, strerror(errno)); \
return -1; \
}
#define DEFAULT_BPF_PATH "/dev/bpf"
#define DEFAULT_BPF_IF "vtnet0"
#define NUM_PACKETS 5
/* Holds the bpf init data */
struct bpf_ctx {
int bpf_fd;
char *bpf_path;
char *bpf_ifname;
char inp_dmac[512];
uint16_t inp_sport;
uint16_t inp_dport;
};
/* Set up bpf file, the same as bpf(4) */
static int
bpf_init (struct bpf_ctx *bpf)
{
int ret = 0;
int multi_write = 1;
struct ifreq ifr = { 0 };
bpf->bpf_path = DEFAULT_BPF_PATH;
bpf->bpf_ifname = DEFAULT_BPF_IF;
bpf->bpf_fd = open(bpf->bpf_path, O_RDWR);
FAIL_EQ(bpf->bpf_fd, -1, "Failed to open BPF file");
/* BIOCSMMWRITE must be set before BIOCSETIF */
ret = ioctl(bpf->bpf_fd, BIOCSMMWRITE, &multi_write);
FAIL_EQ(ret, -1, "Failed to set BPF mmwrite");
strlcpy(ifr.ifr_name, bpf->bpf_ifname, sizeof(ifr.ifr_name));
ret = ioctl(bpf->bpf_fd, BIOCSETIF, &ifr);
FAIL_EQ(ret, -1, "Failed to set BPF interface");
/* Set destination MAC */
bpf->inp_dmac[0] = 0x02; /* local */
bpf->inp_dmac[1] = 0xde;
bpf->inp_dmac[2] = 0xad;
bpf->inp_dmac[3] = 0xbe;
bpf->inp_dmac[4] = 0xef;
bpf->inp_dmac[5] = 0x00;
bpf->inp_sport = 0;
bpf->inp_dport = 42557;
return 0;
}
/* Create pseudo packet for demonstration */
static int
get_ethhdr(struct bpf_ctx *bpf, iov_t *iov)
{
struct ether_header *eh;
eh = malloc(sizeof(struct ether_header));
FAIL_EQ(eh, NULL, "Failed to allocate ether_header memory");
/* bpf fills ether_shost if BIOCSHDRCMPLT isn't set */
memcpy(eh->ether_dhost, bpf->inp_dmac, sizeof(eh->ether_dhost));
eh->ether_type = htons(ETHERTYPE_LOOPBACK);
/* Set as part of the packet */
SETIOV(iov, eh, sizeof(struct ether_header));
return 0;
}
/* Populate the bpf_whdr for a packet */
static int
get_whdr(iov_t *iov, int data_size)
{
int ret = 0;
int padding;
struct bpf_whdr *wh;
/*
* bpf multi-write assumes that the packets are aligned the same
* way they are returned on read. For example, with a UDP packet:
* | whdr | pad | ethhdr | iphdr | udphdr | app |
*
* The padding being: BPF_WORDALIGN(packetlen) - packetlen
* This helps with performance
*/
padding = BPF_WORDALIGN(data_size) - data_size;
/* Size of the header + padding, in the above comment */
wh = malloc(sizeof(*wh) + padding);
FAIL_EQ(wh, NULL, "Failed to allow bpf_whdr memory");
/* Include padding in bh_hdrlen. The actual packet size is bh_ datalen */
wh->bh_hdrlen = sizeof(*wh) + padding;
wh->bh_datalen = data_size;
/* Set before the rest of the packet */
SETIOV(iov, wh, wh->bh_hdrlen);
return 0;
}
int
main (void)
{
int i, j;
int ret = 0;
int num_iov = NUM_PACKETS * 2;
iov_t iov[100] = {0};
iov_t *t_iov;
struct bpf_ctx bpf;
ret = bpf_init(&bpf);
FAIL_EQ(ret, -1, "Failed to initialize BPF ctx");
/*
* Setup X packets to send in a single write. Each layer can be in
* their own iov or grouped together, as long as the header is separate.
* For example:
*
* | iov[0] | iov[1] | iov[2] | iov[3] | iov[4] |
* | whdr | pad | ethhdr | iphdr | udphdr | app |
*
* OR
*
* | iov[0] | iov[1] |
* | whdr | pad | ethhdr | iphdr | udphdr | app |
*
* As long as bh_datalen is the size of the entire packet
*/
for (i = 0, j = 0; j < NUM_PACKETS; i += 2, j++) {
/* As above, first set the eth header */
t_iov = &iov[i+1];
ret = get_ethhdr(&bpf, t_iov);
FAIL_EQ(ret, -1, "Failed to get ethhdr");
/* And then set the bpf multi-write header */
t_iov = &iov[i + 0];
ret = get_whdr(t_iov, iov[i+1].iov_len);
FAIL_EQ(ret, -1, "Failed to get whdr");
/*
* Where this gives us:
* | iov[i] | iov[i+1] |
* | whdr | pad | ethhdr |
*/
}
/* The packets that were built are malformed as they're just an eth hdr */
ret = writev(bpf.bpf_fd, iov, num_iov);
FAIL_EQ(ret, -1, "Write failed");
printf("Packets sent: %i\n", NUM_PACKETS);
printf("Bytes written: %i\n", ret);
}
For additional ioctl() command codes, go to the FreeBSD documentation (https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD+13.3-RELEASE+and+Ports).