Repository navigation
Add TFPS
TFPS watches the SIP traffic arriving at a machine. It blocks the sources that are attacking it:
- scanners such as SIPVicious,
- password guessers,
- callers trying to run up international fraud.
It blocks them in the kernel with an XDP program, before the packets reach your SIP server.
- It does not sit in the call path.
- It needs nothing from your SIP server, OpenSIPS or Kamailio. Every step below is the same for both.
- It reads a copy of the traffic, the way a capture tool does.
Install it on the machine that receives SIP from the Internet. That is usually the machine running your SIP server.
- This guide does not use sipnab.
- When TFPS is working, Let sipnab see and control TFPS adds sipnab.
Two terms used below:
-
XDP (eXpress Data Path) is a hook in the Linux network driver. There, a
small program can drop a packet before the rest of the kernel sees it.
- TFPS's program drops packets to or from your SIP ports whose source it has blocked.
- It passes everything else, so a blocked address can still reach SSH.
-
BTF (BPF Type Format) is a description of the running kernel's data
structures, at
/sys/kernel/btf/vmlinux.- TFPS compiles its XDP program against it during the install.
- So the kernel needs BTF built in.
Every block on this page, and every command in the table of the "Operate it" step, ran as written, in order, on 2026-09-28.
- The machines were clean x86_64 virtual machines with 2 cores and 3 GB of
memory:
- Debian 13 (kernel 6.12.63),
- Ubuntu 24.04.5 (kernel 6.8.0).
- A second machine played the attacker.
- TFPS came from a build of master at
984577dc. - On Debian 13, causing each fault under
When something does not work produced what it
describes.
- The exception is a kernel without BTF, which none of the machines had. The installer's source prints that message.
The examples use 192.0.2.20 for the TFPS machine. They use 198.51.100.60 for
a second machine that plays the attacker. Replace both with yours.
TFPS needs Linux 5.15 or newer, built with BTF:
# Run all of these, in order.
uname -r
ls -l /sys/kernel/btf/vmlinuxThe file must exist. Debian 11, Ubuntu 22.04 and later distribution kernels have it.
Build TFPS from its master branch, at
984577dc.
- The latest release, v0.2.1, has no
--jsonoption. Let sipnab see and control TFPS needs that option. - Master has it, merged on 2026-09-18. No release carries it yet.
- When one does, install that release instead.
The installer compiles the XDP program on this machine.
- It installs
clangandbpftoolfor that. - It does not install the BPF headers the program includes.
- So the first line installs them, with
git:
# Run all of these, in order.
sudo apt-get install -y git libbpf-dev
sudo git clone https://github.com/sippulse/tfps.git /usr/local/src/tfps
sudo git -C /usr/local/src/tfps checkout 984577dc
cd /usr/local/src/tfps
sudo TMPDIR=/var/tmp sh packaging/install.shWithout libbpf-dev the install stops at fatal error: 'bpf/bpf_helpers.h' file not found.
The build does these things:
- downloads a temporary Rust toolchain,
- compiles,
- installs,
- removes the toolchain again.
It needs about 1.5 GB of free memory.
-
TMPDIR=/var/tmpmatters on Debian 13. There,/tmpis a RAM disk sized to half the memory. - On a 2 GB machine the toolchain does not fit there. The build fails with
No space left on device.
It ends with tfps is running. What it installed:
| Path | What |
|---|---|
/usr/local/bin/tfps |
the daemon |
/usr/local/bin/tfps_ctl |
the command you use to ask it questions and lift blocks |
/usr/local/lib/tfps/tfps_xdp.o |
the XDP program, compiled for this kernel |
/etc/systemd/system/tfps.service |
the unit; the installer replaces it on upgrade |
/etc/tfps/config.json |
settings, written once and never overwritten |
/var/lib/tfps/tfps.db |
what TFPS has learned, and its audit log |
# Run all of these, in order.
systemctl is-active tfps
sudo journalctl -u tfps -n 30
sudo tfps_ctl statusThe journal's startup report names:
- the interface TFPS chose (the one carrying the default route),
- the mode,
PREVENTION, - where the XDP program went, for example
XDP native (DRV) on eth0.
It also lists the addresses it never blocks. Out of the box that is only this machine's own addresses.
Some sources should never lose access:
- a carrier that delivers calls to you,
- a monitoring system,
- your own management network.
List them in ignoreip in /etc/tfps/config.json, as single addresses or
ranges. This adds a management range and one carrier to the settings the
installer wrote:
# Run all of these, in order.
sudo tee /etc/tfps/config.json >/dev/null <<'EOF'
{
"ports": [5060],
"intl_prefixes": ["+", "00", "011", "9011"],
"learn_days": 30,
"block_ttl": 3600,
"stats_every": 60,
"ignoreip": ["192.168.10.0/24", "203.0.113.7"]
}
EOF
sudo systemctl restart tfps
sleep 2
sudo journalctl -u tfps -n 30 | grep -A4 ignoreip | tail -5ports are the SIP ports TFPS watches and blocks on.
- List
5061as well if you take SIP over TLS. - You may have OpenSIPS and Kamailio on one machine, with Kamailio on 5062 as
OpenSIPS and Kamailio on one machine
sets it up. Then list both:
"ports": [5060, 5062]. - TFPS detects attacks only in SIP it can read, which means UDP.
- It cannot read encrypted SIP.
- It does not reassemble SIP over TCP.
- A source it has blocked, though, loses every listed port, TCP and TLS included.
- TFPS's README lists these limits under "What it does not do".
block_ttl is how long a block lasts, in seconds.
Do not list the address of a firewall that forwards SIP to this machine. If the firewall rewrites the source address, fix the forward instead.
- With a plain port forward, TFPS sees each attacker's own address.
- If the firewall replaces it with its own, every attacker looks like the firewall. Trusting the firewall then trusts them all.
From a second machine, send one SIP request the way a scanner does. Use
SIPVicious's friendly-scanner user agent. Then send a second one. This uses
only Python's standard library:
python3 - <<'EOF'
import socket, time, uuid
TARGET = ("192.0.2.20", 5060)
def options(n):
cid = uuid.uuid4().hex
return (f"OPTIONS sip:100@{TARGET[0]} SIP/2.0\r\n"
f"Via: SIP/2.0/UDP 198.51.100.60:5098;branch=z9hG4bK{cid}\r\n"
"Max-Forwards: 70\r\n"
f"From: <sip:100@198.51.100.60>;tag={n}\r\n"
f"To: <sip:100@{TARGET[0]}>\r\n"
f"Call-ID: {cid}\r\nCSeq: 1 OPTIONS\r\n"
"User-Agent: friendly-scanner\r\nContent-Length: 0\r\n\r\n").encode()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(options(1), TARGET)
time.sleep(2)
s.sendto(options(2), TARGET)
EOFThen, on the TFPS machine:
# Run all of these, in order.
sudo tfps_ctl banned --why
sudo tfps_ctl stats | head -6
sudo journalctl -u tfps -n 5 | grep BLOCKEDThe first request got the sender blocked for an hour. The kernel dropped the second before anything else saw it:
SOURCE EXPIRES IN REASON
198.51.100.60 59m user-agent (friendly)
seen on SIP ports : 2
dropped by XDP : 1 (50.0% — gone before sngrep)
The test has to come from a second machine. TFPS never blocks the machine it runs on.
tfps_ctl reads and changes what the running daemon holds. Reading or changing
blocks needs root.
| Command | What it does |
|---|---|
sudo tfps_ctl status |
what is running, what TFPS blocks, how fresh the learned state is |
sudo tfps_ctl stats |
every counter: kernel drops, the traffic mix, what got blocked |
sudo tfps_ctl banned --why |
every blocked source, the time left and the reason |
sudo tfps_ctl unban 198.51.100.60 |
lift a block; --all lifts every one |
sudo tfps_ctl ban 192.0.2.10 --ttl 600 |
block a source by hand for 600 seconds; --ttl 0 is forever |
sudo tfps_ctl log --limit 20 |
the audit log of blocks, newest first |
Blocks live in the kernel.
- They last while
tfpsruns. -
tfps_ctl banandunbanwork only while it is running.
Observe before you enforce. For the first days on a busy machine, you can have TFPS decide without blocking.
- The journal records every decision as
WOULD BLOCKinstead. - Add a drop-in rather than editing the unit. An upgrade replaces the unit.
# Run all of these, in order.
sudo mkdir -p /etc/systemd/system/tfps.service.d
sudo tee /etc/systemd/system/tfps.service.d/observe.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/local/bin/tfps --no-enforce
EOF
sudo systemctl daemon-reload
sudo systemctl restart tfpsIn this mode TFPS attaches no XDP program.
- So
tfps_ctl bannedanswersno loaded eBPF map called `blocked` — is tfps running, and are you root?. There is nothing to list. - The journal holds the decisions, as
WOULD BLOCK peer=... (observe only).
To start blocking, remove the drop-in and restart:
# Run all of these, in order.
sudo rm /etc/systemd/system/tfps.service.d/observe.conf
sudo systemctl daemon-reload
sudo systemctl restart tfps
sudo tfps_ctl statusUpgrade by running the installer again with the newer tarball.
- It replaces the binaries, the XDP program and the unit.
- It keeps
/etc/tfps/config.jsonand/var/lib/tfps.
Uninstall. Stopping the service removes the XDP program, so no block outlives it:
# Run all of these, in order.
sudo systemctl disable --now tfps
sudo rm -f /etc/systemd/system/tfps.service /usr/local/bin/tfps /usr/local/bin/tfps_ctl
sudo rm -rf /usr/local/lib/tfps
sudo systemctl daemon-reload/var/lib/tfps and /etc/tfps stay behind, so that a reinstall keeps what TFPS
learned. Remove them too if you no longer want it:
sudo rm -rf /var/lib/tfps /etc/tfps-
fatal error: 'bpf/bpf_helpers.h' file not found. Installlibbpf-devand run the installer again. -
no /sys/kernel/btf/vmlinux. The kernel has no BTF or is older than 5.15. Use a distribution kernel. -
Nothing is ever blocked.
- Check the startup report's
ignoreiplist and where your SIP arrives from. - A firewall may forward SIP with its own address as the source. Then TFPS
sees one source, and
ignoreipmay already trust it.
- Check the startup report's
-
You blocked yourself. A block covers only the SIP ports, so SSH still
works:
sudo tfps_ctl unban <your address>.
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
- Installation
- First CLI Triage
- Glossary
- Cookbook
- Runnable Examples
- Real World Captures
- Troubleshooting
- Tuning Capture
- Encapsulations
- Rtpengine Relays
- Backers
Using the TUI
CLI & automation
- CLI Reference
- Error Messages
- Filter DSL
- Output Formats
- SIP Response Codes
- SIP Methods
- SIP Header Fields
- SIP Parameters
- SIP Lint Rules
- MOS and Codecs
Configuration
Integrations (API & MCP)
- REST API
- Prometheus Metrics
- Authentication
- MCP
- MCP Deployment
- MCP Across an Estate
- MCP Tools
- MCP Tools Core
- MCP Tools Signaling
- MCP Tools Captures
- MCP Tools Security
- MCP Tools Media
- MCP Tools Relay
- MCP Tools TFPS
- MCP Tools Server
- MCP Tools vCon
- MCP Tools TLS
- MCP Protocol
- Uprobe Walkthrough
- Capture SIP over TLS
- WASM Plugins
- vCon Export
- vCon Capture Stack
- G729 Decoding
Your SIP server
- Use OpenSIPS as the SIP Server
- Run sipnab beside OpenSIPS
- Use Kamailio as the SIP Server
- Run sipnab beside Kamailio
- Use Asterisk as the PBX
- Run sipnab beside Asterisk
- Use FreeSWITCH in the Voice Stack
- Run sipnab beside FreeSWITCH
Add to your voice stack
- Add a vCon Server
- Send vCons to a vCon Server
- Send vCons to vcon store
- Add TFPS
- Control TFPS from sipnab
- Ban SIP Scanners with fail2ban
- Feed fail2ban from sipnab
- Add rtpengine
- Name rtpengine Media with sipnab
- Add rtpproxy
- Name rtpproxy Media with sipnab
- Add Homer
- Connect sipnab to Homer
- Add Prometheus and Grafana
- Add sipnab Metrics to Prometheus
Development & internals
- Internals Index
- Internals Subsystem Guide
- Internals Invariants
- Internals Testing
- Internals Walkthroughs
- Internals Build CI Release
- Internals Profiling
- Internals Domain Primer
- Library API
- Benchmarks
- Fault Model
- Threat Model
- Assurance Case
- Architecture
- Internals Threading
- Internals TUI Testing
- Internals Zero Copy Payloads
- Internals Uprobe Capture
- Internals Rtpengine Control Plane
- Internals Relay Control Decoding
- Internals vCon Exporter
- Internals Operator Guide Notes
- Internals Postmortems
- Internals Code Map
- Internals Lint Rules
- Internals Encapsulation Decoding
- Internals MCP Design Notes
- Internals Metrics And Output Notes