Mixed VLAN tags and BPF syntax

This post contains a warning and a solution for anyone using BPF syntax when filtering traffic for network security monitoring. 


I have been writing material for the Zeek documentation project. I was collecting a sample trace in my home lab, which includes a Ubiquiti switch. The switch in this setup sends a copy of traffic on its uplink via a SPAN port to a network security monitoring sensor. On the sensor, I collected traffic using Tcpdump, with an IP address filter for a Raspberry Pi. On the Pi, I used the Linux “curl” command to visit www.taosecurity.com. Here is the command I used to collect the traffic:

As you might expect, Tcpdump collected DNS requests and responses in addition to the TCP traffic to and from port 443 TCP on the Web server. Here is the beginning of the traffic:

The first four datagrams are DNS requests and responses. The next three segments are the TCP three-way handshake.


I decided that I wanted to filter out the DNS traffic for the document I was creating. I used the following Berkeley Packet Filter (BPF) syntax to view a subset of the original traffic:

This is indeed TCP traffic. However, all of the traffic originates from the Web server at There is no initial SYN from the Web client, or a final ACK to finish the three-way handshake.

Leveraging Wireshark

I had a suspicion about what could be causing this, so I decided to take a closer look using Wireshark.

Notice in the previous figure the VLAN tag that I have highlighted in frame 5. These VLAN tags create a 4 byte offset, making “normal” BPF syntax incorrect. Initially I thought that all of this traffic would have a VLAN tag, making it fairly simple for me to adjust to this situation. I could change my BPF from “tcp” to “vlan and tcp”.

However, I looked at the next segment to see if that answer would truly fix my problem.

Frame 6 does not  have a VLAN tag. In other words, we have a “mixed VLAN tag” situation. 

Another filtering attempt

If I add “vlan and tcp” as the BPF, I will get results like this:

Now I am only seeing traffic originating from the Pi, and none of the responses from the Web server. 

Correct mixed VLAN tag filtering

In order to handle the mixed VLAN tagging, I need to use a filter like the following:

Note that the following does not work:

I leave it as an exercise for the reader to figure out why. You might want to run each command with the -d option to produce the BPF byte code. For more information, please see these posts:



What did Zeek think?

I should mention that one of the first indications I had that something was wrong with the initial filter occurred when I ran Zeek against it. Here is the conn.log entry for the Web traffic with the incorrect filter:

It is suspicious to see the originator send 0 bytes, 0 packets, and 0 IP bytes. The connection state is also weird. According to the scripting manual, SHR means “Responder sent a SYN ACK followed by a FIN. We never saw a SYN from the originator.”

Here is the conn.log entry for the Web traffic with the correct filter:

Now we have a SSL service identified, non-zero byte and packet counts, and a normal TCP history field. Decoding the history field using the scripting reference, we have the following:

This is a normal connection history for a benign session, as was the case with this traffic.


I don’t know why traffic in one direction has a VLAN tag, while the other direction does not. I will have to research it further.

I hope this article has helped if you encounter similar issues! 

I’d like to thank Christian Rossow for his post on the same syntax:




    Recent Posts