Ask Your Question
0

Capture Filter not working due to incorrect BPF?

asked 2023-02-20 10:26:23 +0000

Srivats gravatar image

updated 2023-02-20 15:48:37 +0000

Hi,

Wireshark 3.6.2 (Ubuntu 22.04.1 LTS) is not able to capture packets with the below filter -

(ether[len - 4:4] == 0x1d10c0da) and not (icmp or (vlan and icmp))

The packets are UDP with VLAN and have the pattern 0x1d10c0da at the end which should match the above capture filter, but they don't.

To investigate, I used dumpcap -d with the above filter

$ dumpcap -c 5 -i enp0s9 -f "(ether[len - 4:4] == 0x1d10c0da) and not (icmp or (vlan and icmp))" -d
Capturing on 'enp0s9'
(000) ld       #0x0
(001) st       M[4]
(002) ld       #pktlen
(003) sub      #4
(004) tax
(005) ld       [x + 0]
(006) st       M[2]
(007) ld       #0x1d10c0da
(008) st       M[3]
(009) ld       M[2]
(010) jeq      #0x1d10c0da      jt 11   jf 32
(011) ldh      [12]
(012) jeq      #0x800           jt 13   jf 15
(013) ldb      [23]
(014) jeq      #0x1             jt 32   jf 15
(015) ldb      [vlanp]
(016) jeq      #0x1             jt 25   jf 17
(017) ld       #0x1d10c0de
(018) st       M[3]
(019) ld       #0x4
(020) st       M[4]
(021) ldh      [12]
(022) jeq      #0x8100          jt 25   jf 23
(023) jeq      #0x88a8          jt 25   jf 24
(024) jeq      #0x9100          jt 25   jf 31
(025) ldx      M[4]
(026) ldh      [x + 12]
(027) jeq      #0x800           jt 28   jf 31
(028) ldx      M[3]
(029) ldb      [x + 23]
(030) jeq      #0x1             jt 32   jf 31
(031) ret      #262144
(032) ret      #0

All seems ok till we come post the vlanp (vlan present) check.

If I'm reading the instructions correctly, I think the problem is (017), (018) which stores 0x1d10c0de intoM[3] which is accessed by (028), (029).

Instruction (028) seems incorrect to me as (029) expects x to be 4 similar to (026).

tcpdump -d also generates the same bpf instructions. Trying --no-optimize with tcpdump has a similar error in the unoptimized code.

However, the Wireshark Capture Options | Compile BPFs seems to generate the correct BPF instructions -

(000) ld       #pktlen
(001) sub      #4
(002) tax      
(003) ld       [x + 0]
(004) jeq      #0x1d10c0da      jt 5    jf 17
(005) ldh      [12]
(006) jeq      #0x800           jt 7    jf 9
(007) ldb      [23]
(008) jeq      #0x1             jt 17   jf 16
(009) jeq      #0x8100          jt 12   jf 10
(010) jeq      #0x88a8          jt 12   jf 11
(011) jeq      #0x9100          jt 12   jf 16
(012) ldh      [16]
(013) jeq      #0x800           jt 14   jf 16
(014) ldb      [27]
(015) jeq      #0x1             jt 17   jf 16
(016) ret      #262144
(017) ret      #0

Does Wireshark Compile BPFs use a different BPF compiler than dumpcap? Since the instructions generated by dumpcap is same as tcpdump, I assume both of them use the libpcap pcap_compile()?

dumpcap version

$ dumpcap --version
Dumpcap (Wireshark) 3.6.2 (Git v3.6.2 packaged as 3.6.2-2)

Copyright 1998-2022 Gerald Combs <[email protected]> and contributors.
License GPLv2+: GNU GPL version 2 or later <https://www.gnu.org/licenses/gpl-2.0.html>
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or ...
(more)
edit retag flag offensive close merge delete

Comments

In Wireshark, did you select the same interface enp0s9 ?

Jaap gravatar imageJaap ( 2023-02-20 12:32:22 +0000 )edit

@Jaap - yes, same interface enp0s9 on Wireshark as well. If I remove the or (vlan and icmp) from the filter, packets start matching -- I'm wondering if this is something related to libpcap VLAN handling on linux specifically?

Srivats gravatar imageSrivats ( 2023-02-20 12:39:26 +0000 )edit

Can you update the question with the output of dumpcap -v?
There was a similar question here: 16116: when using vlan capturing filter priority frames dropped

Chuckc gravatar imageChuckc ( 2023-02-20 15:24:16 +0000 )edit

@Chuckc I've updated dumpcap and tcpdump versions. However, I'm not sure if it's related to the issue you mentioned.

Srivats gravatar imageSrivats ( 2023-02-20 15:50:21 +0000 )edit
1

There are two compile paths in tcpdump.c - with or without an interface specified.

root@ubuntu2204:/usr/bin# tcpdump -d -f vlan -i 1
(000) ldb      [vlanp]
(001) jeq      #0x1             jt 6    jf 2
(002) ldh      [12]
(003) jeq      #0x8100          jt 6    jf 4
(004) jeq      #0x88a8          jt 6    jf 5
(005) jeq      #0x9100          jt 6    jf 7
(006) ret      #262144
(007) ret      #0
root@ubuntu2204:/usr/bin# tcpdump -d -f vlan
Warning: assuming Ethernet
(000) ldh      [12]
(001) jeq      #0x8100          jt 4    jf 2
(002) jeq      #0x88a8          jt 4    jf 3
(003) jeq      #0x9100          jt 4    jf 5
(004) ret      #262144
(005) ret      #0
Chuckc gravatar imageChuckc ( 2023-02-21 17:15:50 +0000 )edit

4 Answers

Sort by ยป oldest newest most voted
0

answered 2023-02-21 01:13:15 +0000

Chuckc gravatar image

updated 2023-02-21 17:04:57 +0000

Wireshark and dumpcap both call `pcap_compile(). I don't have an answer why the output is different.
Update: see the 2nd answer for path through the two compiles.

Have you tried reversing the filter logic?

$ dumpcap -c 5 -i 1 -f "not (icmp or (vlan and icmp)) and (ether[len - 4:4] == 0x1d10c0da)" -d
Capturing on 'ens160'
(000) ld       #0x0
(001) st       M[0]
(002) st       M[1]
(003) ldh      [12]
(004) jeq      #0x800           jt 5    jf 7
(005) ldb      [23]
(006) jeq      #0x1             jt 28   jf 7
(007) ldb      [vlanp]
(008) jeq      #0x1             jt 16   jf 9
(009) ld       #0x4
(010) st       M[0]
(011) st       M[1]
(012) ldh      [12]
(013) jeq      #0x8100          jt 16   jf 14
(014) jeq      #0x88a8          jt 16   jf 15
(015) jeq      #0x9100          jt 16   jf 22
(016) ldx      M[1]
(017) ldh      [x + 12]
(018) jeq      #0x800           jt 19   jf 22
(019) ldx      M[0]
(020) ldb      [x + 23]
(021) jeq      #0x1             jt 28   jf 22
(022) ld       #pktlen
(023) sub      #4
(024) tax      
(025) ld       [x + 0]
(026) jeq      #0x1d10c0da      jt 27   jf 28
(027) ret      #262144
(028) ret      #0
edit flag offensive delete link more

Comments

Yes, reversing the filter works. The filter is actually part of an application and was written in the form it was to be more performant. However, I guess correctness is more important than performance!

However, would you agree that this looks like a BPF code generation bug in libpcap? If so, I'll raise a bug against libpcap on github.

Srivats gravatar imageSrivats ( 2023-02-21 08:55:27 +0000 )edit

(adding the information in the 2nd answer as a comment here blew up and no patience to debug the Ask site today)

Chuckc gravatar imageChuckc ( 2023-02-21 17:02:40 +0000 )edit

It certainly does look like one (or more) BPF code generation bugs in libpcap to me. I can reproduce the problem on a RHEL 8.2 (Ootpa) machine with libpcap version 1.9.1 (with TPACKET_V3); however, on an older RHEL 7.4 (Maipo) machine with libpcap version 1.5.3, the BPF code generated looks much more sane.

Out of curiosity, were the Wireshark-generated BPF instructions generated on the same system as the dumpcap-generated BPF instructions? Because they look to me like what you'd get on a Windows system, not a Linux system, as in those are the exact same instructions I get using dumpcap on my Windows PC.

cmaynard gravatar imagecmaynard ( 2023-02-21 23:00:51 +0000 )edit

I haven't looked at npcap to see what happens in with dumpcap.
Wireshark will call pcap_open_dead() on all platforms.

root@ubuntu2204:/usr/bin# uname -a
Linux ubuntu2204 5.15.0-53-generic #59-Ubuntu SMP Mon Oct 17 18:53:30 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

root@ubuntu2204:/usr/bin# grep -i version /etc/os-release 
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy

Chuckc gravatar imageChuckc ( 2023-02-21 23:41:40 +0000 )edit

@cmaynard - yes, the Wireshark generated BPF instructions were taken on the same system as dumpcap - an Ubuntu 22.04. The difference between them is possibly because of the 2 code paths that @Chuckc has identified.

@Chuckc - dumpcap (using npcap underneath) generates correct BPF instructions. That's why I feel this is related to the Linux-only vlanp BPF helper code gen path

Srivats gravatar imageSrivats ( 2023-02-22 05:28:34 +0000 )edit
0

answered 2023-03-01 20:29:32 +0000

cmaynard gravatar image

If you're looking for an alternate work-around solution that is nearly identical to the correctly-generated BPF instructions from the original capture filter expression of (ether[len - 4:4] == 0x1d10c0da) and not (icmp or (vlan and icmp)), then you might try this filter:

(ether[len - 4:4] = 0x1d10c0da) and not (icmp or ((ether[12:2] = 0x8100 or ether[12:2] = 0x88a8 or ether[12:2] = 0x9100) and (ether[16:2] = 0x0800 and ether[27:1] = 1)))

This basically handles vlan the old-fashioned hard way, but a way that works regardless of the libpcap version. The generated BPF is as follows, which adds only 1 extra instruction to reload the Ethertype (i.e., the 2-byte value at ether[12:2]), since there's an implicit loading of that value when comparing the first icmp expression, as it first ensures that the Ethertype is IP before looking at the value of the IP header's Protocol field.

(000) ld       #pktlen
(001) sub      #4
(002) tax
(003) ld       [x + 0]
(004) jeq      #0x1d10c0da      jt 5    jf 18
(005) ldh      [12]
(006) jeq      #0x800           jt 7    jf 9
(007) ldb      [23]
(008) jeq      #0x1             jt 18   jf 9
(009) ldh      [12]
(010) jeq      #0x8100          jt 13   jf 11
(011) jeq      #0x88a8          jt 13   jf 12
(012) jeq      #0x9100          jt 13   jf 17
(013) ldh      [16]
(014) jeq      #0x800           jt 15   jf 17
(015) ldb      [27]
(016) jeq      #0x1             jt 18   jf 17
(017) ret      #262144
(018) ret      #0

Now if you're looking for an alternate work-around solution that produces identical correctly-generated BPF instructions, then you could try this filter:

(ether[len - 4:4] = 0x1d10c0da) and not ((ether[12:2] = 0x0800 and ether[23:1] = 1) or ((ether[12:2] = 0x8100 or ether[12:2] = 0x88a8 or ether[12:2] = 0x9100) and (ether[16:2] = 0x0800 and ether[27:1] = 1)))

This one explicitly loads the Ethertype to ensure it is IP before comparing the IP Protocol to ICMP, but in doing so allows the optimizer to only load that value once. Now we get the exact same output as bpfexam produces and which is 1 instruction fewer than the previous attempt:

(000) ld       #pktlen
(001) sub      #4
(002) tax
(003) ld       [x + 0]
(004) jeq      #0x1d10c0da      jt 5    jf 17
(005) ldh      [12]
(006) jeq      #0x800           jt 7    jf 9
(007) ldb      [23]
(008) jeq      #0x1             jt 17   jf 16
(009) jeq      #0x8100          jt 12   jf 10
(010) jeq      #0x88a8          jt 12   jf 11
(011) jeq      #0x9100          jt 12   jf 16
(012) ldh      [16]
(013) jeq      #0x800           jt 14   jf 16
(014) ldb      [27]
(015) jeq      #0x1             jt 17   jf 16
(016) ret      #262144
(017) ret      #0
edit flag offensive delete link more

Comments

Thanks Chris! This is great. Two curiosity questions - 1. is there a "runtime" way to tell libpcap NOT to use bpf extensions? 2. can we provide libpcap manually compiled BPF instructions rather than using pcap_compile()?

Srivats gravatar imageSrivats ( 2023-03-02 05:11:31 +0000 )edit

Both good questions! Unfortunately, I don't have answers to them.

And my apologies - while I did test on the RHEL8 machine with libpcap 1.9.1, I now realize that due to the BPF extensions problem and what I assume means that the VLAN tags are stripped off, my proposed solution won't help. So the best you may be able to do then is either use @Chuckc's solution for reversing the filter logic, or your own solution of using an older version of libpcap like 1.6.2 that doesn't exhibit this problem.

cmaynard gravatar imagecmaynard ( 2023-03-02 18:19:08 +0000 )edit

From the pcap_setfilter(3PCAP) man page:

pcap_setfilter is used to specify a filter program. fp is a pointer to a bpf_program struct, usually the result of a call to pcap_compile(3PCAP).

"usually the result of a call to pcap_compile()" ... but you could in theory generate your own bpf_program instead. I don't know if that would help you?

cmaynard gravatar imagecmaynard ( 2023-03-02 18:44:52 +0000 )edit

OK, my head is starting to hurt, but ... why wouldn't my proposed work-around solution work? If the VLAN tags are stripped away, then the packet will be rejected whether [vlanp] is true or not since there's already a comparison for ICMP under the assumption that the Ethertype is IP, correct? So is there even a need to check that case explicitly? As far as I can tell, there isn't. You want to reject all non-VLAN ICMP packets and that will happen. The remainder of the filter handles the ICMP case when the VLAN tag is not stripped away. Maybe I'm missing something?

cmaynard gravatar imagecmaynard ( 2023-03-02 20:19:03 +0000 )edit

And to illustrate the difference, suppose you only wanted to discard ICMP packets if present in VLAN-tagged packets but accept ICMP packets present in non-VLAN-tagged packets. Well, in that case, you would need a different filter, one that checked if the packet was VLAN-tagged whether the tag was stripped away or not. In other words, this capture filter: (ether[len - 4:4] == 0x1d10c0da) and not (vlan and icmp).

But if you attempted to use that with a libpcap version 1.7.0 or higher, then it would be just as broken, so you'd have to use something like this instead: (ether[len - 4:4] = 0x1d10c0da) and not ((ether[-4048:1] = 1 and icmp) or ((ether[12:2] = 0x8100 or ether[12:2] = 0x88a8 or ether[12:2] = 0x9100) and (ether[16:2] = 0x0800 and ether[27:1] = 1))).

This of course assumes that the -4048 "offset" is standardized for ...(more)

cmaynard gravatar imagecmaynard ( 2023-03-02 20:53:13 +0000 )edit
0

answered 2023-02-22 01:32:43 +0000

Chuckc gravatar image

the-tcpdump-group issue for the libpcap compiler output:
Capture Filter not working due to incorrect BPF? #1162

edit flag offensive delete link more

Comments

Chuckc gravatar imageChuckc ( 2023-02-22 03:09:29 +0000 )edit

@Chuckc - thanks for updating the link to the libpcap issue here - I forgot to do that. I've seen that FAQ entry, but I felt it should not apply to this particular case. Also, this seems to be a regression as the same expression generates correct BPF with an older libpcap version (1.5.3) as identified by @cmaynard

Srivats gravatar imageSrivats ( 2023-02-22 05:31:49 +0000 )edit
0

answered 2023-02-21 17:01:42 +0000

Chuckc gravatar image

Yes, but in this case you get two issues for the price of one. :-)
I think there is a problem with the bpf generated by libpcap (at least 1.10.1).
And maybe wireshark should open the interface so that compiling the capture filter is more accurate.

I was hoping to test outside of Wireshark and dumpcap using the libpcap test program - filtertest - but was unable to recreate the dumpcap output.

filtertest and wireshark call pcap_open_dead() before compiling the capture filter.
filtertest.c:

pd = pcap_open_dead(dlt, snaplen);
if (pd == NULL)
    error("Can't open fake pcap_t");

if (pcap_compile(pd, &fcode, cmdbuf, Oflag, netmask) < 0)
    error("%s", pcap_geterr(pd));


wireshark - compiled_filter_output.cpp:

            pcap_t *pd = pcap_open_dead(device->active_dlt, WTAP_MAX_PACKET_SIZE_STANDARD);
            if (pd == NULL)
                break;
            g_mutex_lock(pcap_compile_mtx);
            if (pcap_compile(pd, &fcode, compile_filter_.toUtf8().data(), 1, 0) < 0) {
                compile_results.insert(interfaces, QString(pcap_geterr(pd)));


dumpcap opens the interface before compiling the capture filter. dumpcap.c:

    pcap_h = open_capture_device(capture_opts, interface_opts,
        CAP_READ_TIMEOUT, &open_status, &open_status_str);
...
    if (!compile_capture_filter(interface_opts->name, pcap_h, &fcode,
                                interface_opts->cfilter)) {
...
compile_capture_filter(const char *iface, pcap_t *pcap_h,
                   struct bpf_program *fcode, const char *cfilter)
{
...
    if (pcap_compile(pcap_h, fcode, (char *)cfilter, 1, netmask) < 0)


admin1@ubuntu2204:/usr/bin$ dumpcap --log-level debug -i 1 -f vlan -d --log-file /tmp/foo.out
Capturing on 'ens160'
(000) ldb      [vlanp]
(001) jeq      #0x1             jt 6    jf 2
(002) ldh      [12]
(003) jeq      #0x8100          jt 6    jf 4
(004) jeq      #0x88a8          jt 6    jf 5
(005) jeq      #0x9100          jt 6    jf 7
(006) ret      #262144
(007) ret      #0
admin1@ubuntu2204:/usr/bin$ cat /tmp/foo.out
 ** (dumpcap:1905481) 10:39:29.522753 [Capchild DEBUG] ./capture/capture-pcap-util.c:1578 -- open_capture_device(): Entering open_capture_device().
 ** (dumpcap:1905481) 10:39:29.522913 [Capchild DEBUG] ./capture/capture-pcap-util.c:1278 -- open_capture_device_pcap_create(): Calling pcap_create() using ens160.
 ** (dumpcap:1905481) 10:39:29.523013 [Capchild DEBUG] ./capture/capture-pcap-util.c:1280 -- open_capture_device_pcap_create(): pcap_create() returned 0x562799ce8770.
 ** (dumpcap:1905481) 10:39:29.523057 [Capchild DEBUG] ./capture/capture-pcap-util.c:1290 -- open_capture_device_pcap_create(): Calling pcap_set_promisc() with promisc_mode 1.
 ** (dumpcap:1905481) 10:39:29.523096 [Capchild DEBUG] ./capture/capture-pcap-util.c:1336 -- open_capture_device_pcap_create(): buffersize 2.
 ** (dumpcap:1905481) 10:39:29.523134 [Capchild DEBUG] ./capture/capture-pcap-util.c:1340 -- open_capture_device_pcap_create(): monitor_mode 0.
 ** (dumpcap:1905481) 10:39:29.541767 [Capchild DEBUG] ./capture/capture-pcap-util.c:1344 -- open_capture_device_pcap_create(): pcap_activate() returned 0.
 ** (dumpcap:1905481) 10:39:29.541841 [Capchild DEBUG] ./capture/capture-pcap-util.c:1649 -- open_capture_device(): open_capture_device SUCCESS : ens160


The linux code for libpcap does a check for vlan when opening the socket and sets a flag for the bpf code generator:
pcap-linux.c:

#if defined(SO_BPF_EXTENSIONS) && defined(SKF_AD_VLAN_TAG_PRESENT)
    /*
     * Can we generate special code for VLAN checks?
     * (XXX - what if we need the special code but it's not supported
     * by the OS?  Is that possible?)
     */
    if (getsockopt(sock_fd, SOL_SOCKET, SO_BPF_EXTENSIONS,
        &bpf_extensions, &len) == 0) {
        if (bpf_extensions >= SKF_AD_VLAN_TAG_PRESENT) {
            /*
             * Yes, we can.  Request that we do so.
             */
            handle->bpf_codegen_flags |= BPF_SPECIAL_VLAN_HANDLING;
        }
    }
#endif /* defined(SO_BPF_EXTENSIONS) && defined(SKF_AD_VLAN_TAG_PRESENT) */

gencode.c:

             * Do we need special VLAN handling?
             */
            if (cstate->bpf_pcap->bpf_codegen_flags & BPF_SPECIAL_VLAN_HANDLING)
                b0 = gen_vlan_bpf_extensions(cstate, vlan_num,
                    has_vlan_tag);
            else
                b0 = gen_vlan_no_bpf_extensions(cstate,
                    vlan_num, has_vlan_tag);
edit flag offensive delete link more

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

1 follower

Stats

Asked: 2023-02-20 10:26:23 +0000

Seen: 655 times

Last updated: Mar 01 '23