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.