iovisor / bcc

BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

attach_raw_socket function does not work well on Qualcomm modem

fourcolor opened this issue · comments

I use eBPF to capture packets, and I found that it works fine on regular network cards, but it doesn't function properly when I use it on a Qualcomm modem.
I expect my code to execute as follows:

$ sudo python3 udp_sniffer.py -i enp43s0 -p 26425,26426
timestamp,saddr,daddr,sport,dport,sequence,epoch,microseconds,direction
2024-05-06 12:56:35.059705,140.112.20.182,140.112.20.183,47003,26425,10394,1714971395,59455,0,
2024-05-06 12:56:35.060625,140.112.20.183,140.112.20.182,26426,34069,10396,1714971395,23433,2,
2024-05-06 12:56:35.061726,140.112.20.182,140.112.20.183,47003,26425,10395,1714971395,61455,0,
2024-05-06 12:56:35.062661,140.112.20.183,140.112.20.182,26426,34069,10397,1714971395,25434,2,
2024-05-06 12:56:35.063719,140.112.20.182,140.112.20.183,47003,26425,10396,1714971395,63455,0,
2024-05-06 12:56:35.064900,140.112.20.183,140.112.20.182,26426,34069,10398,1714971395,27434,2,
2024-05-06 12:56:35.065643,140.112.20.182,140.112.20.183,47003,26425,10397,1714971395,65455,0,

When I use the Qualcomm modem as a network interface, however, nothing is displayed.

$ sudo python3 udp_sniffer.py -i qc01 -p 26425,26426
timestamp,saddr,daddr,sport,dport,sequence,epoch,microseconds,direction

And I have used ss --bpf --packet -p to confirm, and the Recv-Q of qc01 has data.

$ ss --bpf --packet -p
Netid             Recv-Q              Send-Q                           Local Address:Port                              Peer Address:Port             Process
        bpf filter (0):              
p_raw             213504              0                                            *:qc01                                          *                 
        bpf filter (0):              

Below is a snippet of the eBPF code I executed

#!/usr/bin/python

from bcc import BPF
import time
import sys
import argparse

PACKET_SIZE = 250

parser = argparse.ArgumentParser()
parser.add_argument("-i", "--device", help="net_interface")
parser.add_argument("-p", "--ports",
    help="comma-separated list of ports to trace.")
parser.add_argument("-w", "--file", help="output file")
args = parser.parse_args()

bpf_text = """

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
#include <linux/bpf.h>

#define IP_TCP 6
#define IP_UDP 17
#define IP_ICMP 1
#define ETH_HLEN 14

BPF_PERF_OUTPUT(skb_events);
 
struct perf_data {
    int len;
    int dir;
};

int packet_monitor(struct __sk_buff *skb) {
    u8 *cursor = 0;
    u32 saddr, daddr;
    u32 sport, dport;
    u32 udp_header_length = 0;
    u32 ip_header_length = 0;
    u32 payload_offset = 0;
    u32 payload_length = 0;
    
    struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
    struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));

    if (ip->ver != 4)
        goto KEEP;

    ip_header_length = ip->hlen << 2;  // SHL 2 -> *4 multiply

    /* check ip header length against minimum */
    if (ip_header_length < sizeof(*ip))
        goto KEEP;
        
    if (ip->nextp == IP_TCP) 
        goto KEEP;
    if (ip->nextp == IP_ICMP) 
        goto KEEP;
        
    if (ip -> nextp == IP_UDP){
        struct udp_t *udp = cursor_advance(cursor, sizeof(*udp));
        payload_offset = ETH_HLEN + ip_header_length + 8;
        payload_length = ip->tlen - ip_header_length - 8;
        dport = udp->dport;
        sport = udp->sport;
        saddr = ip -> src;
        daddr = ip -> dst;
        int len = payload_length;
        FILTER_PORT
        struct perf_data data = {.len = len, .dir = skb->ingress_ifindex};
        skb_events.perf_submit_skb(skb, skb->len, &data, sizeof(data));
    }
    
/* keep the packet and send it to userspace returning -1 */
KEEP:
    return -1;

/* drop the packet returning 0 */
DROP:
    return 0;
}

"""

from ctypes import *
import ctypes as ct
import sys
import socket
import os
import struct
import ipaddress
import ctypes
from datetime import datetime
from struct import unpack

out = sys.stdout
if args.file:
    out = open(args.file,"w+")
if args.ports:
    dports = [int(port) for port in args.ports.split(',')]
    dports_if = ' && '.join([f'dport != {port} && sport != {port}' for port in dports])
    bpf_text = bpf_text.replace('FILTER_PORT',
        'if (%s) { goto KEEP; }' % dports_if)
else:
    bpf_text = bpf_text.replace('FILTER_PORT',"")

bpf = BPF(text=bpf_text)

function_skb_matching = bpf.load_func("packet_monitor", BPF.SOCKET_FILTER)

BPF.attach_raw_socket(function_skb_matching, args.device)

def payload_info(cpu, data, size):
    class SkbEvent(ct.Structure):
        _fields_ = [
            ("len", ct.c_uint32),
            ("dir", ct.c_uint32),
            ("raw", ct.c_ubyte * (size - 2*ct.sizeof(ct.c_uint32))),
        ]
    try:
            ......

            print(f"{ts},{saddr},{daddr},{sport},{dport},{seq},{epoch},{microseconds},{dir},",file=out,flush=True)
    except ValueError:
        return "Invalid input"
try:
    bpf["skb_events"].open_perf_buffer(payload_info)
    print("timestamp,saddr,daddr,sport,dport,sequence,epoch,microseconds,direction",file=out,flush=True)
    while True :
        bpf.perf_buffer_poll()
        
except KeyboardInterrupt:
    sys.stdout.close()
    pass

I found that the device I'm using operates on Raw IP level sockets, so parsing should start from the IP header. This issue can be closed now.