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

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 device bpf line in your kernel. If you don't know how to create your own kernel, take a look at the excellent FreeBSD handbook.

If you want to learn more about the BPF, type man 4 bpf.

Create and configure a BPF device

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

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";
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 );

Read 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;
bpf_hdr* bpf_buf = new bpf_hdr[ buf_len ];
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:

Please note that ethernet_frame is my own structure used to describe one ethernet frame (802.3). If you want to learn about the ethernet's structure, you could use something like Wireshark, or read the standard RFCs (or both ;) ).

Send (your own) packets

Sometimes, you might want to send your own packets instead of sticking to the analysis of capture 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:

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:

Following the given example, your frame could look like:

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 is clearly 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.