USDT Probes
nDPI supports USDT (User-level Statically Defined Tracing)
probes for zero-overhead dynamic tracing in production. USDT probes compile to a single NOP
instruction and have no runtime cost when not actively being traced. External tools like
bpftrace, perf, and SystemTap can attach to these probes at runtime without restarting
the application.
Building with USDT Support
Install the required header (Linux):
# Debian/Ubuntu
sudo apt-get install systemtap-sdt-dev
# RHEL/CentOS/Fedora
sudo dnf install systemtap-sdt-devel
Then configure nDPI with USDT enabled:
./autogen.sh
./configure --enable-usdt-probes
make
Note
On macOS, sys/sdt.h is provided by the system. On platforms where it is
unavailable, the probes compile to no-ops and have zero impact.
Available Probes
Probe Name |
Arguments |
Description |
|---|---|---|
|
arg0: master protocol ID (u16)arg1: application protocol ID (u16)arg2: confidence level (enum)arg3: category (enum) |
Fires exactly once per flow when classification is finalized. Covers all exit paths: successful detection, giveup, max-packets, nBPF match, and extra-dissector completion. |
|
arg0: hostname string (char *)arg1: master protocol ID (u16)arg2: application protocol ID (u16) |
Fires when a hostname/SNI is extracted from a flow. Covers all protocols that resolve hostnames: TLS (SNI), DNS, HTTP (Host header), QUIC, NetBIOS, DHCP, STUN, and others. |
bpftrace Examples
List available probes:
bpftrace -l "usdt:./src/lib/.libs/libndpi.so:ndpi:*"
flow_classified Examples
Real-time protocol classification log:
bpftrace -e 'usdt::ndpi:flow_classified {
printf("master=%d app=%d confidence=%d category=%d\n",
arg0, arg1, arg2, arg3);
}'
Protocol distribution histogram:
bpftrace -e 'usdt::ndpi:flow_classified {
@proto_master[arg0] = count();
}'
Confidence level breakdown:
bpftrace -e 'usdt::ndpi:flow_classified {
@confidence[arg2] = count();
}'
Category distribution:
bpftrace -e 'usdt::ndpi:flow_classified {
@category[arg3] = count();
}'
Count unknown/unclassified flows:
bpftrace -e 'usdt::ndpi:flow_classified /arg0 == 0/ {
@unknown = count();
}'
Flow classification rate (flows/sec):
bpftrace -e 'usdt::ndpi:flow_classified {
@ = count();
} interval:s:1 { print(@); clear(@); }'
Filter by specific protocol (e.g., TLS = 91):
bpftrace -e 'usdt::ndpi:flow_classified /arg0 == 91/ {
@tls[arg1] = count();
}'
Flows classified as SocialNetwork (category 6):
bpftrace -e 'usdt::ndpi:flow_classified /arg3 == 6/ {
@social[arg0, arg1] = count();
}'
hostname_set Examples
Real-time hostname log:
bpftrace -e 'usdt::ndpi:hostname_set {
printf("%s (master=%d app=%d)\n", str(arg0), arg1, arg2);
}'
Top hostnames by flow count:
bpftrace -e 'usdt::ndpi:hostname_set {
@top[str(arg0)] = count();
}'
Monitor a specific domain (e.g., all *.google.com traffic):
bpftrace -e 'usdt::ndpi:hostname_set /strcontains(str(arg0), "google.com")/ {
@google[str(arg0)] = count();
}'
Hostnames resolved via DNS only (DNS = 5):
bpftrace -e 'usdt::ndpi:hostname_set /arg1 == 5/ {
@dns[str(arg0)] = count();
}'
TLS SNI extraction in real time (TLS = 91):
bpftrace -e 'usdt::ndpi:hostname_set /arg1 == 91/ {
printf("TLS SNI: %s\n", str(arg0));
}'
Hostnames with their application protocol breakdown:
bpftrace -e 'usdt::ndpi:hostname_set {
@host_app[str(arg0), arg2] = count();
}'
Hostname resolution rate (hostnames/sec):
bpftrace -e 'usdt::ndpi:hostname_set {
@ = count();
} interval:s:1 { print(@); clear(@); }'
Detect potential DGA activity (short hostnames with many unique values):
bpftrace -e 'usdt::ndpi:hostname_set /arg1 == 5/ {
@unique_dns = count();
} interval:s:10 {
printf("Unique DNS hostnames in last 10s: %d\n", @unique_dns);
clear(@unique_dns);
}'
Correlate hostnames with protocol classification (combine both probes):
bpftrace -e '
usdt::ndpi:hostname_set { @host[tid] = str(arg0); }
usdt::ndpi:flow_classified /@host[tid] != ""/ {
printf("host=%s master=%d app=%d conf=%d cat=%d\n",
@host[tid], arg0, arg1, arg2, arg3);
delete(@host[tid]);
}'
perf Example
Record probe hits with perf:
perf probe -x ./src/lib/.libs/libndpi.so sdt_ndpi:flow_classified
perf record -e sdt_ndpi:flow_classified -p $(pidof ndpiReader) -- sleep 10
perf report
Overhead
When not tracing: zero overhead. Probes compile to a single NOP instruction.
When actively tracing: approximately 2-5 microseconds per probe hit, depending on the tracing tool and the complexity of the attached script.
Both probes fire once per flow (not per packet), so even under active tracing the overhead is negligible for typical traffic volumes.