Skip to content

Add TFPS

github-actions[bot] edited this page Oct 16, 2026 · 1 revision

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.

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.

Tested on

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.

Check the kernel

TFPS needs Linux 5.15 or newer, built with BTF:

# Run all of these, in order.
uname -r
ls -l /sys/kernel/btf/vmlinux

The file must exist. Debian 11, Ubuntu 22.04 and later distribution kernels have it.

Install TFPS

Build TFPS from its master branch, at 984577dc.

  • The latest release, v0.2.1, has no --json option. 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 clang and bpftool for 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.sh

Without 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/tmp matters on Debian 13. There, /tmp is 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

Check that it is enforcing

# Run all of these, in order.
systemctl is-active tfps
sudo journalctl -u tfps -n 30
sudo tfps_ctl status

The 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.

Tell it which sources to trust

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 -5

ports are the SIP ports TFPS watches and blocks on.

  • List 5061 as 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.

Watch it block an attacker

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)
EOF

Then, 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 BLOCKED

The 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.

Operate it

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 tfps runs.
  • tfps_ctl ban and unban work 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 BLOCK instead.
  • 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 tfps

In this mode TFPS attaches no XDP program.

  • So tfps_ctl banned answers no 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 status

Upgrade by running the installer again with the newer tarball.

  • It replaces the binaries, the XDP program and the unit.
  • It keeps /etc/tfps/config.json and /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

When something does not work

  • fatal error: 'bpf/bpf_helpers.h' file not found. Install libbpf-dev and 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 ignoreip list and where your SIP arrives from.
    • A firewall may forward SIP with its own address as the source. Then TFPS sees one source, and ignoreip may already trust it.
  • You blocked yourself. A block covers only the SIP ports, so SSH still works: sudo tfps_ctl unban <your address>.

Home

Getting started

Using the TUI

CLI & automation

Configuration

Integrations (API & MCP)

Your SIP server

Add to your voice stack

Development & internals

Clone this wiki locally