This is a small HOWTO about the BPF device under FreeBSD. I will show you how to access and configure this device. You will also learn how to send and receive ethernet frames. If you want to see an example for possible BPF uses, you might want to consider taking a look at in medias res.

Any C compiler should be able to compile the example code. Thanks to Pedro for pointing out several syntax errors.

What is the BPF?

The Berkeley Packet Filter is one of FreeBSD's most impressive devices. It provides you full ("raw") access to your NICs data link layers, i.e. you are totally protocol-independent. In general, you should be able to capture and send all packets that arrive on your network card, even if they are meant to reach other hosts (for example: if you are using a hub instead of a switch, higher-leveled raw interfaces will probably discard frames that are not for your MAC address. The BPF won't...). To use this really powerful device, you need a kernel that contains device bpf. If you don't know how to create your own kernel, take a look at the excellent FreeBSD handbook.

More information about the BPF is readily available via man 4 bpf.

Creating and configuring a BPF device

In order to create a functional, readable instance of the BPF device, you have to:

  • Open /dev/bpfn, where n depends on how many other applications are using a BPF
  • Associate your file descriptor with one network interface
  • Set the "immediate mode" so that a call to read will return immediately if a packet has been received
  • Request the BPF's buffer size

Let's proceed chronologically. First, we will try to open the next available BPF device:

char buf[ 11 ] = { 0 };
int bpf = 0;

for( int i = 0; i < 99; i++ )
{
    sprintf( buf, "/dev/bpf%i", i );
    bpf = open( buf, O_RDWR );

    if( bpf != -1 )
        break;
}

Now we are going to associate it with a specific network device, such as fxp0:

const char* interface = "fxp0";
struct ifreq bound_if;

strcpy(bound_if.ifr_name, interface);
if(ioctl( bpf, BIOCSETIF, &bound_if ) > 0)
    return(-1);

All's well at the moment, so let's enable immediate mode and request the buffer size. The last point is very important, as the BPF is allowed to provide you with more than one packet after issuing a call to read. If you know the buffer size, you can advance to the next packet.

int buf_len = 1;

// activate immediate mode (therefore, buf_len is initially set to "1")
if( ioctl( bpf, BIOCIMMEDIATE, &buf_len ) == -1 )
    return( -1 );

// request buffer length
if( ioctl( bpf, BIOCGBLEN, &buf_len ) == -1 )
      return( -1 );

Reading packets

Now, as we are completely done with the initialization and have a working file descriptor, we want to capture incoming traffic. The good thing about BPF is that you can set up filter rules if you only want to receive specific traffic, such as TCP/IP packets.

In theory, there is no need to do more than making a call to read. The resulting buffer contains a bpf_hdr and following after that, a packet. So one could just do something like that to convert this buffer into a valid ethernet frame:

frame = (ethernet_frame*) ( (char*) bpf_buf + bpf_buf->bh_hdrlen);

Unfortunately, sometimes the kernel likes to add more than one packet to your buffer. Well, the lazy approach would just read one packet per buffer, and wait for the TCP retransmissions that may arrive. But being lazy is not a good solution. Therefore, we need a loop to read all packets that are in the buffer:

int read_byes = 0;

ethernet_frame* frame;
struct bpf_hdr* bpf_buf = new bpf_hdr[buf_len];
struct bpf_hdr* bpf_packet;

while(run_loop)
{
    memset(bpf_buf, 0, buf_len);

    if((read_bytes = read(bpf, bpf_buf, buf_len)) > 0)
    {
        int i = 0;

        // read all packets that are included in bpf_buf. BPF_WORDALIGN is used
        // to proceed to the next BPF packet that is available in the buffer.

        char* ptr = reinterpret_cast<char*>(bpf_buf);
        while(ptr < (reinterpret_cast<char*>(bpf_buf) + read_bytes))
        {
            bpf_packet = reinterpret_cast<bpf_hdr*>(ptr);
            frame = (ethernet_frame*)((char*) bpf_packet + bpf_packet->bh_hdrlen);

            // do something with the Ethernet frame
            // [...]

            ptr += BPF_WORDALIGN(bpf_packet->bh_hdrlen + bpf_packet->bh_caplen);
        }
    }
}

The above loop does the following things:

  • As long as the "distance" between the original bpf_buf and the auxiliary pointer ptr is not bigger as the number of bytes actually read...

  • ...the auxiliary pointer is advanced to the next ethernet frame. BPF_WORDALIGN rounds up to the next even multiple of BPF_ALIGNMENT. This means that you will jump over all bytes that are used for padding purposes. Hence, bpf_packet always points to the next bpf_hdr structure, always given the fact that there is more than one.

Please note that ethernet_frame is my own structure used to describe one ethernet frame (802.3). Read the standard RFCs or use Wireshark if you want to learn more.

Sending (your own) packets

Sometimes, you might want to send your own packets instead of sticking to the analysis of captured ones. No problem with the BPF. If the BPF is initialised as aforementioned, sending packets is really no problem at all. A quick call to write will do the trick:

write(bpf, frame, bpf_buf->bh_caplen);

In this snippet, bpf is the BPF's file descriptor, frame is a pointer to an ethernet frame that has a TCP/IP packet attached (remember the initialization of frame above?). Of course, this is totally useless, but if you want to write a little broadcast router or something like that, you could just change the destination MAC address and write the more or less unchanged frame plus the payload to the BPF. You won't have to care about the source MAC address, as the BPF does that for you (look at the man page and search for BIOCGHDRCMPLT if you want to disable this feature).

Ethernet frames

An ethernet frame is the basic structure that is sent through your network cables. You have to use it if you need to access the link layer, i.e. if you want to send your own raw packets. This is how an ethernet frame (802.3, ethernet version 2.0) could look like:

destination hardware (MAC) address [6 bytes] source hardware (MAC) address [6 bytes] layer-3 protocol type [2 bytes] payload [46 - 1500 bytes] FCS [4 bytes]

The FCS field is not necessarily needed. The other attributes should be initialised, except the source MAC address (see above for explanation). This is what you should do if you want to send your own packets:

  • Prepare one ethernet frame and supply it with the proper values
  • Pay particular attention to the type field. Otherwise, you might experience errors (for example: IP packets with an ARP type field).
  • Attach the payload. For an arbitrary TCP/IP packet, you would need:
    • IP header
    • TCP header
    • TCP payload
  • Send it!
  • For debugging purposes, you should have a network sniffer which will tell you if something went wrong.

Following the given example, your frame could look like this:

01:02:03:04:05:06 Destination MAC
01:02:03:04:05:06 Source MAC
0x0800 Type: IP
IP header
TCP header
TCP payload

Conclusion

The BPF clearly is a very powerful thing. If you know something about the underlying network structure, you can do unbelievable things with it. Of course, you do not need to stick to the TCP. For a nice example of using the BPF, take a look at IMR, a man-in-the-middle application that uses ARP and directs traffic between two victim hosts.

Additional information is available through these documents:

You could also take a closer look on the additional BPF flags, for example BIOCGHDRCMPLT. This flag allows you to fill in the link level source address of an ethernet frame by yourself, thus allowing you to create arbitrary spoofed packets that may trick other hosts in your network.