nftable-router
Software Policy Router for nftables — GeoIP/domain based policy routing via NFQUEUE, with a built-in web admin, transparent-proxy process supervision, PowerDNS Recursor management and SNMP switch ARP/MAC collectors.
Install
Requirements
| Python | >= 3.8 (3.13 tested; see note on pysnmp below) |
| Kernel | nftables + NFQUEUE (nfnetlink_queue) |
| Services | redis (the UI stream, ARP/MAC cache and cross-process state board) |
| Build deps | build-essential, libnetfilter-queue-dev (for NetfilterQueue) |
One dependency cannot come from pip: the nftables Python binding ships
with the nftables project itself, not PyPI (the PyPI name nftables is an
unrelated project — do not install it).
apt install python3-nftables build-essential libnetfilter-queue-dev
Install the package
pip install .
# or from a built wheel
pip install dist/nftable_router-*.whl
If you install into a venv created without --system-site-packages, link
the distro-provided binding in, otherwise import nftables fails:
ln -s /usr/lib/python3/dist-packages/nftables \
/path/to/venv/lib/python3.*/site-packages/
nft-router checks this at startup and prints the exact command if missing.
Console scripts
| command | what it runs |
|---|---|
nft-router |
the router itself (reads nft_route.json from CWD, or $NFT_ROUTE_CONFIG) |
nft-router-webadmin |
the web admin standalone (normally supervised by the router) |
nft-router-arp |
one switch's SNMP collector (normally supervised by the router) |
A note on ipdb
The GeoIP reader imported as ipdb comes from ipip-ipdb-hp (IPIP.net
database format, C extension). Do not pip install ipdb — that is the
IPython debugger and will shadow it with a package that has no City class.
Requires >= 0.1.2. Earlier versions do not compile on GCC 14+ (Debian
trixie, Ubuntu 24.04+), which turned -Wincompatible-pointer-types and
-Wimplicit-function-declaration into errors.
The pure-Python ipip-ipdb exposes the same City / find_map / is_ipv6
API and works as a fallback if the extension cannot be built — but it is
~11x slower (40k vs 450k lookups/s, measured against a 175 MB database),
and this lookup runs for every new connection, so prefer the C reader.
A note on pysnmp
The switch collectors need pysnmp, and there are two incompatible generations in the wild:
- pysnmp >= 7 (lextudio, actively maintained, asyncio API) — required on Python >= 3.12.
- pysnmp 4.4.x (2019, unmaintained, sync API) — imports
asyncore, which was removed in Python 3.12, so it only works on older interpreters.
arp_snmp.py speaks both and picks automatically, and the dependency
markers install the right one for the running interpreter. The collectors are
separate child processes, so they can run under a different interpreter
than the router via switches.python in the config — useful when only that
interpreter has a working pysnmp.
Replaced dependencies
Two long-unmaintained packages were dropped in favour of compat.py:
| was | last release | now |
|---|---|---|
netifaces |
2021-05 | psutil (already required); only interfaces() was used |
python-prctl |
2020-11, needs libcap headers | setproctitle + a ctypes prctl(PR_SET_NAME) call |
pytput |
2020-05, imports the removed pkg_resources |
compat.TputFormatter, same {x:spec,style} syntax |
pytput is not merely stale — it raises ModuleNotFoundError on import
under a Python 3.13 venv, since setuptools 81 dropped pkg_resources and
new venvs do not ship setuptools at all.
Running
cd /etc/network # wherever nft_route.json lives
nft-router # needs root: nftables, NFQUEUE, SO_MARK
The web admin starts automatically as a supervised child (see the
webadmin config section) — by default on http://127.0.0.1:8788.
Reload after editing config: kill -USR1 $(cat /run/nft_route.pid), or the
「重载主进程」button on the UI's 状态 page.
Tests
Offline, no root / kernel / switches / redis needed:
cd nftable_router
for t in test_*.py; do python3 "$t"; done
Icon Means
Status ICON
ALIVE:
🟩 - Global Lock Idle
🔴 - Process Dead
🟡 - Process Busying
🟩 - Process Idle > 30s
🟢 - Process Idle
Proxy Test Status:
⚫ for Line
⬛ for Proxy
⚫ - N/A
🔴 - Failed
🟢 - <= 100ms
🔵 - <= 200ms
🟣 - <= 400ms
🟡 - <= 600ms
🟠 - <= 800ms
🟤 - > 800ms
Config.json
ipdb_v4- Path for IPDB IPv4ipdb_v6- Path for IPDB IPv6nat_interfaces- Interface for internal network (from this interfaces will be nat)tunnel_ip- Tunnel IP, would be ignore to software routerallow_ecmp- Allow Equal Cost multi-path CIDR (TODO)allow_ecmp_port- Allow Equal Cost multi-path Ports (TODO)ignore_print_domain- No output for Print domainignore_list- Ignore source CIDR for software router (such as internal router)proxy- Line Listrules- Rules array for process (array for priority)from- match by source ip (highest priority)any- match any trafficresolve- match by resolved domain namecidr- match by target ip CIDRcountry_name- match by country nameregion_name- match by region name (such asALIDNS.COM)city_name- match by cityowner_domain- match by owner domain (such asgithub.com,twitter.com)isp_domain- match by ISP (such as阿里云,阿里云/电信/联通/移动/教育网)country_code- match by 2 char country code (such asCN)anycast- match by is anycast ip (onlyorANYCAST)idc- match by is idc ip (onlyorIDC)base_station- match by is base_station ip (onlyor基站)
webadmin section
"webadmin": {
"enabled": true, "host": "127.0.0.1", "port": 8788,
"redis_host": "127.0.0.1", "redis_port": 6379, "redis_db": 1,
"log": "/var/log/nft_webadmin.log",
"restart": {"max": 5, "window": 300},
"pdns_config": "/etc/powerdns/pdns-recursor.json",
"pdns_poison_list": "/etc/powerdns/dns_posion_list.txt",
"pdns_host": "user@192.168.30.2",
"rec_control": "rec_control"
}
pdns_host routes all PowerDNS file I/O and rec_control over ssh (keys /
~/.ssh/config of the invoking user; nothing is managed by this package).
Omit it when the recursor is local. Omit pdns_config to hide the DNS tab.
switches section — SNMP ARP/MAC collectors
One supervised child process per switch, polling ARP / MAC / interface tables
into redis (ARP::MAPPING, MAC::TABLE::<sysname>, SW::INT::<sysname>),
which is what the flow view's 源设备 column resolves against.
"switches": {
"enabled": true,
"python": "python3.9",
"log_dir": "/var/log/nft_route",
"poll_interval": 300,
"iface_interval": 1800,
"restart": {"max": 5, "window": 300},
"devices": [
{"name": "sw-core", "ip": "192.168.11.1",
"user": "monitor", "auth_key": "...", "priv_key": "..."},
{"name": "sw-acc", "ip": "192.168.11.4", "community": "public"}
]
}
python— interpreter for the collector children; omit to use the router's own. Set it when only another interpreter has a working pysnmp.- per device:
community(v2c) oruser+auth_key(+priv_key) for v3 (HMAC192SHA256 / AES128),enabled: falseto keep the entry but stop polling.
Both Huawei ARP MIBs are handled automatically: the standard
ipNetToPhysical table on VRP8/CloudEngine, and HUAWEI-ETHARP-MIB on VRP5
(S5700 family), whose index layout differs.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nftable_router-0.2.0.tar.gz.
File metadata
- Download URL: nftable_router-0.2.0.tar.gz
- Upload date:
- Size: 167.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f83af1fb2e5f194d767034bf05c78a73d9e0d10bf5915fdc6461528a9fb5773
|
|
| MD5 |
a5118a8ff40ce6afb7af98d5ae0f6d08
|
|
| BLAKE2b-256 |
2490aab3b9cf1a41e4861081bdae9f233d8d38f35fcc2176602d5bf9d82252a3
|
File details
Details for the file nftable_router-0.2.0-py3-none-any.whl.
File metadata
- Download URL: nftable_router-0.2.0-py3-none-any.whl
- Upload date:
- Size: 178.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c43acdcddf56d1c96fe3bff8543224585a04fdd07cd5f19c2f74cc6a06815bb9
|
|
| MD5 |
c8856f9a2c82db39ed2d87ad1c0c4cd6
|
|
| BLAKE2b-256 |
9810d753ad5b93299fb539f6f82b6309b586ae1657e3d72e4ba1d3bf94fe1955
|