Technical Documentation: openwrt/packages
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
OpenWrt packages feed
Description
This is the OpenWrt "packages"-feed containing community-maintained build scripts, options and patches for applications, modules and libraries used within OpenWrt.
Installation of pre-built packages is handled directly by the opkg utility within your running OpenWrt system or by using the OpenWrt SDK on a build system.
Usage
This repository is intended to be layered on-top of an OpenWrt buildroot. If you do not have an OpenWrt buildroot installed, see the documentation at: OpenWrt Buildroot – Installation on the OpenWrt support site.
This feed is enabled by default. To install all its package definitions, run:
./scripts/feeds update packages
./scripts/feeds install -a -p packagesLicense
See LICENSE file.
Package Guidelines
See CONTRIBUTING.md file.
Overview (`lang/python/README.md`)
Python packages folder
Table of contents
- Description
- Introduction
- Python 2 end-of-life
- Using Python in external/other package feeds
- Build considerations
- General folder structure
- Building a Python package
Description
This section describes specifics for the Python packages that are present in this repo, and how things are structured.
In terms of license, contributing guide, etc, all of that information is described in the top README.md file, and it applies here as well. This document attempts to cover only technical aspects of Python packages, and maybe some explanations about how things are (and why they are as they are).
Introduction
This sub-tree came to exist after a number of contributions (Python packages) were made to this repo, and the lang subtree grew to a point where a decision was made to move all Python packages under lang/python.
It contains the Python 3 interpreter and Python packages. Most of the Python packages are downloaded from pypi.org. Python packages from pypi.org are typically preferred when adding new packages.
If more packages (than the ones packaged here) are needed, they can be downloaded via pip. Note that the versions of pip & setuptools [available in this repo] are the ones that are packaged inside the Python package (yes, Python comes packaged with pip & setuptools).
Python 2 end-of-life
Python 2 will not be maintained past 2020. All Python 2 packages have been removed from the packages feed (this repo) and archived in the abandoned packages feed.
Using Python in external/other package feeds
In the feeds.conf (or feeds.conf.default file, whatever is preferred), the packages repo should be present.
Example
src-git packages https://git.openwrt.org/feed/packages.git
src-git luci https://git.openwrt.org/project/luci.git
src-git routing https://git.openwrt.org/feed/routing.git
src-git telephony https://git.openwrt.org/feed/telephony.git
#
#
src-git someotherfeed https://github.com/<github-user>/<some-other-package>Assuming that there are Python packages in the <some-other-package>, they should include python3-package.mk like this:
include $(TOPDIR)/feeds/packages/lang/python/python3-package.mkSame rules apply for python3-package.mk as the Python packages in this repo.
One important consideration:: if the local name is not packages, it's something else, like openwrt-packages. And in feeds.conf[.default] it's:
src-git openwrt-packages https://git.openwrt.org/feed/packages.gitThen, the inclusions also change:
include $(TOPDIR)/feeds/openwrt-packages/lang/python/python3-package.mkEach maintainer[s] of external packages feeds is responsible for the local name, and relative inclusion path back to this feed (which is named packages by default).
In case there is a need/requirement such that the local package feed is named something else than packages, one approach to make the package flexible to change is:
PYTHON3_PACKAGE_MK:=$(wildcard $(TOPDIR)/feeds/*/lang/python/python3-package.mk)
# verify that there is only one single file returned
ifneq (1,$(words $(PYTHON3_PACKAGE_MK)))
ifeq (0,$(words $(PYTHON3_PACKAGE_MK)))
$(error did not find python3-package.mk in any feed)
else
$(error found multiple python3-package.mk files in the feeds)
endif
else
$(info found python3-package.mk at $(PYTHON3_PACKAGE_MK))
endif
include $(PYTHON3_PACKAGE_MK)This should solve the corner-case where the python3-package.mk can be in some other feed, or if the packages feed will be named something else locally.
Build considerations
In order to build the Python interpreter, a host Python interpreter needs to be built, in order to process some of the build for the target Python build. The host Python interpreter is also needed so that Python bytecodes are generated, so the host interpreter needs to be the exact version as on the target. And finally, the host Python interpreter also provides pip, so that it may be used to install some Python packages that are required to build other Python packages.
That's why you'll also see a Python build & staging directories.
As you're probably thinking, this sounds [and is] somewhat too much complication [just for packaging], but the status of things is-as-it-is, and it's probably much worse than what's currently visible on the surface [with respect to packaging Python & packages].
As mentioned earlier, Python packages are shipped with bytecodes, and the reason for this is simply performance & size.
The thought/discussion matrix derives a bit like this:
- shipping both Python source-code & bytecodes takes too much space on some devices ; Python source code & byte-code take about similar disk-size
- shipping only Python source code has a big performance penalty [on some lower end systems] ; something like 500 msecs (Python source-only) -> 70 msecs (Python byte-codes) time reduction for a simple "Hello World" script
- shipping only Python byte-codes seems like a good trade-off, and this means that
python3-srccan be provided for people that want the source code
By default, automatic Python byte-code generation is disabled when running a Python script, in order to prevent a disk from accidentally filling up. Since some disks reside in RAM, this also means not filling up the RAM. If someone wants to convert Python source to byte-code then he/she is free to compile it [directly on the device] manually via the Python interpreter & library.
General folder structure
The basis of all these packages is:
- lang/python/python3 - The Python 3.x.y interpreter
This is a normal OpenWrt package, which will build the Python interpreter. This also provides python3-pip & python3-setuptools. Each Python package is actually split into multiple sub-packages [e.g. python3-urllib, python3-sqlite3, etc]. This can be viewed inside lang/python/python3/files.
The reason for this splitting, is purely to offer a way for some people to package Python in as-minimal-as-possible-and-still-runable way, and also to be somewhat maintainable when packaging. A standard Python installation can take ~20-30 MBs of disk, which can be somewhat big for some people, so there is the python3-base package which brings that down to ~5 MBs. This seems to be good enough (and interesting) for a number of people.
The Python interpreter is structured like this:
python3-base, which is just the minimal package to startup Python and run basic commandspython3is a meta-package, which installs almost everything (python3-base [plus] Python library [minus] some unit-tests & some windows-y things)python3-lightispython3[minus] packages that are in lang/python/python3/files ; the size of this package may be sensible (and interesting) to another group of people
All other Python packages (aside from the intepreter) typically use these files:
- python3-host.mk - this file contains paths and build rules for running the Python interpreter on the host-side; they also provide paths to host interprete, host Python lib-dir & so on
- python3-package.mk
- includes python3-host.mk
- contains all the default build rules for Python packages; these will be detailed below in the Building a Python package section
Note that Python packages don't need to use these files (i.e. python3-package.mk & python3-host.mk), but they do provide some ease-of-use & reduction of duplicate code. And they do contain some learned-lessons about packaging Python packages, so it's a good idea to use them.
Building a Python package
Include python3-package.mk
Add this after include $(INCLUDE_DIR)/package.mk
include ../python3-package.mkThis will make sure that build rules for Python can be specified and picked up for build.
Include pypi.mk (optional)
pypi.mk is an include file that makes downloading package source code from pypi.org simpler.
To use pypi.mk, add this before include $(INCLUDE_DIR)/package.mk:
include ../pypi.mkpypi.mk has several PYPI_* variables that can/must be set (see below); these should be set before pypi.mk is included, i.e. before the include ../pypi.mk line.
pypi.mk also provides default values for PKG_SOURCE and PKG_SOURCE_URL, so these variables may be omitted.
Required variables:
PYPI_NAME: Package name on pypi.org. This should match the PyPI name exactly.For example (from the
python-yamlpackage):textPYPI_NAME:=PyYAML
Optional variables:
PYPI_SOURCE_NAME: Package name component of the source tarball filename
Default: Same value asPYPI_NAMEPYPI_SOURCE_EXT: File extension of the source tarball filename
Default:tar.gz
pypi.mk constructs the default PKG_SOURCE value from these variables (and PKG_VERSION):
PKG_SOURCE?=$(PYPI_SOURCE_NAME)-$(PKG_VERSION).$(PYPI_SOURCE_EXT)Add Package/ OpenWrt definitions
This part is similar to default OpenWrt packages.
Example:
define Package/python3-lxml
SECTION:=lang
CATEGORY:=Languages
SUBMENU:=Python
TITLE:=Pythonic XML processing library
URL:=https://lxml.de
DEPENDS:=+python3-light +libxml2 +libxslt +libexslt
endef
define Package/python3-lxml/description
The lxml XML toolkit is a Pythonic binding
for the C libraries libxml2 and libxslt.
endefSome considerations here (based on the example above):
- typically the package is named
Package/python3-<something>; this convention makes things easier to follow, though it could work without naming things this way TITLEcan be something a bit more verbose/neat ; typically the name is short as seen above
Python package dependencies
Aside from other libraries and programs, every Python package will depend on at least one of these three types of packages:
The Python interpreter: All Python packages should depend on one of these three interpreter packages:
python3-lightis the best default for most Python packages.python3-baseshould only be used as a dependency if you are certain the bare interpreter is sufficient.python3is useful if many (more than three) Python standard library packages are needed.
Python standard library packages: As noted above, many parts of the Python standard library are packaged separate from the Python interpreter. These packages are defined by the files in lang/python/python3/files.
To find out which of these separate standard library packages are necessary, after completing a draft Makefile (including the
$(eval ...)lines described in the next section), runmakewith theconfiguretarget andPY3=stdlib V=sin the command line. For example:textmake package/python-lxml/configure PY3=stdlib V=sIf the package has been built previously, include the
cleantarget to trigger configure again:textmake package/python-lxml/{clean,configure} PY3=stdlib V=sThis will search the package for module imports and generate a list of suggested dependencies. Some of the found imports may be false positives, e.g. in example or test files, so examine the matches for more information.
Other Python packages: The easiest way to find these dependencies is to look for the
install_requireskeyword inside the package'ssetup.pyfile (it will be a keyword argument to thesetup()function). This will be a list of run-time dependencies for the package.There may already be packages in the packages feed that provide these dependencies. If not, they will need to be packaged for your Python package to function correctly.
Any packages in a
setup_requireskeyword argument are build-time dependencies that may need to be installed on the host (host Python inside of OpenWrt buildroot, not system Python that is part of the outer computer system). To ensure these build-time dependencies are present, see Host-side Python packages for build. (Note that Setuptools is already installed as part of host Python.)
Wrapping things up so that they build
If all the above prerequisites have been met, all that's left is:
$(eval $(call Py3Package,python3-lxml))
$(eval $(call BuildPackage,python3-lxml))The $(eval $(call Py3Package,python3-lxml)) part will instantiate all the default Python build rules so that the final Python package is packaged into an OpenWrt.
And $(eval $(call BuildPackage,python3-lxml)) will bind all the rules generated with $(eval $(call Py3Package,python3-lxml)) into the OpenWrt build system.
These packages will contain byte-codes and binaries (shared libs & other stuff).
If a user wishes to ship source code, adding one more line creates one more package that ship Python source code:
$(eval $(call Py3Package,python3-lxml))
$(eval $(call BuildPackage,python3-lxml))
$(eval $(call BuildPackage,python3-lxml-src))The name *-src must be the Python package name; so for python3-lxml-src a equivalent python3-lxml name must exist.
Customizing things
Some packages need custom build rules (because they do).
The default package build and install processes are defined in python3-package.mk.
Building
The default build process calls setup.py install inside the directory where the Python source package is extracted (PKG_BUILD_DIR). This "installs" the Python package to an intermediate location (PKG_INSTALL_DIR) where it is used by the default install process.
There are several Makefile variables that can be used to customize this process (all optional):
PYTHON3_PKG_SETUP_DIR: Path wheresetup.pycan be found, relative to the package directory (PKG_BUILD_DIR).
Default: empty value (setup.pyis in the package directory)PYTHON3_PKG_SETUP_VARS: Additional environment variables to set for the call tosetup.py. Should be in the form ofVARIABLE1=value VARIABLE2=value ....
Default: empty valuePYTHON3_PKG_SETUP_GLOBAL_ARGS: Additional command line arguments to pass tosetup.py, before / in front of theinstallcommand.
Default: empty valuePYTHON3_PKG_SETUP_ARGS: Additional command line arguments to pass tosetup.py, after theinstallcommand.
Default:--single-version-externally-managed
Conceptually, these variables are used in this way:
cd $(PKG_BUILD_DIR)/$(PYTHON3_PKG_SETUP_DIR)
$(PYTHON3_PKG_SETUP_VARS) python3 setup.py $(PYTHON3_PKG_SETUP_GLOBAL_ARGS) install $(PYTHON3_PKG_SETUP_ARGS)The default build process can be completely overridden by defining a custom Py3Build/Compile rule in the package Makefile.
Installing
The default install process copies some/all of the files from PKG_INSTALL_DIR, placed there by the build process, to a location passed to the install rule as the first argument ($(1)). The OpenWrt build system will then take those files and create the actual .ipk package archives.
This default process uses 2 build rules:
Py3Package//filespecwhich are Python library files relative to/usr/lib/pythonX.Y; by default this is/usr/lib/python$(PYTHON3_VERSION)/site-packages(PYTHON3_PKG_DIR) ; most Python packages generate files that get installed in this sub-folderPy3Package//installis similar toPackage//install; this allows binary (or other files) to be installed on the target
Both the 2 above rules generate a Package//install build rule, which gets picked up by the build system. Both can be used together (they are not mutually exclusive), and provide a good enough flexibility for specifying Python packages.
The Py3Package//filespec rule contains one or more lines of the following format (whitespace added for clarity):
<one of: +-=> | <file/directory path> | <file permissions>The initial character controls the action that will be taken:
+: Install the given path. If the path is a directory, all files and subdirectories inside are installed.- If file permissions is specified (optional), then the file or directory (and all files and subdirectories) are assigned the given permissions; if omitted, then the file or directory retains its original permissions.
-: Remove the given path. Useful when most of a directory should be installed except for a few files or subdirectories.- File permissions is not used / ignored in this case.
=: Assign the given file permissions to the given path. File permissions is required in this case.
As mentioned, the default Py3Package//filespec installs PYTHON3_PKG_DIR:
define Py3Package/python3-example/filespec
+|$(PYTHON3_PKG_DIR)
endefIf the package installs a example_package directory inside PYTHON3_PKG_DIR, and there is an examples directory and test_*.py files that can be omitted to save space, this can be specified as:
define Py3Package/python3-example/filespec
+|$(PYTHON3_PKG_DIR)
-|$(PYTHON3_PKG_DIR)/example_package/examples
-|$(PYTHON3_PKG_DIR)/example_package/test_*.py
endefHost-side Python packages for build
These can be installed via pip and ideally they should only be installed like this, because it's a bit simpler than running them through the OpenWrt build system.
Requirements files
All host-side Python packages are installed with pip using requirements files, with hash-checking mode enabled. These requirements files are stored in the host-pip-requirements directory.
Each requirements file is named after the Python package it installs and contains the package's pinned version and --hash option. The --hash option value is the SHA256 hash of the package's source tarball; this value can be found on pypi.org.
For example, the requirements file for setuptools-scm (setuptools-scm.txt) contains:
setuptools-scm==4.1.2 --hash=sha256:a8994582e716ec690f33fec70cca0f85bd23ec974e3f783233e4879090a7faa8If the Python package to be installed depends on other Python packages, those dependencies, with their pinned versions and --hash options, also need to be specified in the requirements file. For instance, cffi.txt includes information for pycparser because pycparser is a dependency of cffi and will be installed with cffi.
There are two types of requirements files in host-pip-requirements:
Installs the latest version of a Python package.
A requirements file of this type is named with the package name only (for example, setuptools-scm.txt) and is used when there is no strict version requirement.
These files will be updated as newer versions of the Python packages are available.
Installs a specific version of a Python package.
A requirements file of this type is named with the package name and version number (for example, Django-1.11.txt) and is used when a specific (usually older) version is required.
Installing the latest versions of packages is preferred over specific versions whenever possible.
Installing host-side Python packages
Set HOST_PYTHON3_PACKAGE_BUILD_DEPENDS to the names of one or more requirements files in host-pip-requirements, without the directory path or ".txt" extension.
For example:
HOST_PYTHON3_PACKAGE_BUILD_DEPENDS:=setuptools-scmThe Python package will be installed in $(STAGING_DIR_HOSTPKG)/lib/pythonX.Y/site-packages.
Non-Python packages installing host-side Python packages
Non-Python packages can also install host-side Python packages using the same mechanism:
Set
HOST_PYTHON3_PACKAGE_BUILD_DEPENDS(see above for details).Include
python3-package.mk(setPYTHON3_PKG_BUILD:=0to avoid using the default Python package build recipes).Call
Py3Build/InstallBuildDependsto initiate the installation.
For example:
PYTHON3_PKG_BUILD:=0
HOST_PYTHON3_PACKAGE_BUILD_DEPENDS:=setuptools-scm
include $(INCLUDE_DIR)/package.mk
include ../../lang/python/python3-package.mk
# If outside of the packages feed:
# include $(TOPDIR)/feeds/packages/lang/python/python3-package.mk
define Build/Compile
$(call Py3Build/InstallBuildDepends)
$(call Build/Compile/Default)
endefOverview (`net/adblock-fast/README.md`)
adblock-fast
A fast, lightweight DNS-based ad-blocker for OpenWrt that works with dnsmasq, smartdns, or unbound.
It runs once to process and install blocklists, then exits — keeping memory usage low.
Features
- Minimal runtime memory use
- Parallel blocklist download and processing
- Persistent cache support
- Optional Web UI for custom block/allow lists
- Reverts if DNS resolution fails after restart
📚 Full documentation:
https://docs.mossdef.org/adblock-fast/
Overview (`net/adblock/README.md`)
DNS based ad/abuse domain blocking
Table of Contents
- Description
- Quick Start
- Main Features
- Prerequisites
- Installation & Usage
- Adblock CLI interface
- Adblock Config Options
- Examples
- Best practice and tweaks
- Troubleshooting & debug options
- Support
- Removal
- Donations
Description
A lot of people already use adblocker plugins within their desktop browsers, but what if you are using your (smart) phone, tablet, watch or any other (wlan) gadget!? Getting rid of annoying ads, trackers and other abuse sites (like facebook) is simple: block them with your router.
When the DNS server on your router receives DNS requests, you will sort out queries that ask for the resource records of ad servers and return a simple NXDOMAIN. This is nothing but Non-eXistent Internet or Intranet domain name, if a domain name cannot be resolved using the DNS server, a condition called the NXDOMAIN occurred.
Quick Start
For a typical setup these few steps are enough to get adblock up and running — see the sections below for details:
- Install the LuCI companion package:
apk update && apk add luci-app-adblock(this pulls in theadblockbackend as a dependency). - Enable the adblock system service under
System → Startup, then open LuCI underServices → Adblock, tickEnabledand (recommended) set aStartup Trigger Interfaceto your WAN interface(s). - Keep the small, pre-selected default feed selection to start with (e.g.
adguard,adguard_trackingandcertpl, ≈280K domains). - Start and verify the service:
/etc/init.d/adblock start
/etc/init.d/adblock statusPlease note: don't blindly enable (too) many feeds at once — on low memory devices this will sooner or later lead to OOM conditions.
Main Features
Support of the following fully pre-configured domain blocklist feeds (free for private usage, for commercial use please check their individual licenses)
| Feed | Enabled | Size | Focus | Information |
|---|---|---|---|---|
| 1Hosts | VAR | compilation | Link | |
| adguard | x | L | general | Link |
| adguard_tracking | x | L | tracking | Link |
| android_tracking | S | tracking | Link | |
| andryou | L | compilation | Link | |
| anti_ad | L | compilation | Link | |
| anudeep | M | compilation | Link | |
| bitcoin | S | mining | Link | |
| certpl | x | L | phishing | Link |
| cpbl | XL | compilation | Link | |
| disconnect | S | general | Link | |
| divested | XXL | compilation | Link | |
| doh_blocklist | S | doh_server | Link | |
| firetv_tracking | S | tracking | Link | |
| games_tracking | S | tracking | Link | |
| hagezi | VAR | compilation | Link | |
| hblock | XL | compilation | Link | |
| ipfire_dbl | VAR | compilation | Link | |
| oisd_big | XXL | general | Link | |
| oisd_nsfw | XXL | porn | Link | |
| oisd_nsfw_small | M | porn | Link | |
| oisd_small | L | general | Link | |
| phishing_army | S | phishing | Link | |
| smarttv_tracking | S | tracking | Link | |
| spam404 | S | general | Link | |
| stevenblack | VAR | compilation | Link | |
| stopforumspam | S | spam | Link | |
| utcapitole | VAR | general | Link | |
| wally3k | S | compilation | Link | |
| whocares | M | general | Link | |
| winspy | S | win_telemetry | Link | |
| yoyo | S | general | Link |
Please note: Feeds marked with size VAR (1Hosts, hagezi, ipfire_dbl, stevenblack, utcapitole) additionally require a category selection via the options adb_hst_feed, adb_hag_feed, adb_ipf_feed, adb_stb_feed and adb_utc_feed (or via the LuCI feed configuration). Without a category the feed is skipped during processing.
- List of supported and fully pre-configured adblock sources, already active sources are pre-selected.
To avoid OOM errors, please do not select too many lists!
List size information with the respective domain ranges as follows:
• S (-10k), M (10k-30k) and L (30k-80k) should work for 128 MByte devices
• XL (80k-200k) should work for 256-512 MByte devices
• XXL (200k-) needs more RAM and Multicore support, e.g. x86 or raspberry devices
• VAR (50k-900k) variable size depending on the selection - Zero-conf like automatic installation & setup, usually no manual changes needed
- Simple but yet powerful adblock engine: adblock does not use error prone external iptables rulesets, http pixel server instances and things like that
- Supports six different DNS backend formats: dnsmasq, unbound, named (bind), kresd, smartdns or raw (e.g. used by dnscrypt-proxy)
- Supports three different SSL-enabled download utilities: uclient-fetch, full wget or curl
- Supports SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay
- Fast downloads & list processing as they are handled in parallel running background jobs with multicore support
- The download engine supports ETAG headers to download only updated feeds
- Supports a wide range of router modes, even AP modes are supported
- Full IPv4 and IPv6 support
- Provides top level domain compression (
tld compression), this feature removes thousands of needless host entries from the blocklist and lowers the memory footprint for the DNS backend - Packs multiple domains per dnsmasq 'local=' line (max. 1000 characters), this reduces the size of the final blocklist by ~30% and speeds up the dnsmasq start
- Provides a
DNS Blocklist Shift, where the generated final DNS blocklist is moved to the backup directory and only a soft link to this file is set in memory. As long as your backup directory is located on an external drive, you should activate this option to save valuable RAM. - Feed parsing by a very fast & secure domain validator, all domain rules and feed information are placed in an external JSON file (
/etc/adblock/adblock.feeds) - Overall duplicate removal in generated blocklist file
adb_list.overall - Additional local allowlist for manual overrides, located in
/etc/adblock/adblock.allowlist - Additional local blocklist for manual overrides, located in
/etc/adblock/adblock.blocklist - Implements firewall‑based DNS Control to force DNS interfaces/ports and to redirect to external unfiltered/filtered DNS server
- Includes firewall‑based Remote DNS Allow, a CGI-Interface to allow certain MACs temporary bypass the local adblock DNS
- Supports firewall‑based temporary DNS Bridging, to ensure a Zero‑Downtime during adblock-related DNS Restarts
- Connection checks during blocklist update to ensure a reliable DNS backend service
- Minimal status & error logging to syslog, enable debug logging to receive more output
- Procd based init system support (
start,stop,restart,reload,enable,disable,running,status,suspend,resume,search,report) - Auto-Startup via procd network interface trigger or via classic time based startup
- Suspend & Resume adblock temporarily without blocklist re-processing
- Provides comprehensive runtime information
- Provides a detailed DNS Report with DNS related information about client requests, top (blocked) domains and more
- Provides a powerful search function to quickly find blocked (sub-)domains, e.g. to allow certain domains
- Implements a jail mode - only domains on the allowlist are permitted, all other DNS requests are rejected
- Automatic blocklist backup & restore: backups are used on
start/restartand as a fallback on download errors — feeds are only actually refreshed viareload - Send notification E-Mails, see example configuration below
- Add new adblock feeds on your own with the
Custom Feed Editorin LuCI or via CLI, see example below - Strong LuCI support, all relevant options are exposed to the web frontend
Prerequisites
- OpenWrt, latest stable release or a development snapshot
- A usual setup with a working DNS backend
- A download utility with SSL support:
wget,uclient-fetchwith one of thelibustream-*ssl libraries orcurlis required - A certificate store such as
ca-bundleorca-certificates, as adblock checks the validity of the SSL certificates of all download sites by default - For E-Mail notifications you need to install and setup the additional
msmtppackage - For DNS reporting you need to install the additional package
tcpdump-miniortcpdump
Please note:
- Devices with less than 128MB of RAM are not supported
- For performance reasons, adblock depends on gnu sort and gawk
- Before update from former adblock releases please make a backup of your local allow- and blocklists. In the latest adblock these lists have been renamed to
/etc/adblock/adblock.allowlistand/etc/adblock/adblock.blocklist. There is no automatic content transition to the new files. - The uci configuration of adblock is automatically migrated during package installation via the uci-defaults mechanism using a housekeeping script
- Only
reloadactually refreshes the feeds (ETag check plus download of changed feeds).start,restart— andboot/resume— restore the existing blocklist backups and only download feeds that have no backup yet; they do not re-fetch already cached feeds. To update your blocklists (e.g. from a cron job) always usereload
Installation & Usage
- Make a backup and update your local opkg/apk repository
- Install the LuCI companion package
luci-app-adblockwhich also installs the mainadblockpackage as a dependency - Enable the adblock system service (System -> Startup) and enable adblock itself (adblock -> General Settings)
- It's strongly recommended to use the LuCI frontend to easily configure all aspects of adblock, the application is located in LuCI under the
Servicesmenu - It's also recommended to configure a
Startup Trigger Interfaceto depend on your WAN interface events during boot or restart of your router. Listing IPv6 interfaces (wan6) is fine as well: a trigger only starts a run if the last one did not succeed or if its blocklist is gone, so the chatty netifd update events no longer cause repeated downloads
Adblock CLI interface
- The most important adblock functions are accessible via CLI as well.
~# /etc/init.d/adblock
Syntax: /etc/init.d/adblock [command]
Available commands:
start Start the service
stop Stop the service
restart Restart the service
reload Reload configuration files (or restart if service does not implement reload)
enable Enable service autostart
disable Disable service autostart
enabled Check if service is started on boot
suspend Suspend adblock processing
resume Resume adblock processing
search <domain> Search active blocklists and backups for a specific domain
report [<cli>|<mail>|<gen>|<json>] Print DNS statistics
running Check if service is running
status Service status
trace Start with syscall trace
info Dump procd service infoThe report sub-command accepts an output mode: cli (default, human-readable table printed to the console), json (machine-readable output, incl. GeoIP map data when adb_map=1), mail (send the report via msmtp) and gen (regenerate the report data files in the background, used by the LuCI frontend).
Adblock Config Options
- Usually the auto pre-configured adblock setup works quite well and no manual overrides are needed
| Option | Default | Description/Valid Values |
|---|---|---|
| adb_enabled | 1, enabled | set to 0 to disable the adblock service |
| adb_feedfile | /etc/adblock/adblock.feeds | full path to the used adblock feed file |
| adb_dns | -, auto-detected | dnsmasq, unbound, named, kresd, smartdns or raw |
| adb_cores | -, auto-detected | limit the cpu cores used by adblock; only auto-detection is memory-capped |
| adb_fetchcmd | -, auto-detected | uclient-fetch, wget or curl |
| adb_fetchparm | -, auto-detected | manually override the config options for the selected download utility |
| adb_fetchretry | 5 | number of download attempts in case of an error (not supported by uclient-fetch) |
| adb_fetchinsecure | 0, disabled | don't check SSL server certificates during download |
| adb_trigger | -, not set | logical reload trigger interface(s), e.g. wan and wan6 |
| adb_triggerdelay | 5 | additional trigger delay in seconds before adblock processing begins |
| adb_debug | 0, disabled | set to 1 to enable the debug output |
| adb_nicelimit | 0, standard prio. | valid nice level range 0-19 of the adblock processes |
| adb_dnsshift | 0, disabled | shift the blocklist to the backup directory and only set a soft link to this file in memory |
| adb_dnsdir | -, auto-detected | path for the generated blocklist file adb_list.overall |
| adb_dnstimeout | 20 | timeout in seconds to wait for a successful DNS backend restart |
| adb_dnsinstance | 0, first instance | set the relevant dnsmasq backend instance used by adblock |
| adb_dnsflush | 0, disabled | set to 1 to flush the DNS Cache before & after adblock processing |
| adb_lookupdomain | localhost | domain to check for a successful DNS backend restart |
| adb_report | 0, disabled | set to 1 to enable the background tcpdump gathering process for reporting |
| adb_map | 0, disabled | enable a GeoIP Map with blocked domains |
| adb_reportdir | /tmp/adblock-report | path for DNS related report files |
| adb_repiface | -, auto-detected | name of the reporting interface or any used by tcpdump |
| adb_repport | 53 | list of reporting port(s) used by tcpdump |
| adb_repfilter | -, not set | additional tcpdump filter expression, logically ANDed to the internal reporting port filter |
| adb_repchunkcnt | 5 | report chunk count used by tcpdump |
| adb_repchunksize | 1 | report chunk size used by tcpdump in MB |
| adb_represolve | 0, disabled | resolve reporting IP addresses using reverse DNS (PTR) lookups |
| adb_tld | 1, enabled | set to 0 to disable the top level domain compression (tld) function |
| adb_basedir | /tmp | path for all adblock related runtime operations, e.g. downloading, sorting, merging etc. |
| adb_backupdir | /tmp/adblock-backup | path for adblock backups |
| adb_safesearch | 0, disabled | enforce SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay |
| adb_safesearchlist | -, not set | limit SafeSearch to certain provider (see above) |
| adb_mail | 0, disabled | set to 1 to enable notification E-Mails in case of a processing errors |
| adb_mailreceiver | -, not set | receiver address for adblock notification E-Mails |
| adb_mailsender | no-reply@adblock | sender address for adblock notification E-Mails |
| adb_mailtopic | adblock notification | topic for adblock notification E-Mails |
| adb_mailprofile | adb_notify | mail profile used in msmtp for adblock notification E-Mails |
| adb_jail | 0 | jail mode - only domains on the allowlist are permitted, all other DNS requests are rejected |
| adb_nftforce | 0, disabled | redirect all local DNS queries from specified LAN zones to the local DNS resolver |
| adb_nftdevforce | -, not set | firewall LAN Devices/VLANs that should be forced locally |
| adb_nftportforce | -, not set | firewall ports that should be forced locally |
| adb_nftallow | 0, disabled | routes MACs or interfaces to an unfiltered external DNS resolver, bypassing local adblock |
| adb_nftmacallow | -, not set | listed MAC addresses will always use the configured unfiltered DNS server |
| adb_nftdevallow | -, not set | entire interfaces or VLANs will be routed to the unfiltered DNS server |
| adb_allowdnsv4 | -, not set | external IPv4 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy |
| adb_allowdnsv6 | -, not set | external IPv6 DNS resolver applied to MACs and interfaces using the unfiltered DNS policy |
| adb_nftremote | 0, disabled | routes MACs to an unfiltered external DNS resolver, bypassing local adblock |
| adb_nftmacremote | -, not set | Allows listed MACs to remotely access an unfiltered external DNS resolver, bypassing local adblock |
| adb_nftremotetimeout | 15 | Time limit in minutes for remote DNS access of the listed MAC addresses |
| adb_remotednsv4 | -, not set | external IPv4 DNS resolver applied to MACs using the unfiltered remote DNS policy |
| adb_remotednsv6 | -, not set | external IPv6 DNS resolver applied to MACs using the unfiltered remote DNS policy |
| adb_nftblock | 0, disabled | routes MACs or interfaces to a filtered external DNS resolver, bypassing local adblock |
| adb_nftmacblock | -, not set | listed MAC addresses will always use the configured filtered DNS server |
| adb_nftdevblock | -, not set | entire interfaces or VLANs will be routed to the filtered DNS server |
| adb_blockdnsv4 | -, not set | external IPv4 DNS resolver applied to MACs and interfaces using the filtered DNS policy |
| adb_blockdnsv6 | -, not set | external IPv6 DNS resolver applied to MACs and interfaces using the filtered DNS policy |
| adb_nftbridge | -, not set | enables a temporary DNS bridge to an external DNS resolver during local DNS restarts |
| adb_bridgednsv4 | -, not set | external IPv4 DNS resolver used during bridging |
| adb_bridgednsv6 | -, not set | external IPv6 DNS resolver used during bridging |
| adb_hst_feed | -, not set | category selection for the 1hosts feed (required to enable it) |
| adb_hag_feed | -, not set | category selection for the hagezi feed (required to enable it) |
| adb_ipf_feed | -, not set | category selection for the ipfire_dbl feed (required to enable it) |
| adb_stb_feed | -, not set | category selection for the stevenblack feed (required to enable it) |
| adb_utc_feed | -, not set | category selection for the utcapitole feed (required to enable it) |
Examples
Change the DNS backend to unbound:
No further configuration is needed, adblock deposits the final blocklist adb_list.overall in /var/lib/unbound by default.
To preserve the DNS cache after adblock processing please install the additional package unbound-control.
Change the DNS backend to bind:
Adblock deposits the final blocklist adb_list.overall in /var/lib/bind by default.
To preserve the DNS cache after adblock processing please install the additional package bind-rndc.
To use the blocklist please modify /etc/bind/named.conf:
in the `options` namespace add:
response-policy { zone "rpz"; };
and at the end of the file add:
zone "rpz" {
type master;
file "/var/lib/bind/adb_list.overall";
allow-query { none; };
allow-transfer { none; };
};Change the DNS backend to kresd:
Adblock deposits the final blocklist adb_list.overall in /tmp/kresd, no further configuration needed.
Change the DNS backend to smartdns:
No further configuration is needed, adblock deposits the final blocklist adb_list.overall in /tmp/smartdns by default.
Service status output:
In LuCI you'll see the realtime status in the Runtime section on the overview page.
To get the status in the CLI, just call /etc/init.d/adblock status or /etc/init.d/adblock status_service:
root@blackhole:~# /etc/init.d/adblock status
::: adblock runtime information
+ adblock_status : enabled
+ frontend_ver : 4.5.2-r4
+ backend_ver : 4.5.2-r4
+ blocked_domains : 888 135
+ active_feeds : 1hosts, adguard, adguard_tracking, bitcoin, certpl, doh_blocklist, hagezi, ipfire_dbl, phishing_army, smarttv_tracking, stevenblack, winspy
+ dns_backend : unbound (1.24.2-r1), /mnt/data/adblock/backup, 346.57 MB
+ run_ifaces : trigger: wan, report: br-lan
+ run_information : base: /mnt/data/adblock, dns: /var/lib/unbound, backup: /mnt/data/adblock/backup, report: /mnt/data/adblock/report, error: /dev/null
+ run_flags : shift: ✔, custom feed: ✘, ext. DNS (std/prot/remote/bridge): ✘/✔/✔/✔, force: ✔, flush: ✘, tld: ✔, search: ✘, report: ✔, mail: ✔, jail: ✘, debug: ✘
+ last_run : mode: reload, 2026-03-12T19:08:41+01:00, duration: 0m 57s, 1337.01 MB available
+ system_info : cores: 4, fetch: curl, Bananapi BPI-R3, mediatek/filogic, OpenWrt SNAPSHOT (r33360-ab0872a734)Best practice and tweaks
Recommendation for low memory systems
adblock keeps all working data in RAM to avoid unnecessary flash wear. The number of parallel processing jobs and the sort buffer size are automatically scaled to the available memory, so on constrained devices adblock already throttles itself during feed processing. On devices with only 128–256 MB RAM, you can further reduce memory pressure with the following optimizations:
- Limit CPU parallelism: the auto-detected core count is capped to the available memory; set
adb_cores=1to force single-threaded processing with minimal peak memory. A manually set value is always used as-is — it is never lowered, so raising it on a constrained device is at your own risk - Use external storage: with the defaults the feed backups (
adb_backupdir) and the final blocklist (adb_dnsdir) are kept in RAM permanently, not only during feed processing, e.g. 72 MB backups and a 53 MB blocklist with 1.1M entries. Setadb_basedir,adb_backupdirandadb_reportdirto a USB drive or SSD to offload the temporary and persistent data - Enable blocklist shifting: activate
adb_dnsshiftto store the generated blocklist in the backup directory on external storage and keep only a symlink in RAM - Use firewall‑based DNS redirection: Route DNS queries via nftables to external filtered DNS resolvers and keep only a minimal local blocklist active
- Use compressed swap: install the
zram-swappackage so the kernel can page out cold memory into compressed RAM under pressure (see below)
Top level domain compression (adb_tld)
The top level domain compression is enabled by default. It removes all subdomains of already blocked domains (e.g. ads.example.com if example.com is blocked as well), for this it needs an additional label reversal per feed and an extra pass over the merged blocklist. How much it saves depends on the selected feeds: curated feeds like hagezi or oisd are largely compressed already, feeds with many plain host entries are not. Some measurements on 2-core routers:
- Large feed selection with many host entries (1hosts, adguard, adguard_tracking, certpl, doh_blocklist, hagezi, ipfire_dbl, stevenblack, winspy), unbound, x86: the processing time rises from 78 to 109 seconds, but the blocklist shrinks from 2.94M to 1.07M entries (-64%). unbound loads it in 15 instead of 57 seconds and needs 400 MB instead of 1.1 GB RAM, including the restart of the DNS backend the whole run is even faster with
adb_tld=1 - Small feed selection (anti_ad, hagezi, yoyo), dnsmasq, ARM: the processing time rises by 2 seconds, the blocklist shrinks from 113k to 82k entries (-27%), dnsmasq needs 2.3 MB less RAM
- Curated feeds only (hagezi pro, oisd big), dnsmasq: the processing time drops from 14.5 to 7.5 seconds with
adb_tld=0, the blocklist grows by 1.3% (~4,700 entries)
Recommendation: keep the default. Only on slow CPUs with curated, already compressed feeds adb_tld=0 can save processing time at the cost of a slightly larger blocklist. To check it for your own feed selection, compare the number of blocked domains in the adblock status with and without the compression.
Use compressed swap (zram-swap) on low memory devices
The simplest way to survive the memory peak during feed processing is to give the kernel a compressed swap device and let it page out cold memory under pressure. The zram-swap package sets this up automatically at boot — no scripting, no changes to adblock, and adblock benefits transparently. Because the swap lives in compressed RAM rather than on flash, this incurs no flash wear.
apk add zram-swapSize the swap device relative to your physical RAM — a sensible rule of thumb is half to one times the installed RAM. Note that the compressed pages occupy RAM themselves, so do not oversize it on very small devices:
| Installed RAM | Suggested zram_size_mb |
|---|---|
| 128 MB | 64–128 |
| 256 MB | 128–256 |
| 512 MB | 256–512 |
| 1 GB and more | 512 |
The device size is configured in /etc/config/system via LuCI (System -> ZRam Settings) or via CLI:
uci set system.@system[0].zram_size_mb='128'
uci commit system
/etc/init.d/zram restartA running zram device cannot be resized in place, so the restart is required for a changed size to take effect.
To make the kernel reclaim into zram more eagerly during the short processing peak, raise the swappiness and persist it across reboots, e.g.:
echo 'vm.swappiness=100' >> /etc/sysctl.conf
sysctl -pPlease note: zram covers the short memory peak during feed processing, it does not replace external storage for the backups and the shifted blocklist (see above): without it, they occupy RAM permanently, and the gzip compressed backups can't be compressed any further by zram. On very small devices (128 MB) compressed swap helps, but heavy swapping costs CPU — if RAM is truly marginal, the more honest fix is to activate fewer feeds.
Sensible choice of blocklists
Don`t just blindly activate too many feeds at once, sooner or later this will lead to OOM conditions.The following feeds are just my personal recommendation as an initial setup:
adguard,adguard_trackingandcertpl
In total, this feed selection blocks about 280K domains. It may also be useful to include compilations like hagezi, stevenblack or oisd.
DNS reporting, enable the GeoIP Map
adblock includes a powerful reporting tool on the DNS Report tab which shows the latest DNS statistics generated by tcpdump. To get the latest statistics always press the "Refresh" button.
In addition to a tabular overview adblock reporting includes a GeoIP map in a modal popup window/iframe that shows the geolocation of your own uplink addresses (in green) and the locations of blocked domains in red. To enable the GeoIP Map set the following option in "Advanced Report Settings" config tab: set adb_map to 1 to include the external components listed below and activate the GeoIP map.
To make this work, adblock uses the following external components:
- Leaflet is a lightweight open-source JavaScript library for interactive maps
- The free and quite fast IP Geolocation API to resolve the required IP/geolocation information (max. 45 blocked Domains per request)
The basemap is no longer pulled from a tile service. CARTO started to require an API key for the raster basemaps at basemaps.cartocdn.com and watermarks every unauthenticated tile request, and a key is bound to a single customer, so it cannot be shipped with a package that lands on every installation. adblock therefore draws the basemap from country outlines that come with luci-app-adblock: Natural Earth 1:110m, public domain and stripped of all attributes. The map page issues no request to a third party, works without a WAN connection and leaks no part of the admin session to a CDN. The outlines are enough to locate an IP, so the map does not zoom in beyond level 6 and labels the continents rather than the countries.
Optional: a higher detail basemap
The shipped 1:110m outlines are coarse around Scandinavia, the Greek islands and the smaller island states. If you want sharper coastlines, build the 1:50m variant with mapshaper and drop it next to the shipped file. LuCI looks for it on every map run and falls back to the shipped outlines when it is missing, no config option is involved:
curl -sSLo ne50.geojson https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson/ne_50m_admin_0_countries.geojson
mapshaper ne50.geojson -filter-fields -simplify 5% keep-shapes -o force precision=0.01 format=geojson world-50m.json
scp world-50m.json root@openwrt:/www/luci-static/resources/view/adblock/The result is roughly 105 kB, about three times the shipped file. This file is not part of any package, so it is removed on sysupgrade unless you add its path to /etc/sysupgrade.conf, and it stays behind when luci-app-adblock is uninstalled.
DNS reporting, limit the tcpdump captureadb_repfilter takes a regular tcpdump/BPF expression which is logically ANDed to the internal reporting port filter. It narrows the capture for every adb_repiface setting, not just for any, e.g. to skip a single noisy client or to limit the report to certain network segments.
The most common use case is a capture with adb_repiface set to any, where tcpdump also picks up DNS traffic on outbound interfaces, e.g. requests that originate from the router itself and are routed through a VPN client interface. Those packets are captured correctly, but they are redundant, they clutter the report and they consume the limited pcap ring buffer configured via adb_repchunkcnt and adb_repchunksize. Some examples:
| Expression | Purpose |
|---|---|
not net 10.0.0.0/24 |
skip a VPN transfer network |
not (net 10.0.0.0/24 or net fd00:dead:beef::/64) |
skip a VPN transfer network, IPv4 and IPv6 |
net 192.168.1.0/24 or net 192.168.2.0/24 |
limit the capture to selected LAN segments |
not host 192.168.1.10 |
skip a single noisy client |
not (host 192.168.1.10 or host 192.168.1.11) |
skip several clients |
not host 9.9.9.9 |
skip the forwarding between the router and its upstream resolver |
ip |
capture IPv4 only |
Please note:
- the expression must not drop one direction of a DNS transaction. A report line is only emitted once the answer to a pending query has been seen, so filters like
inbound,src net ...ordst port 53compile fine but result in an empty report net 10.0.0.1is not a subnet, without a prefix length libpcap assumes /32 and the expression becomes equivalent tohost 10.0.0.1not host A or not host Bdoes not skip both hosts, it only skips the traffic between A and B. Usenot (host A or host B)instead- address, network and port primitives work with every interface, but the
vlanandetherprimitives require an ethernet based capture. They are rejected withadb_repifaceset toany(linux cooked mode) as well as on tunnel or ppp interfaces likewg0orpppoe-wan - the expression can only narrow the capture, additional ports have to be added via
adb_repport - a too narrow expression still compiles and tcpdump starts normally, it just yields an empty report. Keep that in mind when
adb_repifaceis changed while a filter is set, the active filter is part of the runtime status
External adblock test
In addition to the built‑in DNS reporting and GeoIP map, adblock users can verify the effectiveness of their configuration with an external test page. The Adblock Test provides a simple way to check whether your current adblock setup is working as expected. It loads a series of test elements (ads, trackers, and other resources) and reports whether they are successfully blocked by your configuration.
The test runs entirely in the browser and does not require additional configuration. For best results, open the page in the same environment where adblock is active and review the results displayed.
Firewall‑Based DNS Control
adblock provides several advanced firewall‑integrated features that allow you to enforce DNS policies directly at the network layer. These mechanisms operate independently of the local DNS resolver and ensure that DNS traffic follows your filtering rules, even when clients attempt to bypass them.
- Unfiltered external DNS Routing: routes DNS queries from selected devices or interfaces to an external unfiltered DNS resolver
- Filtered external DNS Routing: routes DNS queries from selected devices or interfaces to an external filtered DNS resolver
- Force DNS: blocks or redirects all external DNS traffic to ensure that clients use the local resolver
The DNS routing allows you to apply external DNS (unfiltered and/or filtered) to specific devices or entire network segments. DNS queries from these targets are transparently redirected to a chosen external resolver (IPv4 and/or IPv6):
- MAC‑based targeting for individual devices
- Interface/VLAN targeting for entire segments
- Separate IPv4/IPv6 resolver selection
- Transparent DNS redirection without client‑side configuration
This mode is ideal for guest networks, IoT devices, or environments where certain clients require stricter/lesser DNS filtering.
force DNS ensures that all DNS traffic on your network by specific devices or entire network segments is processed by the local resolver. Any attempt to use external DNS servers is blocked or redirected.
- Blocks external DNS on port 53 and redirects DNS queries to the local resolver when appropriate
- Also prevents DNS bypassing by clients with hardcoded DNS settings on other ports, e.g. on port 853
This mode guarantees that adblock’s filtering pipeline is always applied.
adblock's firewall rules are based on nftables in a separate isolated nftables table (inet adblock) and chains (prerouting), with MAC addresses stored in a nftables set. The configuration is carried out centrally in LuCI on the ‘Firewall Settings’ tab in adblock.
Remote DNS Allow (Temporary MAC‑Based Bypass)
This additional firewall feature lets selected client devices temporarily bypass local DNS blocking and use an external, unfiltered DNS resolver. It is designed for situations where a device needs short‑term access to content normally blocked by the adblock rules.
A lightweight CGI endpoint handles the workflow:
- The client opens the URL, e.g. http(s)://<ROUTER-IP>/cgi-bin/adblock (preferably transferred via QR code shown in LuCI)
- The script automatically detects the device’s MAC address
- If the MAC is authorized, the script displays the current status:
- Not in the nftables set → option to request a temporary allow (“Bypass”)
- Already active → shows remaining timeout
- When renewing, the CGI adds the MAC to an nftables Set with a per‑entry timeout
The CGI interface is mobile‑friendly and includes a LuCI‑style loading spinner during the renew process, giving immediate visual feedback while the nftables entry is created. All operations are atomic and safe even when multiple devices renew access in parallel.
Temporary DNS Bridging (Zero‑Downtime during DNS Restarts)
Adblock can optionally enable a temporary DNS bridging mode to avoid DNS downtime during DNS backend restarts.
When this feature is enabled, all DNS queries from LAN clients are briefly redirected to an external fallback resolver until the local DNS backend becomes available again. This ensures that DNS resolution continues to work seamlessly for all clients, even while adblock reloads blocklists or restarts the DNS service. Just set the options adb_nftbridge, adb_bridgednsv4 and adb_bridgednsv6 accordingly.
Jail mode (allowlist-only):
Enforces a strict allowlist‑only DNS policy in which only domains listed in the allowlist file are resolved, while every other query is rejected. This mode is intended for highly restrictive environments and depends on a carefully maintained allowlist, typically managed manually.
Download options
By default adblock uses the following pre-configured download options:
* curl: --connect-timeout 20 --retry-delay 10 --retry 4 --retry-max-time 80 --retry-all-errors --fail --silent --show-error --location -o
* wget: --no-cache --no-cookies --timeout=20 --waitretry=10 --tries=5 --retry-connrefused -O
* uclient-fetch: --timeout=20 -OTo override the default set adb_fetchretry, adb_fetchinsecure or globally adb_fetchparm to your needs.
Enable E-Mail notification via msmtp:
To use the email notification you have to install & configure the package msmtp.
Modify the file /etc/msmtprc:
[...]
defaults
auth on
tls on
tls_certcheck off
timeout 5
syslog LOG_MAIL
[...]
account adb_notify
host smtp.gmail.com
port 587
from [email protected]
user dev.adblock
password xxx
Finally enable E-Mail support, add a valid E-Mail receiver address in LuCI and setup an appropriate cron job.
Automatic adblock feed updates and E-Mail reports
For a regular, automatic update of the used feeds or other regular adblock tasks set up a cron job. Use reload here — start/restart would only restore the backups instead of fetching fresh feeds. In LuCI you find the cron settings under System => Scheduled Tasks. On the command line the cron file is located at /etc/crontabs/root:
Example 1
# update the adblock feeds every morning at 4 o'clock
00 04 * * * /etc/init.d/adblock reloadExample 2
# update the adblock feeds every hour
0 */1 * * * /etc/init.d/adblock reloadExample 3
# send an adblock E-Mail report every morning at 3 o'clock
00 03 * * * /etc/init.d/adblock report mailChange/add adblock feeds
The adblock default blocklist feeds are stored in an external JSON file /etc/adblock/adblock.feeds. This file is shipped with the package and is overwritten on every package update, so never edit it directly. All of your custom changes belong in the separate JSON file /etc/adblock/adblock.custom.feeds (empty by default), which is preserved across updates. It's recommended to use the LuCI based Custom Feed Editor (Custom Feed Editor tab), which validates the JSON for you.
Please note: if /etc/adblock/adblock.custom.feeds exists and is non-empty, it is loaded instead of the shipped adblock.feeds — it replaces the feed set, it does not merge with it. A custom feed file must therefore contain every feed you want active, not just your additions. The Custom Feed Editor handles this for you by working on a full copy.
A feed is a single JSON object, keyed by a unique feed name (no spaces, no special characters). Example:
[...]
"stevenblack": {
"url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/",
"rule": "feed 0.0.0.0 2",
"size": "VAR",
"descr": "compilation"
},
[...]The object supports the following fields:
| Field | Required | Description |
|---|---|---|
| url | yes | download URL of the domain list (for category feeds: the base URL, see below) |
| rule | yes | the parsing ruleset, max. 4 space separated parameters (see below) |
| size | yes | size hint shown in LuCI: S, M, L, XL, XXL or VAR (see the feed table legend in Main Features) |
| descr | yes | a short human-readable description shown in LuCI and the feed table |
The rule field
The rule consists of max. 4 individual, space separated parameters:
- type: always
feed(required). adblock only supports thefeedtype for external sources; any source whose rule does not start withfeedis skipped. - prefix: an optional search term (a literal string, not a regex) that a line must contain to be treated as a valid entry. Use it to pick the relevant rows of a hosts-style file, e.g.
0.0.0.0. Omit it for a plain list with one bare domain per line. - column: the 1-based column that holds the domain within a matching line, e.g.
2for a0.0.0.0 example.comhosts file or1for a bare list (required). When no prefix is used, give the column directly, e.g.feed 1. - separator: an optional field separator; default is the whitespace character class
[[:space:]]+. Pass a literal character such as,for comma-separated sources.
Examples:
feed 1— plain list, one domain per line, domain in column 1feed 0.0.0.0 2— hosts-style file, keep only0.0.0.0lines, domain in column 2feed 1 ,— comma-separated source, domain in the first field
Category-based feeds (size VAR)
The feeds marked VAR in the feed table (1hosts, hagezi, ipfire_dbl, stevenblack, utcapitole) are built-in feeds that require an additional category selection via the dedicated options adb_hst_feed, adb_hag_feed, adb_ipf_feed, adb_stb_feed and adb_utc_feed (or the LuCI feed configuration). For these, the url is a base URL to which the selected category is appended at download time. This category mechanism is wired to those specific feed names in the backend, so it cannot be reused for arbitrary new feeds — a custom feed you add yourself should point url at a single, complete list URL.
After editing /etc/adblock/adblock.custom.feeds, reload adblock (/etc/init.d/adblock reload) and check the Log View tab (or logread -e adblock-). With adb_debug enabled, a malformed JSON object or a wrong column/separator typically shows up there as a feed that produces zero domains.
Troubleshooting & debug options
Adblock provides an optional debug mode that writes diagnostic information to the system log and captures internal error output in a dedicated error logfile - by default located in the adblock base directory as /tmp/adb_error.log. The log file is automatically cleared at the beginning of each run. Under normal conditions, all error messages are discarded to keep regular runs clean and silent. To enable debug mode, set the option adb_debug to 1. When enabled, the script produces significantly more log output to assist with troubleshooting.
Whenever you encounter adblock related processing problems, please enable debug logging, restart adblock and check the Log View tab in LuCI (or the syslog via logread -e adblock-).
Support
Please join the adblock discussion in this forum thread or contact me by mail [email protected]
Removal
Stop all adblock related services with /etc/init.d/adblock stop and remove the adblock package if necessary, e.g.:
/etc/init.d/adblock stop
apk del luci-app-adblock adblock
rm -f /etc/config/adblock
rm -rf /tmp/luci-*Donations
You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time.
If you still insist to donate some bucks ...
- I would be happy if you put your money in kind into other, social projects in your area, e.g. a children's hospice
- Let's meet and invite me for a coffee if you are in my area, the “Markgräfler Land” in southern Germany or in Switzerland (Basel)
- Send your money to my PayPal account and I will collect your donations over the year to support various social projects in my area
No matter what you decide - thank you very much for your support!
Have fun!
Dirk
Overview (`net/apfree-wifidog/README.md`)
ApFree WiFiDog: A high performance captive portal solution for HTTP(s)
ApFree WiFiDog is a high performance captive portal solution for HTTP(s), which mainly used in (LEDE & OpenWrt) platform.
Enhancement of apfree-wifidog
In fact, the title should be why we choose apfree-wifidog, the reason was the following:
Stable
apfree-wifidog was widely used in tens of thousands device, which were running in business scene. In order to improve its stable, we rewrite all iptables rule by api instead of fork call, which will easily cause deadlock in multithread-fork running environment. we also re-write the code and replace libhttpd (which is unmaintained for years) with libevent
Performance
apfree-wifidog's http request-response is more quick, u can find statistic data in our test document
HTTPs redirect
apfree-wifidog support https redirect, in current internet environment, captive portal solution without supporting https redirect will become unsuitable gradually
More features
apfree-wifidog support mac temporary-pass, ip,domain,pan-domain,white-mac,black-mac rule and etc. all these rules can be applied without restarting wifidog
MQTT support
by enable mqtt support, u can remotely deliver such as trusted ip, domain and pan-domain rules to apfree wifidog
Compilable with wifidog protocol
u don't need to modify your wifidog authentication server to adapt apfree-wifidog; if u have pression on server-side, apfree wifidog's improved protocol can greatly relieve it, which disabled by default
Getting started
before starting apfree-wifidog, we must know how to configure it. apfree-wifidog use OpenWrt standard uci config system, all your apfree-wifidog configure information stored in /etc/confg/wifidogx, which will be parsed by /etc/init.d/wifidogx to /tmp/wifidog.conf, apfree-wifidog's real configure file is /tmp/wifidog.conf
The default apfree-wifidog UCI configuration file like this:
config wifidog
option gateway_interface 'br-lan'
option auth_server_hostname 'wifidog.kunteng.org.cn'
option auth_server_port 443
option auth_server_path '/wifidog/'
option check_interval 60
option client_timeout 5
option apple_cna 1
option thread_number 5
option wired_passed 0
option enable 0auth_server_hostname was apfree-wifidog auth server, it can be domain or ip; wifidog.kunteng.org.cn is a free auth server we provided, it was also open source
apple_cna 1 apple captive detect deceive; 2 apple captive detect deceive to disallow portal page appear
wired_passed means whether LAN access devices need to auth or not, value 1 means no need to auth
enable means whether start apfree-wifidog when we executed
/etc/init.d/wifidogx start, if u wanted to start apfree-wifidog, you must set enable to 1 before executing/etc/init.d/wifidogx start
How to support https redirect
In order to support https redirect, apfree-wifidog need x509 pem cert and private key, u can generate yourself like this:
PX5G_BIN="/usr/sbin/px5g"
OPENSSL_BIN="/usr/bin/openssl"
APFREE_CERT="/etc/apfree.crt"
APFREE_KEY="/etc/apfree.key"
generate_keys() {
local days bits country state location commonname
# Prefer px5g for certificate generation (existence evaluated last)
local GENKEY_CMD=""
local UNIQUEID=$(dd if=/dev/urandom bs=1 count=4 | hexdump -e '1/1 "%02x"')
[ -x "$OPENSSL_BIN" ] && GENKEY_CMD="$OPENSSL_BIN req -x509 -sha256 -outform pem -nodes"
[ -x "$PX5G_BIN" ] && GENKEY_CMD="$PX5G_BIN selfsigned -pem"
[ -n "$GENKEY_CMD" ] && {
$GENKEY_CMD \
-days ${days:-730} -newkey rsa:${bits:-2048} -keyout "${APFREE_KEY}.new" -out "${APFREE_CERT}.new" \
-subj /C="${country:-CN}"/ST="${state:-localhost}"/L="${location:-Unknown}"/O="${commonname:-ApFreeWiFidog}$UNIQUEID"/CN="${commonname:-ApFreeWiFidog}"
sync
mv "${APFREE_KEY}.new" "${APFREE_KEY}"
mv "${APFREE_CERT}.new" "${APFREE_CERT}"
}
}or when u start /etc/init.d/wifidogx start, it will generate it automatically
For more information, please refer to the upstream project page
Overview (`net/banip/README.md`)
banIP - ban incoming and outgoing IP addresses/subnets via Sets in nftables
Table of Contents
- Description
- Quick Start
- Main Features
- Prerequisites
- Installation and Usage
- banIP CLI interface
- banIP config options
- Examples
- Best practise and tweaks
- Troubleshooting & debug options
- Support
- Removal
- Donations
Description
IP address blocking is commonly used to protect against brute force attacks, prevent disruptive or unauthorized address(es) from access or it can be used to restrict access to or from a particular geographic area — for example. Further more banIP scans the log file via logread and bans IPs that make too many password failures, e.g. via ssh.
Quick Start
For a typical setup these few steps are enough to get banIP up and running — see the sections below for details:
- Install the LuCI companion package:
apk update && apk add luci-app-banip(this pulls in thebanipbackend as a dependency). - Open LuCI under
Services → banIP, tickEnabledand (recommended) set aStartup Trigger Interfaceto your WAN interface(s). - Activate a small, sensible feed selection to start with, e.g.
cinsscore,debl,turrisanddohin their default chains (≈20K IPs). - Start and verify the service:
/etc/init.d/banip start
/etc/init.d/banip statusPlease note: don't blindly enable (too) many feeds at once — on low memory devices this will sooner or later lead to OOM conditions.
Main Features
banIP supports the following fully pre-configured IP blocklist feeds (free for private usage, for commercial use please check their individual licenses).
Please note: By default, each feed blocks the packet flow in the chain(s) shown in the table below. Inbound combines the chains WAN-Input and WAN-Forward, Outbound represents the LAN-FWD chain:- WAN-INP chain applies to packets from internet to your router
- WAN-FWD chain applies to packets from internet to other local devices (not your router)
- LAN-FWD chain applies to local packets going out to the internet (not your router)
How to read the default direction: most reputation feeds list the source IPs of attackers, scanners and spammers — those are blocked inbound, because the unwanted connection is initiated from the internet towards you. A second group of feeds lists IPs that your own clients should never talk to (malware hosting, command-and-control servers, threat/DoH/DNS endpoints); those are blocked outbound in the LAN-FWD chain, because the connection is initiated from your LAN. Feeds where both risks apply (e.g. spamhaus, emerging threats, tor, proxy, vpn) default to both directions. This distinction matters: a feed of malware-download or C2 IPs in the inbound chain alone provides little protection, because your LAN client is the one initiating the connection — see the
country/asnnote in the Best practise section for the common "block a country" case.The listed standard assignments can be changed to your needs under the
Feed/Set Settingsconfig tab (optionsban_feedin,ban_feedout,ban_feedinoutandban_feedreset).
| Feed | Focus | Inbound | Outbound | Proto/Port | Information |
|---|---|---|---|---|---|
| asn | ASN segments | x | Link | ||
| backscatterer | backscatterer IPs | x | Link | ||
| becyber | malicious attacker IPs | x | Link | ||
| binarydefense | binary defense banlist | x | Link | ||
| bogon | bogon prefixes | x | Link | ||
| bruteforceblock | bruteforceblocker IPs | x | Link | ||
| country | country blocks | x | Link | ||
| cinsscore | suspicious attacker IPs | x | Link | ||
| debl | fail2ban IP blacklist | x | Link | ||
| dns | public DNS-Server | x | tcp, udp: 53, 853 | Link | |
| doh | public DoH-Server | x | tcp, udp: 80, 443 | Link | |
| dshield | dshield IP blocklist | x | Link | ||
| etcompromised | ET compromised hosts | x | Link | ||
| feodo | feodo tracker | x | x | Link | |
| firehol1 | firehol level 1 compilation | x | Link | ||
| firehol2 | firehol level 2 compilation | x | Link | ||
| firehol3 | firehol level 3 compilation | x | Link | ||
| firehol4 | firehol level 4 compilation | x | Link | ||
| greensnow | suspicious server IPs | x | Link | ||
| hagezi | Threat IP blocklist | x | tcp, udp: 80, 443 | Link | |
| ipblackhole | blackhole IPs | x | Link | ||
| ipexdbl | IPEX dynamic blocklists | x | Link | ||
| ipsum | malicious IPs | x | Link | ||
| ipthreat | hacker and botnet IPs | x | Link | ||
| myip | real-time IP blocklist | x | Link | ||
| proxy | open proxies | x | x | Link | |
| spamhaus | Spamhaus DROP | x | x | Link | |
| threat | emerging threats | x | x | Link | |
| threatview | malicious IPs | x | x | Link | |
| tor | tor exit nodes | x | x | Link | |
| turris | turris sentinel blocklist | x | Link | ||
| uceprotect1 | spam protection level 1 | x | Link | ||
| uceprotect2 | spam protection level 2 | x | Link | ||
| uceprotect3 | spam protection level 3 | x | Link | ||
| urlhaus | urlhaus IDS IPs | x | tcp, udp: 80, 443 | Link | |
| urlvir | malware related IPs | x | tcp, udp: 80, 443 | Link | |
| webclient | malware related IPs | x | tcp, udp: 80, 443 | Link | |
| voip | VoIP fraud blocklist | x | Link | ||
| vpn | vpn IPs | x | x | Link | |
| vpndc | vpn datacenter IPs | x | x | Link |
- Zero-conf like automatic installation & setup, usually no manual changes needed
- All Sets are handled in a separate nft table/namespace
banIP - Full IPv4 and IPv6 support
- Supports nft atomic Set loading
- Supports blocking by ASN numbers and by iso country codes
- Block countries dynamically by Regional Internet Registry (RIR), e.g. all countries related to ARIN. Supported service regions are: AFRINIC, ARIN, APNIC, LACNIC and RIPE
- Supports local allow- and blocklist with MAC/IPv4/IPv6 addresses or domain names
- Supports concatenation of local MAC addresses with IPv4/IPv6 addresses, e.g. to enforce dhcp assignments
- All local input types support ranges in CIDR notation
- Auto-add the uplink subnet or uplink IP to the local allowlist
- Prevent common ICMP, UDP and SYN flood attacks and drop spoofed tcp flags & invalid conntrack packets (DoS attacks) in an additional prerouting chain
- Provides a background log monitor to ban unsuccessful login attempts in real-time (like fail2ban, crowdsec etc.) with three-tier IP deduplication, dynamic cache management and optional RDAP-based subnet blocking
- Auto-add unsuccessful LuCI, Asterisk or ssh login attempts to the local blocklist
- Auto-add entire subnets to the blocklist Set based on an additional RDAP request with the monitored suspicious IP
- Fast feed processing as they are handled in parallel as background jobs (on capable multi-core hardware)
- Per feed it can be defined whether the inbound chain (wan-input, wan-forward) or the outbound chain (lan-forward) should be blocked
- Automatic blocklist backup & restore, the backups will be used in case of download errors or during startup
- Automatically selects one of the following download utilities with ssl support: curl, uclient-fetch or full wget
- Provides HTTP ETag support to download only resources that have been updated on the server side, to speed up banIP reloads and to save bandwidth
- Supports an
allowlist onlymode, this option restricts the internet access only to specific, explicitly allowed IP segments - Optionally screens the allowed IP segments in
allowlist onlymode as well, i.e. the log monitor may ban attackers from an explicitly allowed segment while the local allowlist entries stay exempt (seeban_monitorallowed) - Supports external allowlist URLs to reference additional IPv4/IPv6 feeds
- Optionally always allow certain protocols/destination ports in the inbound chain
- Deduplicate IPs across all Sets (single IPs only, no intervals)
- Implements BCP38 ingress filtering to prevent IP address spoofing
- Provides comprehensive runtime information
- Provides a detailed Set report, incl. a map that shows the geolocation of your own uplink addresses (in green) and the location of potential attackers (in red), drawn from local country outlines without any tile service
- Provides a Set search engine for certain IPs
- Feed parsing by fast & flexible regex rulesets
- Minimal status & error logging to syslog, enable debug logging to receive more output
- Procd based init system support (start/stop/restart/reload/status/report/search/content)
- Procd network interface trigger support
- Add new or edit existing banIP feeds on your own with the LuCI integrated custom feed editor
- Supports destination port & protocol limitations for external feeds (see the feed list above). To change the default assignments just use the custom feed editor
- Supports allowing / blocking of certain VLAN forwards
- Provides an option to transfer logging events on remote servers via cgi interface
Prerequisites
- OpenWrt, latest stable release or a development snapshot with nft/firewall 4 support
- A download utility with SSL support:
curl, fullwgetoruclient-fetchwith one of thelibustream-*SSL libraries, the latter one doesn't provide support for ETag HTTP header - A certificate store like
ca-bundle, as banIP checks the validity of the SSL certificates of all download sites by default - For E-Mail notifications you need to install and setup the additional
msmtppackage
Please note:
- Devices with less than 256MB of RAM are not supported
- After system upgrades it's recommended to start with a fresh banIP default config
- Only
reloadactually refreshes the feeds (ETag check plus download of changed feeds).start,restart— andboot/resume— restore the existing blocklist backups and only download feeds that have no backup yet; they do not re-fetch already cached feeds. To update your blocklists (e.g. from a cron job) always usereload.
Installation and Usage
- Update your router's apk repository (apk update)
- Install the LuCI companion package
luci-app-banipwhich also installs the main banIP package as a dependency - Enable the banIP system service (System -> Startup) and enable banIP itself (banIP -> General Settings)
- It's strongly recommended to use the LuCI frontend to easily configure all aspects of banIP, the application is located in LuCI under the
Servicesmenu - It's also recommended to configure a
Startup Trigger Interfaceto depend on your WAN interface events during boot or restart of your router. Listing IPv6 interfaces (wan6) is fine as well: an interface event that only changed the uplink addresses refreshes the auto-allowed uplink entries in place, without a full banIP restart. This keeps the allowlist in sync with a dynamic IPv6 prefix - To be able to use banIP in a meaningful way, you must activate the service and possibly also activate a few blocklist feeds
- If you're using a complex network setup, e.g. special tunnel interfaces, then untick the
Auto Detectionoption under theGeneral Settingstab and set the required options manually - Start the service with
/etc/init.d/banip startand check everything is working by running/etc/init.d/banip status, also check theProcessing Logtab
banIP CLI interface
- All important banIP functions are accessible via CLI, too. If you're going to configure banIP via CLI, edit the config file
/etc/config/banipand enable the service, add pre-configured feeds and add/change other options to your needs, see the options reference table below.
~# /etc/init.d/banip
Syntax: /etc/init.d/banip [command]
Available commands:
start Start the service
stop Stop the service
restart Restart the service
reload Reload configuration files (or restart if service does not implement reload)
enable Enable service autostart
disable Disable service autostart
enabled Check if service is started on boot
report [text|json|mail|gen] Print banIP related Set statistics
search [<IPv4 address>|<IPv6 address>] Check if an element exists in a banIP Set
content [<Set name>] [true|false] Listing of all or only elements with hits of a given banIP Set
running Check if service is running
status Service status
trace Start with syscall trace
info Dump procd service infoThe report sub-command accepts an output mode: text (default, human-readable table), json (machine-readable output, incl. GeoIP map data when ban_map=1), mail (send the report via msmtp) and gen (regenerate the report data files in the background, used by the LuCI frontend).
banIP config options
| Option | Type | Default | Description |
|---|---|---|---|
| ban_enabled | option | 0 | enable the banIP service |
| ban_nicelimit | option | 0 | ulimit nice level of the banIP service (range 0-19) |
| ban_filelimit | option | 1024 | ulimit max open/number of files (range 1024-4096) |
| ban_loglimit | option | 100 | scan only the last n log entries permanently. A value of 0 disables the monitor |
| ban_logcount | option | 1 | how many times the IP must appear in the log per blocking cycle to trigger auto-blocking |
| ban_logterm | list | regex | various regex for logfile parsing (default: dropbear, sshd, luci, asterisk and cgi-remote events) |
| ban_logreadfile | option | - / logread | parse this log file via tail instead of the default logread; by default banIP reads the system log via logread |
| ban_autodetect | option | 1 | auto-detect wan interfaces, devices and subnets |
| ban_debug | option | 0 | enable banIP related debug logging |
| ban_icmplimit | option | 25 | threshold in number of packets to detect icmp DoS in prerouting chain. A value of 0 disables this safeguard |
| ban_synlimit | option | 10 | threshold in number of packets to detect syn DoS in prerouting chain. A value of 0 disables this safeguard |
| ban_udplimit | option | 100 | threshold in number of packets to detect udp DoS in prerouting chain. A value of 0 disables this safeguard |
| ban_logprerouting | option | 0 | log suspicious packets in the prerouting chain |
| ban_loginbound | option | 0 | log suspicious packets in the inbound chain (wan-input and wan-forward) |
| ban_logoutbound | option | 0 | log suspicious packets in the outbound chain (lan-forward) |
| ban_logratelimit | option | 10 | rate (per second) for the shared nft log limit, applied globally across all logged rules |
| ban_logburstlimit | option | 5 | burst size in packets for the shared nft log limit |
| ban_autoallowlist | option | 1 | add wan IPs/subnets and resolved domains automatically to the local allowlist (not only to the Sets) |
| ban_autoblocklist | option | 1 | add suspicious attacker IPs and resolved domains automatically to the local blocklist (not only to the Sets) |
| ban_autoblocksubnet | option | 0 | add entire subnets to the blocklist Sets based on a rate-limited, non-blocking RDAP lookup for the suspicious IP |
| ban_autoallowuplink | option | subnet | limit the uplink autoallow function to: subnet, ip or disable it at all |
| ban_allowlistonly | option | 0 | restrict the internet access only to specific, explicitly allowed IP segments |
| ban_monitorallowed | option | 0 | restrict the log monitor to the entries of the local allowlist (allowlist-only mode) |
| ban_allowflag | option | - | always allow certain protocols(tcp or udp) plus destination ports or port ranges, e.g.: tcp 80 443-444 |
| ban_allowurl | list | - | external allowlist feed URLs, one or more references to simple remote IP lists |
| ban_basedir | option | /tmp | base working directory while banIP processing |
| ban_reportdir | option | /tmp/banIP-report | directory where banIP stores report files |
| ban_backupdir | option | /tmp/banIP-backup | directory where banIP stores compressed backup files |
| ban_errordir | option | /tmp/banIP-error | directory where banIP stores processing error files |
| ban_protov4 | option | - / autodetect | enable IPv4 support |
| ban_protov6 | option | - / autodetect | enable IPv6 support |
| ban_ifv4 | list | - / autodetect | logical wan IPv4 interfaces, e.g. wan |
| ban_ifv6 | list | - / autodetect | logical wan IPv6 interfaces, e.g. wan6 |
| ban_dev | list | - / autodetect | wan device(s), e.g. eth2 |
| ban_vlanallow | list | - | always allow certain VLAN forwards, e.g. br-lan.20 |
| ban_vlanblock | list | - | always block certain VLAN forwards, e.g. br-lan.10 |
| ban_trigger | list | - | logical reload trigger interface(s), e.g. wan and wan6 |
| ban_triggerdelay | option | 20 | trigger timeout during interface reload and boot |
| ban_deduplicate | option | 1 | deduplicate IP addresses across all active Sets (see optional feed flag dup below) |
| ban_splitsize | option | 0 | split the processing/loading of Sets in chunks of n lines/members (saves RAM) |
| ban_cores | option | - / autodetect | limit the cpu cores used by banIP; only auto-detection is memory-capped |
| ban_nftloglevel | option | warn | nft loglevel, values: emerg, alert, crit, err, warn, notice, info, debug |
| ban_nftpriority | option | -100 | nft priority for the banIP table (the prerouting table is fixed to priority -150) |
| ban_nftpolicy | option | memory | nft policy for banIP-related Sets, values: memory, performance |
| ban_nftexpiry | option | - | expiry time (ms |
| ban_nftretry | option | 3 | number of Set load attempts in case of an error |
| ban_nftcount | option | 0 | enable nft counter for every Set element |
| ban_bcp38 | option | 0 | block packets with spoofed source IP addresses in all supported chains |
| ban_map | option | 0 | enable a GeoIP Map with suspicious Set elements |
| ban_feed | list | - | external download feeds, e.g. yoyo, doh, country or talos (see feed table) |
| ban_asn | list | - | ASNs for the asn feed, e.g.32934 |
| ban_asnsplit | option | - | the selected ASNs are stored in separate Sets |
| ban_region | list | - | Regional Internet Registry (RIR) country selection. Supported regions are: AFRINIC, ARIN, APNIC, LACNIC and RIPE |
| ban_country | list | - | country iso codes for the country feed, e.g. ru |
| ban_countrysplit | option | - | the selected countries are stored in separate Sets |
| ban_blockpolicy | option | drop | drop packets silently on input and forwardwan chains or actively reject the traffic |
| ban_feedin | list | - | limit the selected feeds to the inbound chain (wan-input and wan-forward) |
| ban_feedout | list | - | limit the selected feeds to the outbound chain (lan-forward) |
| ban_feedinout | list | - | set the selected feeds to the inbound and outbound chain (lan-forward) |
| ban_feedreset | list | - | override the default feed configuration and remove existing port/protocol limitations |
| ban_feedcomplete | list | - | opt out the selected feeds from the deduplication process |
| ban_fetchcmd | option | - / autodetect | uclient-fetch, wget or curl |
| ban_fetchparm | option | - / autodetect | set the config options for the selected download utility |
| ban_fetchretry | option | 5 | number of download attempts in case of an error (not supported by uclient-fetch) |
| ban_fetchinsecure | option | 0 | don't check SSL server certificates during download |
| ban_mailreceiver | option | - | receiver address for banIP related notification E-Mails |
| ban_mailsender | option | no-reply@banIP | sender address for banIP related notification E-Mails |
| ban_mailtopic | option | banIP notification | topic for banIP related notification E-Mails |
| ban_mailprofile | option | ban_notify | mail profile used in msmtp for banIP related notification E-Mails |
| ban_mailnotification | option | 0 | receive E-Mail notifications with every banIP run |
| ban_resolver | option | - | external resolver used for DNS lookups, by default the local resolver/forwarder will be used |
| ban_remotelog | option | 0 | enable the cgi interface to receive remote logging events |
| ban_remotetoken | option | - | unique token to communicate with the cgi interface |
Examples
banIP report information
/* Detailed source-code truncated for AI context efficiency. */banIP runtime information
~# /etc/init.d/banip status
::: banIP runtime information
+ status : active (nft: ✔, monitor: ✔)
+ frontend_ver : 1.9.0-r1
+ backend_ver : 1.9.0-r1
+ element_count : 111 582 (chains: 7, sets: 21, rules: 50)
+ active_feeds : allowlist.v4MAC, allowlist.v6MAC, allowlist.v4, allowlist.v6, cinsscore.v4, debl.v4, country.v6, debl.v6, doh.v4, doh.v6, spamhaus.v4, ipthreat.v4, country.v4, spamhaus.v6, threat.v4, turris.v4, turris.v6, blocklist.v4MAC, blocklist.v6MAC, blocklist.v4, blocklist.v6
+ active_devices : trigger: wan / wan: pppoe-wan / wan-if: wan, wan_6 / vlan-allow: - / vlan-block: -
+ active_uplink : 46.167.33.169, 2a04:5700:304:ea66:e1fa:9a7f:ff78:ec41
+ nft_info : ver: 1.1.6-r4, priority: -100, policy: performance, loglevel: warn, expiry: 2h, limit (icmp/syn/udp): 25/10/100, loglimit (rate/burst): 10/5
+ run_info : base: /mnt/data/banIP, backup: /mnt/data/banIP/backup, report: /mnt/data/banIP/report, error: /mnt/data/banIP/error
+ run_flags : auto: ✔, proto (4/6): ✔/✔, bcp38: ✔, log (pre/in/out): ✘/✘/✘, count: ✔, dedup: ✔, split: ✘, custom feed: ✘, allowed only: ✘, debug: ✔
+ last_run : mode: restart, date / time: 2026-09-19 15:27:29, duration: 0m 28s, memory: 1122.99 MB available
+ system_info : cores: 4, log: logread, fetch: curl, Bananapi BPI-R3, mediatek/filogic, OpenWrt SNAPSHOT (r36370-5369699684)banIP search information
~# /etc/init.d/banip search 8.8.8.8
:::
::: banIP Search
:::
Looking for IP '8.8.8.8' on 2025-01-13 22:13:36
---
IP found in Set 'country.v4'
IP found in Set 'doh.v4'banIP Set content information
List all elements of a given Set with hit counters, e.g.:
~# /etc/init.d/banip content turris.v4
:::
::: banIP Set Content
:::
List elements of the Set 'turris.v4' on 2025-06-08 23:28:55
---
1.4.228.135, packets: 0
1.23.16.3, packets: 0
1.33.35.42, packets: 0
1.33.231.132, packets: 0
1.34.29.158, packets: 0
1.34.231.106, packets: 0
1.52.91.174, packets: 0
1.64.149.142, packets: 0
1.69.243.13, packets: 0
1.70.139.250, packets: 0
1.70.171.246, packets: 0
1.82.191.114, packets: 0
[...]List only elements with hits of a given Set with hit counters, e.g.:
~# /etc/init.d/banip content turris.v4 true
:::
::: banIP Set Content
:::
List elements of the Set 'turris.v4' on 2025-06-08 23:30:59
---
74.50.211.178, packets: 1
109.205.213.115, packets: 18
109.205.213.123, packets: 35
109.205.213.248, packets: 29
109.205.213.250, packets: 20
109.205.213.252, packets: 30
122.222.152.65, packets: 1
186.91.25.141, packets: 2
190.203.106.113, packets: 2
200.123.238.20, packets: 1Best practise and tweaks
Recommendation for low memory systems
nftables supports the atomic loading of firewall rules (incl. elements), which is cool but unfortunately is also very memory intensive. To reduce the memory pressure on low memory systems (i.e. those with 256-512MB RAM), you should optimize your configuration with the following options:
- point
ban_basedir,ban_reportdir,ban_backupdirandban_errordirto an external usb drive or ssd - set
ban_coresto1(only useful on a multicore system) to force sequential feed processing. The autodetected value is additionally capped to the available memory; a manually set value is always used as-is and is never lowered - set
ban_splitsizee.g. to1024to split the load of an external Set after every 1024 lines/elements - set
ban_nftcountto0to deactivate the CPU- and memory-intensive creation of counter elements at chain / Set level. With this setting, all packet counters are disabled, the Set Reporting will show zero values for these even when the protection rules are actively dropping traffic. Only the DoS protection counters (syn-flood,udp-flood,icmp-flood, etc.) are always enabled.
Sensible choice of blocklists
The following feeds are just my personal recommendation as an initial setup:
- cinsscore, debl, turris and doh in their default chains
In total, this feed selection blocks about 20K IP addresses. It may also be useful to include some countries to the country feed.
Please note: don't just blindly activate (too) many feeds at once, sooner or later this will lead to OOM conditions.
Feed direction (inbound vs. outbound) — and the country/ASN case
This is the single most common source of confusion, so it's worth understanding the model before you pick feeds.
banIP filters on the connection level (source/destination IP), not on content. The default chain of a feed therefore has to match who initiates the unwanted connection:
- Inbound (WAN-Input + WAN-Forward): blocks connections that come from the internet towards you. This is the right direction for the large majority of reputation feeds, which list the source IPs of attackers, scanners, brute-forcers and spam senders (
cinsscore,debl,dshield,firehol*,bruteforceblock,uceprotect*, …). A fail2ban-style feed only makes sense inbound. - Outbound (LAN-Forward): blocks connections your own LAN clients initiate towards a listed IP. This is the right direction for feeds that list destinations your devices should never reach: malware-download hosts, command-and-control servers, DoH/DNS bypass endpoints and threat IPs (
doh,dns,hagezi,feodo,urlhaus,urlvir,webclient). - Both: feeds where the risk genuinely exists in both directions default to
inout(spamhaus,threat,threatview,tor,proxy,vpn,vpndc).
The classic mistake: enabling a malware-URL / C2 feed like urlhaus in the inbound chain and expecting it to stop a LAN client from downloading malware. It won't — the client is the one opening the connection, so only the outbound chain can intercept it. These feeds ship with sensible defaults (outbound or both), but if you maintain your own custom feeds, keep this rule of thumb in mind.
The country/ASN case: the country and asn feeds default to the inbound chain. This blocks traffic from the selected country/ASN towards you — the typical "keep attackers from region X out" use case. If your goal is instead to stop your own clients from reaching a country or ASN (the recurring "block all connections to country X" request), inbound alone does nothing for you. Switch the feed to the outbound or both chain:
- via LuCI:
Feed/Set Settings→ move the feed intoOutboundorInbound + Outbound - via CLI in
/etc/config/banip:
# block outbound connections to the selected countries as well as inbound
list ban_feedinout 'country'
# or restrict the country feed exclusively to the outbound (LAN-FWD) chain
list ban_feedout 'country'Be aware that bidirectional country blocking on large countries quickly inflates element counts and memory usage; consider ban_countrysplit/ban_asnsplit and the low-memory tweaks above.
Log Terms for logfile parsing
Like fail2ban and crowdsec, banIP supports logfile scanning and automatic blocking of suspicious attacker IPs.
In the default config only the log terms to detect failed login attempts via dropbear and LuCI are in place. The following search patterns have been tested as well:
dropbear : 'Exit before auth from'
LuCI : 'luci: failed login'
sshd1 : 'error: maximum authentication attempts exceeded'
sshd2 : 'sshd.*Connection closed by.*\[preauth\]'
asterisk : 'SecurityEvent=\"InvalidAccountID\".*RemoteAddress='
openvpn : 'TLS Error: could not determine wrapping from \[AF_INET\]'
AdGuard : 'AdGuardHome.*\[error\].*/control/login: from ip'
Remote : 'received a suspicious remote IP'Source IP position
For every matched line banIP picks the attacker's address out of the log entry. By default it uses the last IP in the line, which is correct for all login-style formats above: the service always appends the real remote address as the final field, after any user-supplied data (such as a login name). This stays robust even when an attacker puts an IP-like string into a username or similar field.
Some formats instead lead with the source IP — most notably web-server access logs, where the client address is the first field. For those, prefix the term with first::
nginx : 'first:"[A-Z]+ /wp-login\.php[^"]*" 40[13]'The first:/last: prefix is parsed off before matching and applies only to that single term, so terms with different anchors can be mixed freely.
Please note: first: only fits formats where the source IP is genuinely the first address in the line. Do not use it for Suricata fast.log (its leading [1:2013:7] signature id) or netfilter LOG lines (their leading MAC= field), as that leading field would be picked instead of the source — keep those on the default last.
You find the Log Terms option in LuCI under the Log Settings tab. Feel free to add more log terms to meet your needs and protect additional services.
Allow-/Blocklist handling
banIP supports local allow- and block-lists, MAC/IPv4/IPv6 addresses (incl. ranges in CIDR notation) or domain names. These files are located in /etc/banip/banip.allowlist and /etc/banip/banip.blocklist.
Unsuccessful login attempts or suspicious requests will be tracked and added to the local blocklist (see the ban_autoblocklist option). The blocklist behaviour can be further tweaked with the ban_nftexpiry option.
Elements added by the log monitor, including the RDAP ranges of an offending address, expire after ban_nftexpiry, but every new hit within an existing range resets its timeout. As the Sets use auto-merge, overlapping or adjacent ranges are also combined into a single larger element. A network under continuous attack can therefore stay in the blocklist far beyond the configured expiry and grow over time - this is intended behaviour, not a stale entry.
Depending on the options ban_autoallowlist and ban_autoallowuplink the uplink subnet or the uplink IP will be added automatically to local allowlist.
Entries in CIDR notation always refer to the whole network - set host bits are masked by nftables, e.g. 192.168.1.5/24 ends up as 192.168.1.0/24 in the Set and 2001:db8::e65f:1:2:3/64 as 2001:db8::/64. To allow or block a single host omit the prefix length or use /32 resp. /128.
Furthermore, you can reference external Allowlist URLs with additional IPv4 and IPv6 feeds (see ban_allowurl).
Both local lists also accept domain names as input to allow IP filtering based on these names. The corresponding IPs (IPv4 & IPv6) will be extracted and added to the Sets.
Allowlist-only mode
banIP supports an "allowlist only" mode. This option restricts Internet access only to certain, explicitly permitted IP segments - and blocks access to the rest of the Internet. All IPs that are not listed in the allowlist or in the external allowlist URLs are blocked. In this mode it might be useful to limit the allowlist feed to the inbound chain, to still allow outbound communication to the rest of the world.
In this mode the allowlist Sets define the permitted address scope, they do not act as an accept precedence - the blocklist Set is still evaluated and still applies. By default the log monitor nevertheless treats every allowlisted IP as exempt, therefore attackers coming from a permitted segment (e.g. a country-wide external allowlist feed) are never banned. Enable the ban_monitorallowed option to restrict the log monitor to the entries of the local allowlist. banIP then processes an additional local feed which provides the allowlist.local.v4/allowlist.local.v6 Sets, built from /etc/banip/banip.allowlist only - including the automatically added uplink. These Sets are not referenced by any rule, they are solely used by the log monitor: local entries stay exempt, IPs which are merely covered by an external allowlist feed can be banned. This allows a "permit my own country, but still screen it" setup. The option has no effect unless ban_allowlistonly is enabled, and the additional Sets are removed automatically as soon as it is switched off.
External feeds are skipped in this mode as far as they are redundant: the allowlist emits an inverted and therefore terminal drop rule in every chain it covers, so a feed rule in exactly these chains can never match. With the default allowlist direction all external feeds are skipped. If the allowlist is limited to a single chain - e.g. to the inbound chain - only the feeds of that chain are skipped, feeds which also cover the opposite chain are still processed.
MAC/IP-binding
banIP supports concatenation of local MAC addresses/ranges with IPv4/IPv6 addresses, e.g. to enforce dhcp assignments or to free connected clients from outbound blocking.
The following notations in the local allow- and block-list are supported:
MAC-address only:
C8:C2:9B:F7:80:12 => this will be populated to the v4MAC- and v6MAC-Sets with the IP-wildcards 0.0.0.0/0 and ::/0
MAC-address range:
C8:C2:9B:F7:80:12/24 => this populate the MAC-range C8:C2:9B:00:00:00", "C8:C2:9B:FF:FF:FF to the v4MAC- and v6MAC-Sets with the IP-wildcards 0.0.0.0/0 and ::/0
MAC-address with IPv4 concatenation:
C8:C2:9B:F7:80:12 192.168.1.10 => this will be populated only to v4MAC-Set with the certain IP, no entry in the v6MAC-Set
MAC-address with IPv6 concatenation:
C8:C2:9B:F7:80:12 2a02:810c:0:80:a10e:62c3:5af:f3f => this will be populated only to v6MAC-Set with the certain IP, no entry in the v4MAC-Set
MAC-address with IPv4 and IPv6 concatenation:
C8:C2:9B:F7:80:12 192.168.1.10 => this will be populated to v4MAC-Set with the certain IP
C8:C2:9B:F7:80:12 2a02:810c:0:80:a10e:62c3:5af:f3f => this will be populated to v6MAC-Set with the certain IP
MAC-address with IPv4 and IPv6 wildcard concatenation:
C8:C2:9B:F7:80:12 192.168.1.10 => this will be populated to v4MAC-Set with the certain IP
C8:C2:9B:F7:80:12 => this will be populated to v6MAC-Set with the IP-wildcard ::/0MAC-address logging in nftables
The MAC-address logging format in nftables is a little bit unusual. It is generated by the kernel's NF_LOG module and places all MAC-related data into one flat field, without separators or labels. For example, the field MAC=7e:1a:2f:fc:ee:29:68:34:21:1f:a7:b1:08:00 is actually a concatenation of the following:
[Source MAC (6 bytes)] + [Destination MAC (6 bytes)] + [EtherType (2 bytes)]
7e:1a:2f:fc:ee:29 → the source MAC address
68:34:21:1f:a7:b1 → the destination MAC address
08:00 → the EtherType for IPv4 (0x0800)BCP38
BCP38 (Best Current Practice, RFC 2827) defines ingress filtering to prevent IP address spoofing. In practice, this means:
- dropping packets arriving on the WAN whose source address is not valid or routable via that interface
- dropping packets leaving LAN => WAN whose source address does not belong to the local/internal prefixes
In banIP, the BCP38 implementation uses nftables’ FIB lookup to enforce this. It checks whether the packet’s source address is not valid for the incoming interface or whether the routing table reports no route for this source on this interface. Packets that fail this check are dropped.
Reporting Counter behavior
The ban_nftcount option globally controls all Reporting counters in the banIP table — both per-Set element counters and chain rule counters. Default is 0 (disabled) to keep memory usage and CPU overhead low on constrained devices. Only the DoS protection counters (cnt_synflood, cnt_udpflood, cnt_icmpflood, cnt_tcpinvalid, cnt_ctinvalid, cnt_bcp38) become always populated and visible in /etc/init.d/banip report.
When enabled (ban_nftcount=1):
- every Set element gets its own packet/byte counter (memory cost: ~16 bytes per element, so ~1.6 MB for a 100k-element feed)
- all chain rules count packets and bytes
- the GeoIP Map (
ban_map=1) becomes available, as it requires per-element counters to identify hit IPs
When disabled (ban_nftcount=0):
- memory footprint is minimal, suitable for low memory routers
- the Set Reporting still works structurally, but packet counts will all read as zero
- the GeoIP Map cannot be enabled
For most modern routers with ≥512 MB RAM, enabling ban_nftcount is recommended for full visibility into what banIP is actually blocking.
Log Limit / Options
All log rules (prerouting flood protection, inbound and outbound feeds) share a single nft limit named loglimit. The default of 10/second with burst 5 is intentionally aligned with typical kernel printk rate limits, so banIP log messages are not dropped by the kernel. Tune via ban_logratelimit and ban_logburstlimit accordingly if you have adjusted the kernel defaults. Set the ban_logratelimit to 0 to disable rate limiting entirely, e.g. when using ulogd or other userspace log handlers without printk constraints.
Set reporting, enable the GeoIP Map
banIP includes a powerful reporting tool on the Set Reporting tab which shows the latest NFT banIP Set statistics. To get the latest statistics always press the "Refresh" button.
In addition to a tabular overview banIP reporting includes a GeoIP map in a modal popup window/iframe that shows the geolocation of your own uplink addresses (in green) and the locations of potential attackers (in red). To enable the GeoIP Map set the following options (in "Feed/Set Settings" config tab):
- set
ban_nftcountto1to enable the nft counter for every Set element - set
ban_mapto1to include the external components listed below and activate the GeoIP map
To make this work, banIP uses the following external components:
- Leaflet is a lightweight open-source JavaScript library for interactive maps
- The free and quite fast IP Geolocation API to resolve the required IP/geolocation information
The basemap is no longer pulled from a tile service. CARTO started to require an API key for the raster basemaps at basemaps.cartocdn.com and watermarks every unauthenticated tile request, and a key is bound to a single customer, so it cannot be shipped with a package that lands on every installation. banIP therefore draws the basemap from country outlines that come with luci-app-banip: Natural Earth 1:110m, public domain, stripped of all attributes and simplified to 36 kB. No tile service is contacted anymore, but the map page still loads the Leaflet library from unpkg.com (integrity-checked via SRI and requested without cookies), so it needs a WAN connection. The outlines are enough to locate an IP, so the map does not zoom in beyond level 6 and labels the continents rather than the countries.
Optional: a higher detail basemap
The shipped 1:110m outlines are coarse around Scandinavia, the Greek islands and the smaller island states. If you want sharper coastlines, build the 1:50m variant with mapshaper and drop it next to the shipped file. LuCI looks for it on every map run and falls back to the shipped outlines when it is missing, no config option is involved:
curl -sSLo ne50.geojson https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson/ne_50m_admin_0_countries.geojson
mapshaper ne50.geojson -filter-fields -simplify 5% keep-shapes -o force precision=0.01 format=geojson world-50m.json
scp world-50m.json root@openwrt:/www/luci-static/resources/view/banip/The result is roughly 105 kB, about three times the shipped file. Please note: this file is not part of any package, so it is removed on sysupgrade unless you add its path to /etc/sysupgrade.conf, and it stays behind when luci-app-banip is uninstalled.
Please note: the free ip-api.com batch endpoint is rate limited to 15 requests per minute per source IP. Requests beyond that limit are throttled with HTTP 429, and constantly exceeding the limit gets the IP banned for an hour — in both cases the affected part of the map stays empty and banIP logs an info message. To stay below the limit banIP collects the top listed elements of all Sets, deduplicates them and resolves them in batch requests of 100 IPs each (the maximum the endpoint accepts), capped at 15 requests per report run. Elements beyond ~1500 are left out of the map and banIP logs an info message. A report regenerated within 60 seconds of the last geo lookup reuses its map data instead of querying the service again. Please note that other clients behind the same public IP count against the same limit.
CGI interface to receive remote logging events
banIP ships a basic cgi interface in /www/cgi-bin/banip to receive remote logging events (disabled by default). The cgi interface evaluates logging events via GET or POST request (see examples below). To enable the cgi interface set the following options:
- set
ban_remotelogto1to enable the cgi interface - set
ban_remotetokento a secret transfer token, allowed token characters consist of '[A-Za-z]', '[0-9]', '.' and ':' - add the remote logging event to the logterm
Examples to transfer remote logging events from an internal server to banIP via cgi interface:
- POST request:
curl --insecure --data "=<suspicious IP>" https://192.168.1.1/cgi-bin/banip - GET request:
wget --no-check-certificate https://192.168.1.1/cgi-bin/banip?=<suspicious IP>
Please note: for security reasons use this cgi interface only internally and only encrypted via https transfer protocol.
Download options
By default banIP uses the following pre-configured download options:
* curl: --connect-timeout 20 --speed-time 20 --retry-delay 10 --retry 4 --retry-all-errors --fail --silent --globoff --show-error --location -o
* wget: --no-cache --no-cookies --timeout=20 --waitretry=10 --tries=5 --retry-connrefused -O
* uclient-fetch: --timeout=20 -OThe retry-related values shown above are derived from ban_fetchretry (default 5): for curl --retry is ban_fetchretry - 1, for wget --tries equals ban_fetchretry. To override the defaults set ban_fetchretry, ban_fetchinsecure or globally ban_fetchparm to your needs.
Configure E-Mail notifications via msmtp
To use the email notification you must install and configure the package msmtp.
Modify the file /etc/msmtprc, e.g.:
[...]
defaults
auth on
tls on
tls_certcheck off
timeout 5
syslog LOG_MAIL
[...]
account ban_notify
host smtp.gmail.com
port 587
from <address>@gmail.com
user <gmail-user>
password <password>Finally add a valid E-Mail receiver address in banIP.
Send status E-Mails and update the banIP lists via cron job
For a regular, automatic update of the used feeds or other regular banIP tasks (e.g. status mailing) set up a cron job. Use reload here — start/restart would only restore the backups instead of fetching fresh feeds. In LuCI you find the cron settings under System => Scheduled Tasks. On the command line the cron file is located at /etc/crontabs/root:
55 03 * * * /etc/init.d/banip report mail
00 04 * * * /etc/init.d/banip reloadRedirect asterisk security logs to syslog/logread
By default banIP scans the logfile via logread, so to monitor attacks on asterisk, its security log must be available via logread. To do this, edit /etc/asterisk/logger.conf and add the line syslog.local0 = security, then run asterisk -rx reload logger to update the running asterisk configuration.
Change/add banIP feeds and set optional feed flags
The banIP default blocklist feeds are stored in an external JSON file /etc/banip/banip.feeds. This file is shipped with the package and is overwritten on every package update, so never edit it directly. All of your custom changes belong in the separate JSON file /etc/banip/banip.custom.feeds (empty by default), which is preserved across updates. It's recommended to use the LuCI based Custom Feed Editor (Custom Feed Editortab), which validates the JSON for you.
A feed is a single JSON object, keyed by a unique feed name (no spaces, no special characters). Example:
[...]
"doh":{
"url_4": "https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv4.txt",
"url_6": "https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv6.txt",
"rule": "feed 1",
"chain": "out",
"descr": "public DoH-Server",
"flag": "tcp udp 80 443"
},
[...]The object supports the following fields:
| Field | Required | Description |
|---|---|---|
| url_4 | yes* | download URL of the IPv4 list. *at least one of url_4/url_6 must be present |
| url_6 | yes* | download URL of the IPv6 list. May point to the same URL as url_4 if the source mixes IPv4 and IPv6 in one file |
| rule | yes | the parsing ruleset, max. 4 space separated parameters (see below) |
| chain | yes | the default chain/direction: in, out or inout (see below) |
| descr | yes | a short human-readable description shown in LuCI and the feed table |
| flag | no | optional, space separated list of extra options: archive format and/or protocol/port limitations (see below) |
The url_4 / url_6 fields
Each address family is fetched and processed independently. Three cases:
- IPv4-only source: set
url_4only, omiturl_6 - separate IPv4 and IPv6 files: set both to their respective URLs (e.g.
doh,spamhaus) - a single dual-stack file that mixes v4 and v6 entries: point both
url_4andurl_6at that same URL. banIP fetches it only once and process it for each family and the per-family regex extracts the matching addresses; the non-matching lines are simply ignored. (e.g.threatview, which ships v4 and v6 in one file).
The country and asn feeds are special: banIP fetches one file per selected country code / ASN, so their URLs are templates. The placeholder is replaced by the configured value as-is, so country codes have to be lowercase, just like in banip.countries. The placeholder {country} is replaced by the lowercase ISO country code, {asn} by the bare AS number, e.g. https://www.ipdeny.com/ipblocks/data/aggregated/{country}-aggregated.zone or https://asn.ipinfo.app/api/text/list/AS{asn}. A URL without placeholder is treated as a base path and gets the legacy suffix appended (<cc>-aggregated.zone resp. AS<asn>), so older custom feed files keep working. The placeholder can sit anywhere in the URL, which allows to switch the country source to a differently structured one. Example for a GeoLite2-based alternative, which ships IPv4 and IPv6 in one file per country:
"country":{
"url_4": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/{country}.txt",
"url_6": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/{country}.txt",
"rule": "feed 1",
"chain": "in",
"descr": "country blocks (GeoLite2)"
},Pointing url_4 and url_6 at the same file is the dual-stack case described above, so each country file is downloaded only once and processed for both address families. This source includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com - its use is subject to the MaxMind GeoLite End User License Agreement.
Keep in mind that alternative sources come with their own license terms, which you have to check yourself, and that they differ in semantics (geolocation vs. RIR allocation), granularity (set sizes and memory footprint may grow considerably) and code coverage (a missing country file only logs a download failure for that code).
The rule field
The rule consists of max. 4 individual, space separated parameters:
- type:
feedorsuricata(required)feed: a plain IP/CIDR list, one entry per line (the common case)suricata: a Suricata/Snort-style ruleset; banIP extracts the IPs out of the rule lines
- prefix: an optional search term (a literal string, not a regex) that a line must contain to be considered a valid entry. Use it to skip comment/header lines or to pick only the relevant rows. Omit it if every data line is a bare IP.
- column: the 1-based column that holds the IP within a matching line, e.g.
1for a bare list or13for the dshield block file (required) - separator: an optional field separator; default is the whitespace character class
[[:space:]]. Pass a literal character such as,for comma-separated sources (e.g. turris).
The chain field
Defines the default blocking direction. See the Best practise feed-direction note for the reasoning behind each value:
in: inbound only (WAN-Input + WAN-Forward). Use for source-IP reputation feeds (attackers, scanners, spam senders).out: outbound only (LAN-Forward). Use for destination feeds your own clients should never reach (malware hosts, C2, DoH/DNS endpoints).inout: both directions. Use when the risk genuinely exists on both sides (e.g. spamhaus, tor, proxy, vpn).
This is only the default; a user can always override it at runtime per feed via ban_feedin / ban_feedout / ban_feedinout, or strip a feed's port/protocol limitation via ban_feedreset — without touching the feed JSON.
The flag field (optional)
A space separated list of extra options:
gz: the source is gzip-compressed and will be decompressed before parsing (e.g.backscatterer,ipthreat,uceprotect*)- protocol/port limitation — one or both of
tcp/udpfollowed by one or more destination ports or port ranges. This restricts the feed's rules to those destination ports, which is mainly useful for outbound feeds to cut false positives.
Examples:tcp udp 80 443: limit to HTTP/HTTPS (e.g.doh,hagezi,feodo,urlhaus,urlvir,webclient)tcp udp 53 853: limit to plain DNS and DNS-over-TLS (e.g.dns)tcp 22: limit to SSHtcp 5060-5061 udp 5060: mix of single ports and rangesgzand a port limitation can be combined, e.g.gz tcp udp 80 443
After editing /etc/banip/banip.custom.feeds, reload banIP (/etc/init.d/banip reload) and check the Processing Log tab — a malformed JSON object or a wrong column/separator typically shows up there as a feed that loads zero elements.
Troubleshooting & debug options
banIP provides an optional debug mode that writes diagnostic information to the system log and captures internal error output in a dedicated error logfile - by default located in the banIP base directory as /tmp/ban_error.log. The log file is automatically cleared at the beginning of each run. Under normal conditions, all error messages are discarded to keep regular runs clean and silent.
Whenever you encounter banIP related processing problems, please enable Verbose Debug Logging, restart banIP and check the Processing Log tab.
Typical symptoms:
- The nftables initialization failed: untick the
Auto Detectionoption in theGeneral Settingsconfig section and set the required device and tools options manually - A blocklist feed does not work: maybe a temporary server problem or the download URL has been changed. In the latter case, just use the Custom Feed Editor to point this feed to a new URL
In case of a nft processing error, banIP creates an error directory (by default /tmp/banIP-error) with the faulty nft load files.
For further troubleshooting, you can try to load such an error file manually to determine the exact cause of the error, e.g.: nft -f error.file.nft.
Whenever you encounter firewall problems, enable the logging of certain chains in the Log Settings config section, restart banIP and check the Firewall Log tab.
Typical symptoms:
- A feed blocks a legit IP: disable the entire feed or add this IP to your local allowlist and reload banIP
- A feed (e.g. doh) interrupts almost all client connections: check the feed table above for reference and reset the feed to the defaults in the "Feed/Set Settings" config tab section
- The allowlist doesn't free a certain IP/MAC address: check the current content of the allowlist with the "Set Content" under the "Set Reporting" tab to make sure that the desired IP/MAC is listed - if not, reload banIP
Support
Please join the banIP discussion in this forum thread or contact me by mail [email protected]
If you want to report an error, please describe it in as much detail as possible - with (debug) logs, the current banIP status, your banIP configuration, etc.
Removal
Stop all banIP related services with /etc/init.d/banip stop and remove the banip package if necessary.
Donations
You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time.
If you still insist to donate some bucks ...
- I would be happy if you put your money in kind into other, social projects in your area, e.g. a children's hospice
- Let's meet and invite me for a coffee if you are in my area, the “Markgräfler Land” in southern Germany or in Switzerland (Basel)
- Send your money to my PayPal account and I will collect your donations over the year to support various social projects in my area
No matter what you decide - thank you very much for your support!
Have fun!
Dirk
Overview (`net/etherwake-nfqueue/README.md`)
OpenWrt package feed for etherwake-nfqueue
Wake up computers on netfilter match
This repository contains the OpenWrt package feed for
etherwake-nfqueue,
a fork of the etherwake Wake-on-LAN client, with support to send magic
packets only after a queued packet is received from the Linux nfnetlink_queue
subsystem.
When running etherwake-nfqueue on a residential gateway or other type of
router, it can wake up hosts on its network based on packet filtering rules.
For instance, when your set-top box wants to record a TV programme and
tries to access a network share on your NAS, which is in sleep or standby mode,
etherwake-nfqueue can wake up your NAS. Or when you set up port forwarding
to a host on your home network, etherwake-nfqueue can wake up your host
when you try to access it over the Internet.
The documentation below is mostly OpenWrt specific. For more information on
etherwake-nfqueue itself and use case examples, please consult its
Readme.
Building the package
Currently, no pre-built packages are provided. The following assumes that you
already have a working OpenWrt build system for your target device.
If you haven't, you can follow one of these guides:
If you only want to compile packages, and no firmware image:
Build system – Installation and
Using the SDKTo quickly build a firmware image off a development snapshot:
Quick Image Building GuideOr when you are completely new to using build systems:
Beginners guide to building your own firmware
Dependencies
etherwake-nfqueue depends on these OpenWrt packages:
- libnetfilter-queue
- iptables-mod-nfqueue
They will be automatically selected and compiled for you. If they are not
installed on your target device, opkg will try to resolve dependencies with
packages in the repositories.
Adding the package feed
First, you need to add the etherwake-nfqueue package feed to your build
system. In the root directory of your OpenWrt build system, find the file
feeds.conf (or feeds.conf.default if the former shouldn't exist) and add
the following line to it:
src-git ethernfq https://github.com/mister-benjamin/etherwake-nfqueue-openwrt.gitThen update and install the package feed:
user@host:~/openwrt$ scripts/feeds update ethernfq
user@host:~/openwrt$ scripts/feeds install -a -p ethernfqAfter that, enter OpenWrt's configuration menu
user@host:~/openwrt$ make menuconfigand enable etherwake-nfqueue in the Network sub-menu. It can either be selected
as built-in (*) or module (M), depending on your decision to include it into a
firmware image or just build the opkg package for installation.
Then you should be able to compile the package:
user@host:~/openwrt$ make package/etherwake-nfqueue/compileThe path of the resulting package depends on your selected Target System.
In case of the Linksys WRT1200AC, it can be found here:
bin/packages/arm_cortex-a9_vfpv3/ethernfq/etherwake-nfqueue_2019-09-10-67e9d4ca-1_arm_cortex-a9_vfpv3.ipkInstallation
One way to install the package is by simply copying it over to the device with scp:
user@host:~$ scp etherwake-nfqueue_2019-09-10-67e9d4ca-1_arm_cortex-a9_vfpv3.ipk root@gateway:~And then, install it on the device:
root@gateway:~# opkg install etherwake-nfqueue_2019-09-10-67e9d4ca-1_arm_cortex-a9_vfpv3.ipkConfiguration
WoL Targets
After a fresh installation, no target is configured. Targets are referred
to as the hosts to wake up. Multiple targets can coexist.
Targets can be configured with OpenWrt's UCI.
For example, to add a target called nas, with MAC address
00:25:90:00:d5:fd, which is reachable over the VLAN configured
on eth0.3, issue this command sequence on your router:
uci add etherwake-nfqueue target
uci set etherwake-nfqueue.@target[-1].name=nas
uci set etherwake-nfqueue.@target[-1].mac=00:25:90:00:d5:fd
uci set etherwake-nfqueue.@target[-1].interface=eth0.3
uci commitFor each target, one instance of etherwake-nfqueue will be started.
Each instance should bind to a different nfnetlink_queue. A queue can
be referenced by its queue number. Counting starts from 0, which is the default.
To use a different queue, provide the nfqueue_num option. The
following could have been added to the sequence above to use queue 1 instead
of 0:
uci set etherwake-nfqueue.@target[-1].nfqueue_num=1The necessity of a queue number will probably become clear, when the iptables
rules are configured in section Setup firewall rules.
The full list of options for a target is:
| Option | Required | Description |
|---|---|---|
| name | no | Name of the target, e.g. name=example |
| mac | yes | MAC address of the host to wake up, e.g. mac=00:22:44:66:88:aa |
| nfqueue_num | no | The queue number used for receiving filtered packets, default is nfqueue_num=0 |
| interface | no | The interface used for sending the magic packet, default is interface=eth0 |
| broadcast | no | Send magic packet to broadcast address, default is broadcast=off |
| password | no | Set a password (required by some adapters), e.g. password=00:22:44:66:88:aa or 192.168.1.1 |
| enabled | no | Optionally disable the target, default is enabled=true |
After committing your changes, the settings are persisted to
/etc/config/etherwake-nfqueue. This is an illustrative example:
config etherwake-nfqueue 'setup'
option sudo 'off'
option debug 'off'
config target
option name 'nas'
option mac '00:25:90:00:d5:fd'
option interface 'eth0.3'
config target
option name 'xyz-board'
option mac '00:25:90:00:d5:fc'
option nfqueue_num '1'
option enabled 'false'
config target
option name 'ip-camera'
option mac '00:25:90:00:d5:fb'
option nfqueue_num '2'
option interface 'eth0.3'
option broadcast 'on'
option password '00:25:90:00:d5:fb'When all target(s) are configured, restart the etherwake-nfqueue service:
/etc/init.d/etherwake-nfqueue restartSetting up filters
Without any firewall rules which tell the kernel to match and add packets
to a nfnetlink_queue, etherwake-nfqueue will never send out a magic
packet to wake its target.
Prerequisites
In order to let the netfilter framework of the kernel see the packets,
they need to pass through the router. This is usually not the case when
hosts are on the same subnet and don't require network layer routing.
The data will only pass through the router's switch on the link layer.
As a consequence, we can only use packets as a trigger which need to be
routed or bridged by the router. Packets being forwarded between WAN
and LAN are of that type. For other SOHO use cases, partitioning your
network by means of subnets or VLANs might be necessary. The latter
is often used to set up a DMZ.
For VLANs:
There's a mini howto referring to the LuCI Web Interface
(Network -> Switch) way of configuring VLANs:
How-To: Creating an additional virtual switch on a typical home routerThe manual approach is documented here:
VLAN
Guides to setup a DMZ can be found here:
The physical switch layout is device specific. E.g. the layout for the Linksys
WRT AC Series is documented
here.
Using two LANs or VLANs with the same network address and bridging them again
is a trick to setup a transparent (or bridging) firewall on the same subnet.
This way, packets can be seen by netfilter on the router even if the
packets are not routed. Unfortunately this doesn't help when the host
which we want to wake up is offline, as the ARP requests for the destination
IP address are not answered and thus the client trying to reach out to its
destination will not send any network layer packets. We could use arptables
instead to wake the host when someone requests its MAC address, but this
would probably happen too often and no fine-grained control would be possible.
As a workaround, it might be possible to configure a static ARP entry on your
router (untested), e.g. with:
ip neigh add 192.168.0.10 lladdr 00:25:90:00:d5:fd nud permanent dev eth0.3Note that this requires the ip-full OpenWrt package to be installed.
To make your firewall rules work with bridging, you need to install the
kmod-br-netfilter package and add net.bridge.bridge-nf-call-iptables=1
to /etc/sysctl.conf.
Setup firewall rules
One way to setup custom firewall rules in OpenWrt is through its
/etc/firewall.user script. This file can also be edited by means of
the LuCI Web Interface (Network -> Firewall -> Custom Rules).
The file is interpreted as a shell script, so we can simply use iptables
to add our custom firewall rules.
Notice the comment
# Internal uci firewall chains are flushed and recreated on reload, so
# put custom rules into the root chains e.g. INPUT or FORWARD or into the
# special user chains, e.g. input_wan_rule or postrouting_lan_rule.Refer to Packet flow
for usable chains. In the example below, the chains forwarding_lan_rule and
forwarding_wan_rule are used. To inspect the rule sets of the different tables, one can
use
iptables --list # default is --table filter
iptables --table nat --list
iptables --table mangle --list
iptables --table raw --list # requires kmod-ipt-rawThe following is an example of what could be added to /etc/firewall.user:
/* Detailed source-code truncated for AI context efficiency. */In this example, packets are filtered based on the protocol, their input
and output interfaces, their destination (IP address) and their destination
port(s).
The option --match conntrack --ctstate NEW only matches packets of a new
connection and --match limit --limit 3/hour --limit-burst 1 limits the
amount of packets that are matched. The latter option roughly matches
only one packet per 20 minutes. The intention here is to not be too intrusive
and avoid sending a lot of magic packets.
The --jump NFQUEUE --queue-num 0 options tell the netfilter
framework to enqueue a matching packet to the NFQUEUE number 0. In this
example, all four rules send the matching packets into queue 0. The
additional option --queue-bypass helps in the situation, when
etherwake-nfqueue isn't running. Packets will then be handled
as if the rule wasn't present.
Disabling targets
To disable targets, first find their index:
uci show etherwake-nfqueueThen set its enabled option to false and restart the service.
For index 0, it can be done like this:
uci set etherwake-nfqueue.@target[0].enabled=false
/etc/init.d/etherwake-nfqueue restartTroubleshooting
Debug mode
In order to see what's going on in syslog and get some debug output when
starting the service, enable etherwake-nfqueue's debug mode:
uci set etherwake-nfqueue.setup.debug=onIn another user session tail the log:
logread -fAnd then restart the service:
/etc/init.d/etherwake-nfqueue restartInspect netfilter
To inspect the working of your firewall rules, you can print statistics
of the chains you used, e.g.:
iptables --verbose --list forwarding_lan_ruleIf you happen to have the procps-ng-watch package installed, you can watch
them:
watch iptables --verbose --list forwarding_lan_ruleTo see, if your queues are in place, use:
cat /proc/net/netfilter/nfnetlink_queuePotential improvements
- Add LuCI Web Interface configuration frontend for targets and filter rules
- Add an option to set nice values for instances
Overview (`net/family-dns/Readme.md`)
Package: net/family-dns
This package configures your router to block access to adult websites.
The default settings are designed to be appropriate for most businesses, schools, and families.
Enabled and disable Family DNS by editing /etc/config/family-dns. To make
your changes active, run family-dns-update.
- Default DNS Filter
- CleanBrowsing.org Adult Filter
- https://cleanbrowsing.org/filters#adult
- Blocks access to all adult, pornographic and explicit sites. It does not block proxy or VPNs, nor mixed-content sites. Sites like Reddit are allowed. Google and Bing are set to the Safe Mode. Malicious and Phishing domains are blocked.
- CleanBrowsing.org Adult Filter
- Alternate DNS Filters:
- CleanBrowsing.org Family Filter
- https://cleanbrowsing.org/filters#family
- Blocks access to all adult, pornographic and explicit sites. It also blocks proxy and VPN domains that are used to bypass the filters. Mixed content sites (like Reddit) are also blocked. Google, Bing and Youtube are set to the Safe Mode. Malicious and Phishing domains are blocked.
- Cisco Family Shield
- https://www.opendns.com/home-internet-security/
- https://www.opendns.com/setupguide/#familyshield
- FamilyShield will block domains that are categorized as: Tasteless, Proxy/Anonymizer, Sexuality and Pornography.
- CleanBrowsing.org Family Filter
Overview (`net/geoip-shell/OpenWrt-README.md`)
geoip-shell on OpenWrt
Currently geoip-shell fully supports OpenWrt, both with firewall3 + iptables and with firewall4 + nftables, while providing the same user interface and features as on any other Linux system. So usage is the same as described in the main README.md file, while some parts of the backend (namely persistence implementation), some defaults and the location of the data directory are different.
The geoip-shell-iptables package is for firewall3+iptables OpenWrt systems, while the geoip-shell package is for firewall4+nftables OpenWrt systems.
A LuCi interface has not been implemented (yet). As on any other Linux system, all user interface is via a command line (but my goal is to make this an easy experience regardless). If this discourages you from using geoip-shell, please let me know. A few people asking for this feature may motivate me to prioritize it.
Usage after installation via ipk
After installing the ipk package, geoip-shell will be inactive until you configure it. To do so, run geoip-shell configure and follow the interactive setup. You can also run geoip-shell -h before that to find out about configuration options and then append certain options after the configure action, for example: geoip-shell configure -c "de nl" -m whitelist to configure geoip-shell in whitelist mode for countries Germany and Netherlands. The interactive setup will ask you about all the important options but some niche options are only available non-interactively (for example if you want to configure geoblocking for certain selection of ports). You can always change these settings after initial configuration via the same geoip-shell configure command.
Uninstallation of geoip-shell if installed via ipk
- For nftables-based systems:
opkg remove geoip-shell - For iptables-based systems:
opkg remove geoip-shell-iptables
Resources management on OpenWrt
Because OpenWrt typically runs on embedded devices with limited memory and very small flash storage, geoip-shell implements some techniques to conserve these resources as much as possible:
- During installation on OpenWrt, comments and the debug code are stripped from the scripts to reduce their size.
- Only the required modules are installed, depending on the system (iptables- or nftables- based).
- I've researched the most memory-efficient way for loading ip lists into nftables sets. Currently, nftables has some bugs related to this process which may cause unnecessarily high memory consumption. geoip-shell works around these bugs as much as possible.
- To avoid unnecessary flash storage wear, all filesystem-related tasks geoip-shell does which do not require permanent storage are done in the /tmp directory which in the typical OpenWrt installation is mounted on the ramdisk.
- Some defaults on OpenWrt are different to further minimize flash storage wear (read below).
Scripts size
Typical geoip-shell installation on an OpenWrt system currently consumes around 120kB. The distribution folder itself weighs quite a bit more (mainly because of documentation) but you can install via an ipk which doesn't remain in storage after installation, or if installing via the -install script, delete the distribution folder and free up space taken by it. geoip-shell does not install its documentation into the system.
I have some plans to reduce that size by compressing certain scripts which provide user interface and implementing automatic extraction to /tmp when the user wants to access them, but this is not yet implemented.
To view all installed geoip-shell scripts in your system and their sizes, run ls -lh /usr/bin/geoip-shell-* /usr/lib/geoip-shell/*.
Persistence on OpenWrt
- Persistence of geoip firewall rules and ip sets works differenetly on OpenWrt than on other Linuxes, since geoip-shell has an OpenWrt-specific procd init script.
- The cron job which implements persistence on other Linuxes and runs at reboot is not created on OpenWrt.
- geoip-shell integrates into firewall3 or firewall4 via what's called a "firewall include". On OpenWrt, a firewall include is a setting which tells firewall3 or firewall4 to do something specific in response to certain events.
- The only task of the init script for geoip-shell is to call the geoip-shell-mk-fw-include.sh script, which makes sure that the firewall include exists and is correct, if not then creates the include.
- The firewall include is what does the actual persistence work. geoip-shell firewall include triggers on firewall reload (which happens either at reboot or when the system decides that a reload of the firewall is necessary, or when initiated by the user).
- When triggered, the include script calls the -run script with the "restore" action.
- The -run script verifies that geoip nftables/iptables rules and ip sets exist, and if not then it restores them from backup, or (if backup doesn't exist) initiates re-fetch of the ip lists and then re-creates the rules and the ip sets.
- By default, geoip-shell does not create backups on OpenWrt because typically the permanent storage is very small and prone to wear.
- Automatic updates of ip lists on OpenWrt are triggered from a cron job like on other Linuxes.
Defaults for OpenWrt
Generally the defaults are the same as for other systems, except:
- the data directory which geoip-shell uses to store the status file and the backups is by default in
/tmp/geoip-shell-data, rather than in/var/lib/geoip-shellas on other Linux systems. This is to avoid flash wear. You can change this by running the install script with the-aoption, or after installation via the commandgeoip-shell configure -a. - the 'nobackup' option is set to 'true', which configures geoip-shell to not create backups of the ip lists. With this option, geoip-shell will work as usual, except after reboot (and for iptables-based systems, after firewall restart) it will re-fetch the ip lists, rather than loading them from backup. You can change this by running the -install script with the
-o falseoption (-ostands for nobackup), or after installation via the commandgeoip-shell configure -o false. To have persistent ip list backups, you will also need to change the data directory path as explained above. - if using geoip-shell on a router with just a few MB of embedded flash storage, consider either leaving the nobackup and datadir path defaults as is, or connecting an external storage device to your router (preferably formatted to ext4) and configuring a directory on it as your geoip-shell data directory, then enabling automatic backups. For example, if your external storage device is mounted on /mnt/somedevice, you can do all this via this command:
geoip-shell configure -a /mnt/somedevice/geoip-shell-data -o false. - the default ip lists source for OpenWrt is ipdeny (rather than ripe). While ipdeny is a 3rd party, they provide aggregated lists which consume less memory (on nftables-based systems the ip lists are automatically optimized after loading into memory, so there the source does not matter, but a smaller initial ip lists size will cause a smaller memory consumption spike while loading the ip list).
This is about it for this document. Much more information is available in the main README.md and in the extra .md files inside the Documentation directory. If you have any questions, contact me in this thread:
https://forum.openwrt.org/t/geoip-shell-flexible-geoip-blocker-for-linux-now-supports-openwrt/189611
If you use this project, I will be happy to hear about your experience in the above thread. If for some reason geoip-shell is not working for you, I will want to know that as well so I can improve it.
Overview (`net/geoip-shell/README.md`)
**geoip-shell**
Geoip blocker for Linux. Supports both nftables and iptables firewall management utilities.
The idea of this project is making geoip blocking easy on (almost) any Linux system, no matter which hardware, including desktop, server, VPS or router, while also being reliable and providing flexible configuration options for the advanced users.
Supports running on OpenWrt. Supports ipv4 and ipv6.
Table of contents
**Main Features**
Core functionality is creating either a whitelist or a blacklist in the firewall using automatically downloaded ip lists for user-specified countries.
ip lists are fetched either from RIPE (regional Internet registry for Europe, the Middle East and parts of Central Asia) or from ipdeny. Both sources provide updated ip lists for all regions.
All firewall rules and ip sets required for geoip blocking to work are created automatically during installation or setup.
Implements optional (enabled by default) persistence of geoip blocking across system reboots and automatic updates of the ip lists.
After installation, a utility is provided to check geoip status and firewall rules or change country codes and geoip-related config.
**Reliability**:
- Downloaded ip lists go through validation which safeguards against application of corrupted or incomplete lists to the firewall.
Read more:
- With nftables, utilizes nftables atomic rules replacement to make the interaction with the system firewall fault-tolerant and to completely eliminate time when geoip is disabled during an automatic update.
- All scripts perform extensive error detection and handling.
- All user input is validated to reduce the chance of accidental mistakes.
- Verifies firewall rules coherence after each action.
- Automatic backup of geoip-shell state (optional, enabled by default except on OpenWrt).
- Automatic recovery of geoip-shell firewall rules after a reboot (a.k.a persistence) or in case of unexpected errors.
- Supports specifying trusted ip addresses anywhere on the Internet which will bypass geoip blocking to make it easier to regain access to the machine if something goes wrong.
**Efficiency**:
- Utilizes the native nftables sets (or, with iptables, the ipset utility) which allows to create efficient firewall rules with thousands of ip ranges.
Read more:
- With nftables, optimizes geoip blocking for low memory consumption or for performance, depending on the RAM capacity of the machine and on user preference. With iptables, automatic optimization is implemented.
- Ip list parsing and validation are implemented through efficient regex processing which is very quick even on slow embedded CPU's.
- Implements smart update of ip lists via data timestamp checks, which avoids unnecessary downloads and reconfiguration of the firewall.
- Uses the "prerouting" hook in kernel's netfilter component which shortens the path unwanted packets travel in the system and may reduce the CPU load if any additional firewall rules process incoming traffic down the line.
- Supports the 'ipdeny' source which provides aggregated ip lists (useful for embedded devices with limited memory).
- Scripts are only active for a short time when invoked either directly by the user or by the init script/reboot cron job/update cron job.
**User-friendliness**:
- Good command line interface and useful console messages.
Read more:
- Extensive and (usually) up-to-date documentation.
- Sane settings are applied during installation by default, but also lots of command-line options for advanced users or for special corner cases are provided.
- Provides a utility (symlinked to 'geoip-shell') for the user to change geoip config (turn geoip on or off, change country codes, change geoip blocking mode, change ip lists source, change the cron schedule etc).
- Provides a command ('geoip-shell status') to check geoip blocking status, which also reports if there are any issues.
- In case of an error or invalid user input, provides useful error messages to help with troubleshooting.
- All main scripts display detailed 'usage' info when executed with the '-h' option.
- The code should be fairly easy to read and includes a healthy amount of comments.
**Compatibility**:
- Since the project is written in POSIX-compliant shell code, it is compatible with virtually every Linux system (as long as it has the pre-requisites). It even works well on simple embedded routers with 8MB of flash storage and 128MB of memory (for nftables, 256MB is recommended if using large ip lists such as the one for US until the nftables team releases a fix reducing memory consumption).
Read more:
- Supports running on OpenWrt.
- The project avoids using non-common utilities by implementing their functionality in custom shell code, which makes it faster and compatible with a wider range of systems.
**Usage**
If you want to change geoip blocking config or check geoip blocking status, you can do that via the provided utilities.
A selection of options is given here, for additional options run geoip-shell -h or read NOTES.mdand DETAILS.md.
To check current geoip blocking status: geoip-shell status. For a list of all firewall rules in the geoip chain and for a detailed count of ip ranges in each ip list: geoip-shell status -v.
To add or remove ip lists for countries: geoip-shell <add|remove> -c <"country_codes">
Examples:
- example (to add ip lists for Germany and Netherlands):
geoip-shell add -c "DE NL" - example (to remove the ip list for Germany):
geoip-shell remove -c DE
To enable or disable geoip blocking: geoip-shell <on|off>
To change ip lists source: geoip-shell configure -u <ripe|ipdeny>
To change geoip blocking mode: geoip-shell configure -m <whitelist|blacklist>
To have certain trusted ip addresses or subnets bypass geoip blocking: geoip-shell configure -t <["ip_addresses"]|none>. none removes previously set trusted ip addresses.
To have certain LAN ip addresses or subnets bypass geoip blocking: geoip-shell configure -l <["ip_addresses"]|auto|none>. auto will automatically detect LAN subnets (only use this if the machine has no dedicated WAN interfaces). none removes previously set LAN ip addresses. This is only needed when using geoip-shell in whitelist mode, and typically only if the machine has no dedicated WAN network interfaces. Otherwise you should apply geoip blocking only to those WAN interfaces, so traffic from your LAN to the machine will bypass the geoip filter.
To change protocols and ports geoblocking applies to: geoip-shell configure -p <[tcp|udp]:[allow|block]:[all|]>
(for detailed description of this feature, read NOTES.md, sections 9-11)
To enable or change the automatic update schedule: geoip-shell configure -s <"schedule_expression">
Example
geoip-shell configure -s "1 4 * * *"
To disable automatic updates of ip lists: geoip-shell configure -s disable
To update or re-install geoip-shell: run the -install script from the (updated) distribution directory. It will first run the -uninstall script of the older/existing version, then install the new version.
On OpenWrt, if installed via an ipk package: opkg uninstall <geoip-shell|geoip-shell-iptables>
**Pre-requisites**
Linux. Tested on Debian-like systems and on OpenWrt, should work on any desktop/server distribution and possibly on some other embedded distributions.
POSIX-compliant shell. Works on most relatively modern shells, including bash, dash, ksh93, yash and ash (including Busybox ash). Likely works on mksh and lksh. Other flavors of ksh may or may not work (please let me know if you try them). Does not work on tcsh and zsh.
nftables - firewall management utility. Supports nftables 1.0.2 and higher (may work with earlier versions but I do not test with them).
OR iptables - firewall management utility. Should work with any relatively modern version.
for iptables, requires the ipset utility - install it using your distribution's package manager
standard Unix utilities including tr, cut, sort, wc, awk, sed, grep, pgrep, pidof and logger which are included with every server/desktop linux distribution (and with OpenWrt). Both GNU and non-GNU versions are supported, including BusyBox implementation.
wget or curl or uclient-fetch (OpenWrt-specific utility).
for the autoupdate functionality, requires the cron service to be enabled.
**Notes**
For some helpful notes about using this suite, read NOTES.md.
**In detail**
For specifics about each script, read DETAILS.md.
**OpenWrt**
For information about OpenWrt support, read the OpenWrt README.
**Privacy**
geoip-shell does not share your data with anyone.
If you are using the ipdeny source then note that they are a 3rd party which has its own data privacy policy.
Overview (`net/https-dns-proxy/README.md`)
https-dns-proxy
A lightweight, RFC8484-compliant DNS-over-HTTPS (DoH) proxy service for OpenWrt.
Includes optional integration with dnsmasq, automatic fallback, and canary domain support.
Features
- Small footprint (~40KB installed)
- Seamless dnsmasq integration and fallback
- Optional LuCI Web UI with 40+ built-in resolvers
Full documentation:
https://docs.mossdef.org/https-dns-proxy/
Based on @aarond10's excellent https_dns_proxy
Overview (`net/modemmanager/README.md`)
OpenWrt ModemManager
Description
Cellular modem control and connectivity
Optional libraries libmbim and libqmi are available.
Your modem may require additional kernel modules and/or the usb-modeswitch
package.
Usage
Once installed, you can configure the 2G/3G/4G modem connections directly in
/etc/config/network as in the following example:
config interface 'broadband'
option device '/sys/devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2.1'
option proto 'modemmanager'
option apn 'ac.vodafone.es'
option allowedauth 'pap chap'
option username 'vodafone'
option password 'vodafone'
option pincode '7423'
option iptype 'ipv4'
option plmn '214001'
option lowpower '1'
option signalrate '30'
option allow_roaming '1'
option force_connection '1'
option init_epsbearer '<modem|network|connection|custom>'
option timeout '120'Only 'device' and 'proto' are mandatory options, the remaining ones are all
optional.
The 'allowedauth' option allows limiting the list of authentication protocols.
It is given as a space-separated list of values, including any of the
following: 'pap', 'chap', 'mschap', 'mschapv2' or 'eap'. It will default to
allowing all protocols.
The 'iptype' option supports any of these values: 'ipv4', 'ipv6' or 'ipv4v6'.
It will default to 'ipv4' if not given.
The 'plmn' option allows to set the network operator MCCMNC.
The 'signalrate' option set's the signal refresh rate (in seconds) for the device.
You can call signal info with command: mmcli -m 0 --signal-get
The 'timeout' option set's the command timeout (in seconds) for the long
running 'mmcli' calls ('enable', 'simple-connect',
3gpp-register-in-operator' and '3gpp-set-initial-eps-bearer-settings')
in the modemmanager protohandler. The default value is 120 seconds, if
no value is configured.
The 'force_connection' option is designed to ensure that the modem automatically
attempts to reconnect regardless of any errors encountered during the
connection process.
On 4G and 5G networks, the modem attaches to the network with an initial
EPS bearer, before the data connection is established. The 'init_epsbearer'
option selects where the settings for this initial EPS bearer come from:
- modem: Leave the initial EPS bearer settings stored on the modem
unchanged (default). These are usually provided by the carrier
firmware or set by a previous configuration. - network: Set an empty initial EPS bearer, so the network assigns the APN.
Use this to override settings left on the modem, e.g. after
swapping the SIM card. - connection: Use the options 'apn', 'iptype', 'allowedauth', 'username' and
'password', the same options as for the data connection. - custom: Use separate options, prefixed with 'init_': 'init_apn',
'init_iptype', 'init_allowedauth', 'init_username' and
'init_password'.
The values 'none' and 'default' are deprecated aliases for 'modem' and
'connection'.
The initial EPS bearer settings stored on the modem can also be cleared once
manually:
mmcli -m <modem> --3gpp-set-initial-eps-bearer-settings=""Overview (`net/netifyd/README.md`)
Netify Agent
Copyright ©2015-2020 eGloo Incorporated (www.egloo.ca)
Network Intelligence - Simplified
The Netify Agent is a deep-packet inspection server. The Agent is built on top of nDPI (formerly OpenDPI) to detect network protocols and applications. Detections can be saved locally, served over a UNIX or TCP socket, and/or "pushed" (via HTTP POSTs) to a remote third-party server. Flow metadata, network statistics, and detection classifications are stored using JSON encoding.
Optionally, the Netify Agent can be coupled with a Netify Cloud subscription for further cloud processing, historical storage, machine-learning analysis, event notifications, device detection/identification, along with the option (on supported platforms) to take an active role in policing/bandwidth-shaping specific network protocols and applications.
Download Packages
Supported platforms with installation instructions can be found here.
Alternatively, binary packages are available for the following OS distributions (manual install):
Runtime Requirements
- [Linux] Ensure that the nfnetlink and nf_conntrack_netlink kernel modules are loaded if NAT detection is enabled.
Download Source
When cloning the source tree, ensure you use --recursive to include all
sub-modules.
Build Requirements
Netify requires the following third-party packages:
- libcurl
- libpcap
- zlib
- [Linux] libmnl
- [Linux] libnetfilter-conntrack
Optional:
- google-perftools/gperftools/libtcmalloc (will use bundled version when not available)
Configuring/Building From Source
Read the appropriate documentation in the doc directory, prefixed with: BUILD-*
Generally the process is:
./autogen.sh
./configure
makeOnline Documentation
Further user and developer documentation can be found here.
License
This software is licenced under the GPLv3:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Overview (`net/ntpd/README.md`)
Configuring NTPD with UCI
Precedent
Sysntpd is the lightweight implementation of the NTP protocol under
Busybox. It supports many (but not all) of the same parameters.
It is configured as a config timeserver ntp section in /etc/config/system,
below.
Configuration
A sample configuration looks like:
/etc/config/system:
config timeserver ntp
option enabled 1
option enable_server 1
list server tick.udel.edu
list server tock.udel.edu
list interface eth0
list interface eth1
list interface eth2If you want to temporarily disable the service without deleting all of the
configuration state, this is done by clearing the enabled parameter. If
this parameter is 1 (the default), the service is enabled.
The service can run as a stand-alone client (enable_server 0, the default)
or it can also operate as a server in turn to local clients, by setting this
parameter to 1.
The parameter(s) server enumerate a list of servers to be used for
reference NTP servers by the local daemon. At least one is required,
and two or more are recommended (unless you have an extremely available
local server). They should be picked to be geographically divergent,
and preferably reachable via different network carriers to protect
against network partitions, etc. They should also be high-quality
time providers (i.e. having stable, accurate clock sources).
The interface parameter enumerates the list of interfaces on which
the server is reachable (see enable_server 1 above), and may be a
subset of all of the interfaces present on the system. For security
reasons, you may elect to only offer the service on internal networks.
If omitted, it defaults to all interfaces.
Differences with `sysntpd`
Busybox sysntpd supports configuring servers based on DHCP
provisioning (option 6, per the DHCP and BOOTP
Parameter
list from IANA). This functionality is enabled (in Busybox) with theuse_dhcp boolean parameter (default 1), and the dhcp_interface
list parameter, which enumerates the interfaces whose provisioning
is to be utilized.
Considerations for DHCP-provisioned NTP servers
Most terrestrial and satellite ISPs have access to very high-quality
clock sources (these are required to maintain synchronization on T3,
OC3, etc trunks or earth terminals) but seldom offer access to those
time sources via NTP in turn to their clients, mostly from a misplaced
fear that their time source might come under attack (a slave closely
tied to the master could also provide extremely high-quality time
without the risk of network desynchronization should it come under
sophisticated attack).
As a result, the NTP servers that your ISP may point you at are
often of unknown/unverified quality, and you use them at your own
risk.
Early millennial versions of Windows (2000, XP, etc) used NTP only
to initially set the clock to approximately 100ms accuracy (and
not maintain synchronization), so the bar wasn't set very high.
Since then, requirements for higher-quality timekeeping have
arisen (e.g. multi-master SQL database replication), but most ISPs
have not kept up with the needs of their users.
Current releases of Windows use Domain Controllers for time
acquisition via the NT5DS protocol
when domain joined.
Because of the unreliable quality of NTP servers DHCP-provisioned by
ISPs, support for this functionality was deemed unnecessary.
Overview (`net/openthread-br/README.md`)
OpenThread Border Router
This package contains the OpenThread Border Router.
Requirements
To use this package, you need a Thread Radio Co-Processor (RCP). Testing of
this package was done with Nordic Semiconductor nRF52840 USB dongles.
Building and flashing the dongle with the Thread RCP firmware is out of scope
of this document.
One caveat for this dongle is worth mentioning here. The nRF52840 USB dongle
seems to come with the U2F bootloader. To get it in mass storage mode to copy a
firmware file, you need to plug it in while pressing the reset button. However,
after the initial flash with the ot-rcp firmware, this method stops working.
Instead, you need to double press the reset button after plugging in the dongle.
Packaging decisions
Configurable package build
OpenThread is complex software. Adding config options to change the build of
the package will likely result in more bug reports. As the package and its
dependencies are unlikely to fit in any router with small flash (16MB or less),
I don't see much point in making things configurable for reducing size either.
Firewall support
OpenWrt uses firewall4 with nftables by default, but the OpenThread firewall
implementation uses IPTables and IPset. While we still support firewall3 with
IPTables, it's not a good idea to add new dependencies to old things.
Therefore, firewall support is disabled completely.
This can be revised once the following feature request is implemented:
https://github.com/openthread/ot-br-posix/issues/1675
mDNSResponder
The package depends on mDNSResponder. The alternative, Avahi, depends on D-Bus,
which is not something I feel comfortable with running on any router. While
there are Avahi packages without D-Bus support, using OpenThread Border Router
with Avahi requires libavahi-client, and this requires Avahi to be built with
D-Bus support.
REST Server
The REST server is enabled to make this package compatible with Home Assistant.
TREL support
Thread Radio Encapsulation Link support is enabled, as it allows Border Routers
to communicate over other links (e.g. Ethernet), reducing traffic over the
802.15.4 radios.
The following Github discussion contains a good explanation of TREL:
https://github.com/openthread/openthread/discussions/8478
UCI/netifd support
The package contains a minimal netifd protocol handler. This allows configuring
the Thread network in /etc/config/network. The agent will be started by netifd,
rather than using an init script.
OpenThread does not store prefix information in non-volatile storage. As a
result, every time the agent is restarted, a different prefix would be used.
This is not very nice, and makes it very difficult to run the OpenThread Border
Router on a device that is not your main router. Therefore, prefixes can be
configured in /etc/config/network. This way, you can add a static route to the
Thread prefix(es) in your main router, making it possible to access devices on
the Thread network from your entire network.
Create network
When starting the OpenThread Border Router for the first time, a Thread network
must be created.
As the agent is started by netifd, we first need to create an interface in
/etc/config/network:
config interface 'thread'
option device 'wpan0'
option proto 'openthread'
option backbone_network 'lan'
option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
list prefix 'fd6f:5772:5468:7200::/64 paros'
option verbose '0'Prefix and verbose are optional. Everything else is required. The protocol
handler will fail if a required setting is missing. If something isn't working,
check ifstatus for the OpenThread interface:
# ifup thread
# ifstatus thread
{
"up": false,
"pending": false,
"available": true,
"autostart": false,
"dynamic": false,
"proto": "openthread",
"data": {
},
"errors": [
{
"subsystem": "openthread",
"code": "MISSING_BACKBONE_NETWORK"
}
]
}In the above example, the backbone_network option is missing.
The protocol handler will automatically start the the Thread network, so we
need to bring it down for the initial setup. This only needs to be done once.
ubus call otbr threadstopLuCI
Creating a network in LuCI appears to be broken for the moment.
CLI
ot-ctl dataset init new
ot-ctl dataset panid 0x12ab
ot-ctl dataset extpanid 12ab12ab12ab12ab
ot-ctl dataset networkname OpenWrThread
ot-ctl dataset networkkey ddf429af1c52d1735ffaf36fae343ee8
ot-ctl dataset commit active
ot-ctl ifconfig up
ot-ctl thread start
ot-ctl netdata registerConfigure route
Before you can join a device to your new Thread network, you must add a route
to the Thread prefix on the commissioner device via the OpenWrt router running
the OpenThread Border Router.
Get the prefix:
ot-ctl prefixExample output:
fd6b:a92f:c531:1::/64 paros low f000
DoneConfiguring the route is out of scope of this document, but it must be done, or
joining Thread devices will fail.
Get hex-encoded operational dataset TLV
This is needed to join devices to the Thread Network.
ot-ctl dataset active -xExample output:
0e080000000000010000000300001035060004001fffe00708fd488c6a892ec30c04106e220c964a14a7e10e9004691920ec390c0402a0f7f80102ffff030b5468726541646c6576696f0208ffffffffffffffff0510ddf429af1c52d1735ffaf36fae343ee8Join another OpenThread Border Router
Simply configure the active dataset in /etc/config/network:
config interface 'thread'
option device 'wpan0'
option proto 'openthread'
option backbone_network 'lan'
option dataset '0e080000000000010000000300000f35060004001fffe0020836b86cd9746ab3080708fd9850cbe719b1d205101f11a11320828c7a6ebc2f2e675c0dca030e686f6d652d617373697374616e740102716f041025804ed78614258ebedf4e2db37b3b6e0c0402a0f7f8'
list prefix 'fd6f:5772:5468:7200::/64 paros'
option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
option verbose '0'Afterwards, bring up the interface:
ifup threadJoin a Thread device via Matter
ESP32
The following procedure has been tested with an ESP32-C6 using the Matter
lighting-app example.
Building and flashing that app is out of scope of this document.
During startup, the lighting app will print the SetupQRCode to the serial
console:
I (1614) chip[SVR]: SetupQRCode: [MT:Y.K9042C00KA0648G00]
I (1624) chip[SVR]: Copy/paste the below URL in a browser to see the QR Code:
I (1634) chip[SVR]: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3AY.K9042C00KA0648G00
I (1644) chip[SVR]: Manual pairing code: [34970112332]Decide on a node ID for the device.
./chip-tool pairing code-thread 0x65737933320000 hex:0e080000000000010000000300001035060004001fffe00708fd488c6a892ec30c04106e220c964a14a7e10e9004691920ec390c0402a0f7f80102ffff030b5468726541646c6576696f0208ffffffffffffffff0510ddf429af1c52d1735ffaf36fae343ee8 MT:Y.K9042C00KA0648G00 --paa-trust-store-path /path/to/connectedhomeip/credentials/test/attestation/Overview (`net/openvswitch/README.md`)
Which packages to install
Install openvswitch if you need OpenFlow virtual switch function. It
contains ovs-vswitchd, ovsdb-server and helper utilities such as ovs-vsctl,
ovs-ofctl, ovs-ctl etc.
Linux kernel datapath module openvswitch.ko will also be installed along with
package openvswitch. Tunnel encap support for gre, geneve, vxlan can be
included by installing kmod-openvswitch-{gre,geneve,vxlan} respectively
For OVN deployment
- Install
openvswitch-ovn-northfor ovs-northd, ovsdb-server, ovn helper utitlies - Install
openvswitch-ovn-hostfor ovn-controller andopenvswitch
How to use them
Open vSwitch provides a few very useful helper script in/usr/share/openvswitch/scripts/. A simple initscript is provided. It's
mainly a wrapper around ovs-ctl and ovn-ctl with simple knobs from/etc/config/openvswitch. Procd is not used here.
/etc/init.d/openvswitch start
/etc/init.d/openvswitch stop
/etc/init.d/openvswitch stop north
/etc/init.d/openvswitch restart ovs
/etc/init.d/openvswitch statusUse ovs-ctl and ovn-ctl directly for more functionalities
Open vSwitch in-tree Linux datapath modules
The Open vSwitch build system uses regexp and conditional-compilation
heuristics to support building the shipped kernel module source code against a
wide range of kernels, as of openvswitch-2.10, the list is supposed to include
vanilla linux 3.10 to 4.15, plus a few distro kernels.
It may NOT work
- Sometimes the code does not compile
- Sometimes the code compiles but insmod will fail
- Sometimes modules are loaded okay but actually does not function right
For these reasons, the in-tree datapath modules are NOT visible/enabled by
default.
Building and using in-tree datapath modules requires some level of devel
abilities to proceed. You are expected to configure build options and build
the code on your own
E.g. pair openvswitch userspace with in-tree datapath module
CONFIG_DEVEL=y
CONFIG_PACKAGE_openvswitch=y
# CONFIG_PACKAGE_kmod-openvswitch is not set
CONFIG_PACKAGE_kmod-openvswitch-intree=yE.g. replace in-tree datapath module with upstream version
opkg remove --force-depends kmod-openvswitch-intree
opkg install kmod-openvswitch
ovs-ctl force-reload-kmodUCI configuration options
There are 5 config section types in package openvswitch:
ovs ovn_northd, ovn_controller & ovs_bridge.
Each of these supports a disabled option, which should be
set to 0 to launch the respective daemons.
The ovs section section also supports the options below, to configure a set of
SSL CA, certificate and private key. After adding these to Open vSwitch, you
may specify ssl: connection methods for e.g. the OpenFlow controller. Note that
Open vSwitch only reads these files during startup, so it needs to be restarted
after adding or changing these options.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| disabled | boolean | no | 0 | If set to 1, do not configure SSL |
| ca | string | no | (none) | Path to CA certificate |
| cert | string | no | (none) | Path to certificate |
| key | string | no | (none) | Path to private key |
The ovs_bridge section also supports the options below,
for initialising a virtual bridge with an OpenFlow controller.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| disabled | boolean | no | 0 | If set to true, disable initialisation of the named bridge |
| name | string | no | Inherits UCI config block name | The name of the switch in the OVS daemon |
| controller | string | no | (none) | The endpoint of an OpenFlow controller for this bridge |
| datapath_id | string | no | (none) | The OpenFlow datapath ID for this bridge |
| datapath_desc | string | no | (none) | The OpenFlow datapath description for this bridge |
| drop_unknown_ports | boolean | no | 0 | Remove ports not defined in UCI from the bridge |
| fail_mode | string | no | standalone | The bridge failure mode |
| ports | list | no | (none) | List of ports to add to the bridge |
The ovs_port section can be used to add ports to a bridge. It supports the options below.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| disabled | boolean | no | 0 | If set to 1, do not add the port to the bridge |
| bridge | string | yes | (none) | Name of the bridge to add the port to |
| port | string | yes | (none) | Name of the port to add to the bridge |
| ofport | integer | no | (none) | OpenFlow port number to be used by the port |
| tag | integer | no | (none) | 802.1Q VLAN tag to set on the port |
| type | string | no | (none) | Port type, e.g. internal, erspan, type, ... |
Overview (`net/pbr/README.md`)
Policy-Based Routing (pbr)
Flexible policy-based routing (PBR) framework for OpenWrt.
Allows routing specific traffic (by IP, MAC, port, protocol, or domain) through a specific WAN, VPN, or tunnel.
Features
- Route by IP, MAC, port, or domain name
- Works with WAN, VPNs (WireGuard, OpenVPN), or tunnels
- Lightweight shell-based implementation
- Optional Web UI integration via LuCI
Full documentation:
https://docs.openwrt.melmac.ca/pbr/
Overview (`net/pppossh/README.md`)
This package will add the so-called pppossh protocol support to OpenWrt. The idea is mainly from pvpn project (poor man's VPN over SSH).
PPPoSSH is generally not considered a network setup for production use mainly due to the TCP-over-TCP styles of traffic transport, but it can be quite handy for personal use. And with what's already in OpenWrt, it is really easy and takes little extra space to configure it up and running.
Prerequisites and dependency.
pppossh depends on either dropbear or openssh-client; dropbear is normally enabled in OpenWrt by default.
The following requirements need to be fulfilled for it to work.
A SSH account on the remote machine with
CAP_NET_ADMINcapability is required.Public key authentication must be enabled and setup properly.
Public key of the one generated automatially by dropbear can be induced by the following command. But you can always use your own (dropbear can work with OpenSSH public key).
textdropbearkey -y -f /etc/dropbear/dropbear_rsa_host_keySSH server's fingerprint has to be present in
~/.ssh/known_hostsfor the authentication to proceed in an unattended way.Manually logging in at least once to the remote server from OpenWrt should do this for you.
How to use it.
The protocol name to use in /etc/config/network is pppossh. Options are as described below.
server, SSH server nameport, SSH server port (defaults to22).sshuser, SSH login usernameidentity, list of client private key files.~/.ssh/id_{rsa,dsa}will
be used if no identity file was specified and at least one of them must be
valid for the public key authentication to proceed.ipaddr, local ip address to be assigned.peeraddr, peer ip address to be assigned.ssh_options, extra options for the ssh client.peer_pppd_options, extra options for the pppd command run on the peer side.use_hostdep, set it to0to disable the use ofproto_add_host_dependency. This is mainly for the case that the appropriate route toserveris not registered tonetifdand thus causing a incorrect route being setup.
Tips
An uci batch command template for your reference. Modify it to suite your situation.
uci batch <<EOF
delete network.fs
set network.fs=interface
set network.fs.proto=pppossh
set network.fs.sshuser=root
set network.fs.server=ssh.example.cn
set network.fs.port=30244
add_list network.fs.identity=/etc/dropbear/dropbear_rsa_host_key
set network.fs.ipaddr=192.168.177.2
set network.fs.peeraddr=192.168.177.1
commit
EOFAllow forward and NAT on the remote side (ppp0 is the peer interface on the remote side. eth0 is the interface for Internet access).
sysctl -w net.ipv4.ip_forward=1
iptables -t filter -A FORWARD -i ppp0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADEIt's possible that pppd may output protocol negotiation incompatibilities issues to syslog, such as something like the following which did not hurt the connectivity and was annoying only because we thought it can do better.
Sun Oct 25 09:45:14 2015 daemon.err pppd[22188]: Received bad configure-rej: 12 06 00 00 00 00To debug such problems, we can try adding option pppd_optinos debug to the interface config. In the above case, it's a LCP CCP configure rej (the CCP options struct is exactly 6 octets in size as indicated in source code pppd/ccp.h) and since the internet fee is not charged on the bytes transferred, I will just use noccp to disable the negotiation altogether.
Also to optimize bulk transfer performance, you can try tweaking the ciphers. OpenSSH client does not support none cipher by default and you have to patch and install it by yourself. Another option is to try ciphers like arcfour and blowfish-cbc. In my case, arcfour has the best throughput.
option ssh_options '-o "Ciphers arcfour"'Overview (`net/safe-search/Readme.md`)
Package: net/safe-search
This package prevents adult content from appearing in search results by
configuring dnsmasq to force all devices on your network to use Google and
Bing's Safe Search IP addresses. This is designed to be appropriate for most
businesses and families. The default filtering rules do not interfere with
normal web browsing.
Enabled and disable Safe Search by editing /etc/config/safe-search . To make
your changes active, run safe-search-update.
Currently Supported:
- Enabled By Default
- www.bing.com Safe Search
- DuckDuckGo.com Safe Search
- www.Google.com Safe Search
- Not Enabled By Default:
- youtube Safe Search
- https://support.google.com/a/answer/6214622
- https://support.google.com/a/answer/6212415
- https://www.youtube.com/check_content_restrictions
- Not enabled because it is designed for children, and may annoy adults...
- youtube Safe Search
Overview (`net/shunt/README.md`)
shunt - policy based routing by mac, source, destination and domain
Table of Contents
- Description
- Quick Start
- Main Features
- Prerequisites
- Installation and Usage
- shunt CLI interface
- shunt config options
- How addresses are learned
- Examples
- What it shells out to
- What shunt creates on the system
- Coexistence with pbr and mwan3
- Troubleshooting & debug options
- Known limitations
- Support
- Removal
- Donations
Description
shunt routes selected traffic into a policy interface - a VPN tunnel, a second uplink, a mobile connection - chosen by client address, client MAC, destination or domain. It keeps its own nftables table and one routing table per policy, so it coexists with fw4 and with other routing tools instead of competing with them.
The one thing that defines the project: shunt is not bound to any DNS backend. It works unchanged with dnsmasq, unbound, smartdns, AdGuard Home or anything else, because it never asks the resolver for anything and never sits in the DNS path. Domain to address mapping comes from two sources shunt owns itself, described under How addresses are learned.
Quick Start
For a typical setup these few steps are enough - see the sections below for details:
- Install the LuCI companion package:
apk update && apk add luci-app-shunt(this pulls in theshuntbackend as a dependency). - Make reverse path filtering loose and give the policy interface a masquerading firewall zone - both once, both shown under Prerequisites. Without the first, marked traffic is dropped; without the second it is marked and routed and then goes nowhere, which looks exactly like shunt not working.
- Open LuCI under
Services -> shunt, add a policy on thePoliciestab: pick theInterface, name the clients underSource addressesorSource MAC addresses, and list theDomainsyou want routed. - Start and verify:
/etc/init.d/shunt enable
/etc/init.d/shunt start
shunt checkPlease note: a domain policy only takes effect for a client's next connection to an address that has just been learned - see Known limitations.
Main Features
- Routes by client address, client MAC, ingress device, destination CIDR, port and domain, in any combination
- Resolver independent: works with any DNS backend, and with an encrypted upstream, because it reads the plaintext leg between client and resolver
- Wildcard domains (
*.example.com), learned passively as clients use them - Domain and address lists from a file - a community-maintained set of thousands of names or networks, without a single UCI entry per line
- Per-policy killswitch: reject the traffic when the interface or proxy is gone, instead of leaking it out of the normal uplink - clients fail at once rather than on a timeout
- Per-policy action:
routemarks the traffic for the policy interface,bypassexempts it from every policy below,tproxyhands it to a transparent proxy on the router - Your own networks stay reachable from a policy client, without listing them anywhere
- Puts its own table back when something deletes it,
fw4 flushincluded - Own nftables table and routing tables, disjoint mark range - runs beside
pbrandmwan3 - IPv4 and IPv6 throughout, with a MAC selecting a host in both at once
- Per-element counters on every set, a ubus status object and a LuCI frontend
- No dependency on a specific DNS backend, no resolver configuration, no include files, no hooks into fw4's ruleset
Prerequisites
- OpenWrt with fw4/nftables
ucodeplusucode-mod-fs,ucode-mod-socket,ucode-mod-uci,ucode-mod-uloop,ucode-mod-resolv,ucode-mod-ubus,ucode-mod-rtnl,ucode-mod-logandrpcd-mod-ucode- all pulled in by the package;ipis not needed, routes and rules are written over rtnetlink
kmod-nft-tproxy only for a tproxy policy. It is not pulled in: without it a tproxy policy is skipped as an issue and everything else is applied.
ucode-mod-resolv and ucode-mod-ubus are soft at runtime: without resolv, poll is skipped and the observer carries the service alone; without ubus, gateway discovery and interface events are skipped and the config's own values are used. Both cost one warning in the log, not a failed start.
Which tunnels work
Any of them, and there is no supported-protocols list to check against, because shunt never asks what protocol an interface speaks. It consumes two things: the device to route into, and a gateway if one is needed. Both come from netifd, and a device netifd does not manage is taken as given.
- Point to point tunnels - wireguard, OpenVPN
tun*, L2TP, PPTP, Tailscale, NetBird and the like - need no gateway at all. The route isdefault dev <device> table <n>. - Ethernet style interfaces - OpenVPN
tap*, a second wired uplink, a mobile connection - use the gateway discovered from netifd, orgw4/gw6if you set them. - Interfaces netifd does not manage work by name too. On OpenWrt this is the exception rather than the rule - wireguard, OpenVPN, L2TP and the rest all have netifd protocols and are managed like any other interface. It applies to a tunnel brought up outside netifd, by
wg-quickor a script of your own. If such an interface does need a gateway, discovery cannot find one and you have to setgw4/gw6yourself.
WireGuard: route_allowed_ips belongs off, unless the tunnel is the default uplink. shunt renders default dev <device> into the policy's own table, so it needs no route from the tunnel itself. With the option on and allowed_ips 0.0.0.0/0 netifd installs that prefix as a route in main instead, and the whole box follows the tunnel - the situation policy routing exists to avoid. Turning it off is all that is required; nothing else about the peer changes. Where the tunnel is meant to carry everything and the policies carve out the exceptions, leave it on - see Default uplink and exceptions.
AllowedIPs is not a route, and it still applies. route_allowed_ips decides only whether netifd installs routes for those prefixes - they are written to the wireguard configuration either way, and the kernel uses them as its cryptokey filter. A packet whose destination matches no peer's AllowedIPs is dropped inside wireguard, however correctly it was routed there. A policy meant to carry general traffic therefore needs 0.0.0.0/0 on the peer, and ::/0 if it carries IPv6; what actually enters the tunnel is decided by the policy's selectors, not by the peer. Narrowing AllowedIPs to a few subnets is right only when those subnets really are everything the tunnel should ever carry.
The one thing that does not work is anything that is not a routable interface. Tor is the usual example: it normally offers a SOCKS port, and sending traffic there is a redirect, not a route. shunt marks a packet and looks up a routing table; without a device to put a default route on, there is nothing for it to do.
Two kernel-side prerequisites. shunt never changes either one behind your back - the first can be handed to shunt explicitly (rp_filter_manage, below), the second stays with fw4:
rp_filter must be loose on the policy interface. Marked traffic takes an asymmetric path, so strict reverse path filtering drops it. The kernel decides per incoming packet using max(net.ipv4.conf.all.rp_filter, net.ipv4.conf.<dev>.rp_filter), where 2 is loose - so setting the policy interface alone to 2 suffices even while all stays strict. Prefer this: it leaves reverse path filtering intact on every other interface.
cat > /etc/sysctl.d/99-shunt.conf <<'EOF'
net.ipv4.conf.phy0-sta0.rp_filter=2
EOF
sysctl -p /etc/sysctl.d/99-shunt.confReplace phy0-sta0 with your policy interface's device - the Interface column on the overview shows it - one line per policy device.
There is a boot-order catch. A device that does not exist yet - a tunnel, or a wifi client interface brought up late - has no conf/<dev> entry at boot, so sysctl -p cannot set it and skips the line. When the device finally appears it inherits net.ipv4.conf.default.rp_filter, and if that is strict the device comes up strict and stays that way until the next sysctl -p - which for most setups means until the next reboot, i.e. never in practice. Two ways around it: set net.ipv4.conf.default.rp_filter=2 as well, which makes every later-appearing interface inherit loose (a little broader, but far short of all), or let shunt handle it with the option below.
rp_filter_manage (optional, off by default). With it set, shunt itself sets rp_filter=2 on its own policy devices - at start and again whenever one comes up, which is exactly the boot-order moment a static file misses. It only ever touches the devices shunt routes into, never all or default, and only while the service runs. It is off by default because changing a security setting should be a deliberate choice:
uci set shunt.@global[0].rp_filter_manage='1'
uci commit shunt
/etc/init.d/shunt restartWhichever way you choose, the daemon checks the live per-device values - at start and again whenever a policy interface comes up - and warns, naming the device, only when a policy device exists and is still strict. A device that is not there yet carries no traffic and triggers no warning; it is checked the moment it appears. With rp_filter_manage on the warning therefore simply does not appear - not because the switch is set, but because the values read back are actually loose. With all at 2 (or 0) nothing is ever reported. The package deliberately ships no box-wide sysctl file: rp_filter on all/default is a distribution default OpenWrt sets strict in /etc/sysctl.d/10-default.conf, and loosening it there weakens anti-spoofing on every interface, well beyond shunt's own traffic.
Masquerading stays fw4's job. shunt marks and routes; it does not touch the firewall's NAT. The policy interface needs a zone with masq enabled and forwarding from lan, exactly as any other uplink. If the interface is a netifd one - say a wireguard interface named vpn:
uci add firewall zone
uci set firewall.@zone[-1].name='vpn'
uci set firewall.@zone[-1].input='REJECT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'
uci set firewall.@zone[-1].masq='1'
uci set firewall.@zone[-1].mtu_fix='1'
uci add_list firewall.@zone[-1].network='vpn'
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='lan'
uci set firewall.@forwarding[-1].dest='vpn'
uci commit firewall
/etc/init.d/firewall reloadnetwork names a logical interface, not a device. For a device netifd does not manage - a tunnel brought up outside netifd - use uci add_list firewall.@zone[-1].device='wg0' instead. And if the policy interface already has a zone, because it is an ordinary second uplink, there is nothing to do here.
Symptoms of getting this wrong are worth knowing, because they do not look like a firewall problem: the prerouting counters rise, nft list set shows the learned address being hit, and the client's connection simply times out.
Installation and Usage
- Update your router's apk repository (
apk update) - Install the LuCI companion package
luci-app-shunt, which also installs the mainshuntpackage as a dependency - Make
rp_filterloose and give the policy interface a masquerading firewall zone - both are one-time steps with copy-paste commands under Prerequisites - Configure at least one policy, either in LuCI under
Services -> shuntor by editing/etc/config/shunt - Enable and start the service, then run
shunt check- it prints the mark, routing table and rule priority of every accepted policy, a bypass policy by name alone, and every rejected value with its reason - Check the
Set Reportingtab to see which addresses were learned, and theProcessing Logtab for the service's own messages
shunt CLI interface
All functions are available from the command line, and the config file can be edited directly if you prefer that to LuCI.
shunt check # render everything, print marks and issues, change nothing
shunt run # foreground, the procd service entry point
shunt flush # tear down table, rules, routes and the mapping file
shunt refresh # make the running daemon re-read its list files
shunt -v <cmd> # echo every message to the terminal as wellshunt check is safe at any time, including while the service runs, because it only renders - it never touches the kernel. Run it after every config change. Note that it says nothing about whether the service is running; that is what /etc/init.d/shunt status and the LuCI overview are for.
Exit codes: 0 ok, 1 runtime failure, 2 usage or unusable config.
Logging goes to syslog under the tag shunt, so logread -e shunt shows everything - the daemon's own lines carry its pid, shunt[1234]:. Debug lines stay off unless -v is given or option debug '1' is set - under procd there is no command line, so a bug report needs the config switch. Expect volume: on a router running adblock roughly half of all observed answers are error replies, and debug gives each one a line.
shunt flush is the escape hatch if the daemon ever dies without tearing down. It is idempotent and safe on a box that never ran shunt.
shunt refresh asks the daemon over ubus to re-read every domain_file and dst_file and reports what it found; it is the one change that does not need a restart. Without a running daemon it says so and exits 1 - nothing is lost, the next start reads the files anyway. Everything else in the config still takes /etc/init.d/shunt restart.
shunt config options
Global section
| Option | Default | Description |
|---|---|---|
| enabled | 1 |
master switch; 0 means the service starts and exits |
| debug | 0 |
log every observed answer and every set write |
| rp_filter_manage | 0 |
set rp_filter=2 on shunt's own policy devices, at start and on ifup |
| poll_interval | 300 |
seconds between poll cycles, at least 30 |
| entry_ttl | 1200 |
ceiling for the lifetime of learned elements, at least 60 |
| snoop | 1 |
enable the passive DNS observer |
| snoop_device | br-lan |
LAN devices to observe, a list, one entry per segment |
Values below the minimum are clamped, not rejected, and the clamp is logged. entry_ttl should stay well above poll_interval - a polled element is rewritten once its remaining timeout drops below half of entry_ttl, so the default pair refreshes comfortably within two poll cycles. Elements learned by snoop expire with the TTL of the answer instead, see How addresses are learned.
Policy sections
Each config policy section is one routing policy. The section must be named, and the name must match [A-Za-z0-9_]{1,24} - it becomes an nftables identifier, so a section without a name, or one with a hyphen or a dot in it, is rejected as an issue and never rendered. LuCI enforces the same pattern when a policy is added.
| Option | Description |
|---|---|
| enabled | 0 skips the section entirely |
| action | route (default), bypass or tproxy, see below |
| interface | netifd logical name (wan, trm_wwan) or raw netdev (wg0, phy0-sta0); route only |
| tproxy_port | port the transparent proxy listens on; tproxy only, required there |
| fallback | main (default) or block, see below; route and tproxy |
| keep_local | 1 (default) keeps traffic on main where main has a route for it, see below; route only |
| gw4 / gw6 | gateway override; normally unnecessary |
| src | client addresses or CIDRs whose traffic this policy owns |
| src_mac | client MAC addresses, ORed with src |
| src_dev | ingress devices (br-guest, lan2), ORed with src and src_mac, see below |
| proto | tcp, udp, or both; a port without one covers both |
| dport | destination ports, single or a range like 8000-8080 |
| dst | destination addresses or CIDRs |
| dst_file | files with destination addresses or CIDRs, one per line, see below |
| domain | domain patterns, see below |
| domain_file | files with domain patterns, one per line, see below |
src, src_mac, src_dev, dst, dst_file, domain and domain_file are lists and may repeat.
Interfaces are resolved through netifd: a logical name resolves to its l3_device, a raw netdev is adopted if netifd knows it, and a device netifd knows nothing about passes through as given - which on OpenWrt means a tunnel started outside netifd, since wireguard and the other tunnel types have netifd protocols of their own. Gateways are discovered from the same dump, merged across sibling entries, because netifd splits families. gw4/gw6 override discovery and always win; on a point to point interface no gateway is needed at all.
There is deliberately no list of supported tunnel protocols. shunt asks netifd for the device and the gateway and renders a default route into the policy table - default via <gw> dev <device> when a gateway is known, default dev <device> when none is needed. Wireguard, OpenVPN in both tun and tap mode, L2TP, PPTP, Tailscale, NetBird, a second physical uplink or a mobile connection all reduce to those two shapes, so none of them needs a case of its own. See Which tunnels work for the one thing that genuinely does not fit.
Selectors are ANDed, client selectors OR each other
src,src_macorsrc_devalone marks everything from those clientsdst,dst_file,domain,dportorprotoalone marks that traffic from everyone -dport '25'by itself routes everybody's smtp- clients plus destinations marks only those clients' traffic to those destinations
dport and proto AND with everything else, so a policy with a client, a domain and dport 443 covers that client's HTTPS traffic to that domain and nothing more. A port without a protocol matches both tcp and udp - "port 443" almost always means QUIC too, and requiring the protocol would let it slip through unnoticed. If ports or protocols were configured and none of them is usable, the policy is skipped rather than rendered without the narrowing.
This is the single most common source of "it did not work" reports: with a client and a domain both set, a generic curl ifconfig.me from that client correctly takes the normal uplink, because ifconfig.me is not in the domain list. That is the policy working, not failing.
A client MAC, a client address and an ingress device OR each other, so a host may be named either way. If client selectors were configured and none of them is usable - a typo in the only address, say - the policy is skipped with an issue rather than falling back to "every client", which is what an absent client selector otherwise means. The same holds for dst: if every destination address is unusable and no domain is left to narrow the policy, it is skipped rather than widened to every destination.
Selecting clients by MAC
src_mac exists mainly for IPv6. Clients prefer rotating privacy addresses for outgoing traffic, so a single IPv6 address is not a usable selector and the LAN prefix covers every host in the segment. A MAC picks exactly one host, in both address families, and keeps doing so when the addresses change. A policy with only src_mac therefore needs no v6 address to route v6.
Three limits, none of them guessable:
- Same layer 2 segment only. Anything behind another router arrives with that router's MAC.
- Never the router itself. The
outputchain sees traffic the router generated, which has no ethernet sender, so MAC rules are not installed there. An address based policy does cover the router; a MAC-only one does not. - Phones randomise their MAC, though usually stable per network. Use the address the client shows in your DHCP leases, not the one on the label.
Selecting clients by ingress device
src_dev selects everything that arrives on a device - a whole guest network, an IoT VLAN, a second LAN port - in both address families at once. That is what an address selector cannot do for IPv6 on a line with a dynamic prefix: the delegated prefix changes, and the prefix you configured stops matching the day it does. The device does not change.
config policy 'guest'
option interface 'wg0'
option fallback 'block'
list src_dev 'br-guest'The entry is a kernel device name - br-guest, lan2, eth0.20 - as ip link shows it, not a netifd interface name. It is matched by name, so a device that does not exist yet is no error: the rule matches nothing until it appears. Like src_mac, it exists in prerouting only, so the router's own traffic is never covered by it.
With keep_local at its default the devices' traffic to your other networks stays local, and traffic to the router itself - DNS, DHCP, LuCI - is delivered locally whatever the mark says. For domain policies the device has to be observed as well: add it to snoop_device, or names its clients ask for are learned by poll alone.
Domain patterns
example.com matches the apex only
*.example.com matches subdomains only, at any depth, NOT the apexList both to cover both. This is more typing than dnsmasq's implicit subdomain inclusion, and it is deliberate: dnsmasq's behaviour surprises people regularly, this one does not.
Precedence, in order:
- an exact match always beats any wildcard
- among wildcards the longest suffix wins, so
*.cdn.example.combeats*.example.comregardless of which policy declared them - the same pattern in two policies belongs to both
Rule 3 is what makes one domain usable by two client groups over two different uplinks: the address is written into each policy's set, and each policy's rule matches only its own clients, so they stay apart. Rules 1 and 2 still decide specificity - a shared *.example.com never overrides somebody's exact www.example.com.
Matching is label aligned, never string suffix: evilexample.com does not match *.example.com. A bad pattern is collected as an issue, never fatal.
Domain lists from a file
A list domain entry is right for a handful of names. A community-maintained list has thousands, and the place for those is a file, not UCI:
config policy 'vpn'
option interface 'wg0'
list src '192.168.1.0/24'
list domain 'my-own-exception.example'
list domain_file 'community.txt'A bare name lives under /tmp/shunt/, which shunt creates at start; an absolute path is taken as given, for a list kept on a mounted drive. The default is tmpfs on purpose: a list of that size rewritten every day belongs in RAM, not on the flash the overlay sits on. The price is that after a reboot the file is not there until whatever produces it has run again, and that is handled, not worked around - see below.
The file holds one pattern per line in the same two shapes as list domain - example.com or *.example.com - with # starting a comment and blank lines ignored. It is read at start, validated by the same rules, and its patterns join the policy's own; domain and domain_file are additive, and a file may be named by more than one policy. A file that is not there, is unreadable or holds no usable pattern is reported as an issue against the policy and contributes nothing; the policy itself is still rendered, so with domain_file as its only domain selector its learned sets simply stay empty until the file arrives. Bad lines are counted and the first one is named, rather than logging every one of them.
Two things distinguish a file entry from a list domain one, both deliberate:
- File entries are learned by snoop only. poll resolves the names you typed, not the ones from a file - ten thousand names every
poll_intervalis a DNS storm, not a service. A name from a file reaches its set when a client on the observed LAN segment asks for it, which for a large list is exactly when it matters and never before. The first-connection caveat applies to every one of them. - shunt reads the file, it does not fetch it. Where the list comes from and how often it changes is a job for cron, a script or a package of its own, and whatever writes the file runs
shunt refreshafterwards: the daemon re-reads every domain file and swaps its matcher, and nothing else moves - no restart, the nftables table, the routes and the learned addresses all stay as they are. Write the new list to a temporary name and rename it over the old one, so a half-written file is never read:
uclient-fetch -q -O /tmp/shunt/community.tmp https://example.org/domains.txt && mv /tmp/shunt/community.tmp /tmp/shunt/community.txt && shunt refreshRun from cron that covers the reboot case as well: the first run after boot creates the file and the refresh loads it. That keeps shunt independent of the network, of any download tool and of the format a list publisher happens to choose - converting hosts or dnsmasq syntax into one pattern per line is one sed away and stays outside the daemon.
What a large list costs: 100k patterns take about two seconds to load and roughly 40 MB of memory on an x86 test box, so expect several seconds and a proportionally smaller footprint on a router, at start and on every refresh. A file above 4 MiB is refused outright as an issue. shunt check prints how many files were read and how many patterns they contributed, shunt refresh does the same for the running daemon, and ubus call shunt status shows the count under files together with the time of the last refresh.
Address lists from a file
dst_file is the same mechanism for destinations: a provider's published ranges, a country list, a set of CDN networks - thousands of prefixes that have no business in UCI.
config policy 'streaming'
option interface 'wg0'
list src '192.168.1.0/24'
list dst_file 'provider.txt'The file holds one IPv4 or IPv6 address or prefix per line, both families mixed as they come, with # starting a comment and blank lines ignored. Name, location, size limit, issues and shunt refresh work exactly as for a domain file, and dst and dst_file are additive. Every entry is checked by the rules a list dst entry is checked by, so a bad line costs that line and never the ruleset.
Overlapping prefixes are fine: the sets are created with auto-merge, so 10.0.0.0/8 next to 10.1.0.0/16 is folded into one instead of failing the whole batch. The policy gets its static sets for both families even while the file is missing, so a later file may bring either family. shunt refresh flushes and refills those sets in one nftables transaction - the set is never seen half-filled - and leaves everything else in place.
A list of 100k prefixes takes about two seconds to read and roughly 40 MB of memory on an x86 test box, and nftables about two seconds to load it, at start and on every refresh; expect several times that on a router.
Actions: route, bypass or tproxy
action 'route' (default) is the policy shape everything above describes: matching traffic is marked and looked up in the policy's own table.
action 'bypass' marks nothing. Its rule ends evaluation and the packet leaves shunt untouched, which is the equivalent of pbr's ignore: since a packet takes the first policy it matches, a bypass section carves an exception out of every policy below it. It needs no interface, consumes no mark, and gets no routing table or ip rule of its own - fallback, keep_local and the gateway overrides are not read.
The selectors are the same ones a routing policy uses, domains included, so an exception can be as narrow as one client's traffic to one name:
config policy 'no_vpn'
option action 'bypass'
list src '192.168.1.50'
list domain 'bank.example.com'
config policy 'vpn'
option interface 'wg0'
list src '192.168.1.50'Order matters and only order: a bypass section placed after the policy it is meant to except from never sees the packet.
Transparent proxy: tproxy
action 'tproxy' hands the selected traffic to a transparent proxy running on the router - hev-socks5-tproxy, sing-box or xray in tproxy mode, or anything else that accepts IP_TRANSPARENT connections. shunt only steers: it does not start, configure or watch the proxy. It needs kmod-nft-tproxy.
config policy 'proxy'
option action 'tproxy'
option tproxy_port '1088'
option fallback 'block'
list src '192.168.1.0/24'
list domain '*.example.com'The selectors are the usual ones. tproxy carries tcp and udp only, so a tproxy policy without proto covers both, and the proxy must listen for every protocol the policy selects. A policy consumes a mark and gets a routing table holding a single local default dev lo route - that is how the selected packets reach a socket on the router without their destination being rewritten.
shunt delivers to 127.0.0.1 and ::1. A proxy listening on loopback or on the wildcard address is found; one bound to the router's LAN address is not. The address is not left to the kernel on purpose: without one it picks the address of the incoming interface, and a proxy bound to loopback would never be found - the statement fails silently and the traffic is not proxied.
Each family needs a listener that accepts it. A proxy on 0.0.0.0 or 127.0.0.1 serves IPv4 only, so the IPv6 traffic of the policy finds no listener and follows fallback. Listen on :: - which takes both families unless the proxy sets IPV6_V6ONLY - or on both loopback addresses.
Destinations on the router itself are never proxied, nor broadcast and multicast: DNS, DHCP, LuCI and SSH of a selected client reach the router as they would without shunt, and stay reachable with fallback 'block' while the proxy is down. This is tproxy's counterpart to keep_local, built in and not switchable. It covers the router's own addresses only - traffic to your other networks is forwarded unicast and is proxied like any other. A proxy on the router cannot reach those networks on the client's behalf in a useful way, so exempt them with a bypass section above the tproxy policy:
config policy 'no_proxy'
option action 'bypass'
list dst '192.168.0.0/16'
list dst 'fd00::/8'fallback decides what a new flow does when no listener is found - proxy stopped, crashed, wrong port: main (default) lets it take the normal uplink, block rejects it - tcp with a reset, anything else with ICMP admin-prohibited, so the client gives up at once instead of on a timeout. A flow that was already proxied is rejected the same way once its listener is gone, whatever the fallback. Its state is in the proxy, and its conntrack entry was never NATed, so forwarding the rest of it would put the client's own address on the uplink - measured, which is why this does not follow fallback.
Without proto or dport the policy owns every protocol its clients send, not only what a proxy can take. fallback 'block' therefore also rejects ICMP, ESP, GRE and the rest - while the proxy runs too, so ping from those clients never gets out. Router addresses, broadcast and multicast stay exempt as above, which keeps neighbour discovery and the router's own services working. With proto or dport the policy owns only what it names and block rejects only that; everything else the client sends - QUIC for a policy narrowed to proto 'tcp' - takes the normal uplink.
interface, keep_local and the gateway overrides are not read, and keep_local '1' set explicitly is reported - the exemption of the router's own addresses above takes its place. It would break the policy: main would forward packets the kernel has already handed to the proxy's socket, and the kernel drops those.
Only traffic passing through the router is proxied. tproxy exists in the prerouting hook alone, so the router's own traffic is not touched by a tproxy policy - which also keeps the proxy's upstream connections from looping back into it.
The proxied traffic is delivered to the router itself, so fw4 applies its input rules, not its forward rules. On lan, with input ACCEPT, nothing needs doing. A zone with input REJECT or DROP - a guest network, typically - drops it, and shunt cannot override that from its own table, because a drop in any base chain is final. The packet still carries its original destination port, so a rule on the proxy port does not match; match the policy's mark instead (shunt check prints it):
config rule
option name 'Allow-guest-tproxy'
option src 'guest'
option mark '0x01000000/0xff000000'
option target 'ACCEPT'Example backend: hev-socks5-tproxy. It turns tproxy traffic into connections to a SOCKS5 server and is packaged for OpenWrt. Its sample /etc/hev-socks5-tproxy/main.yml already fits the policy above - tcp and udp on :: port 1088, the SOCKS5 server expected on 127.0.0.1:1080:
socks5:
port: 1080
address: 127.0.0.1
udp: 'udp'
tcp:
port: 1088
address: '::'
udp:
port: 1088
address: '::'Point socks5 at your server, then enable the service with uci set hev-socks5-tproxy.config.enabled='1', uci commit hev-socks5-tproxy and /etc/init.d/hev-socks5-tproxy start. Its own connections to the SOCKS5 server start on the router, so the tproxy policy never sees them; a route policy can still send them through a tunnel.
Local traffic: keep_local
A policy that names only clients marks everything those clients send, and the mark decides the routing table before the destination is looked at. Without keep_local that includes traffic to your own networks: a marked packet to another VLAN would find only the policy table's default route and leave through the tunnel, and with fallback 'block' it would be rejected.
keep_local '1' (default) renders a second ip rule per family, on the same mark and one priority ahead of the policy rule, that consults main with the default route suppressed:
ip rule add pref 31001 fwmark 0x1000000/0xff000000 lookup main suppress_prefixlength 0
ip rule add pref 31501 fwmark 0x1000000/0xff000000 lookup 8001Anything main has a specific route for - every attached subnet, every static route - keeps taking it; everything that would have used the default route falls through to the policy rule. The policy itself is unaffected, because its whole purpose is the default route it renders into its own table.
keep_local '0' renders the policy rule alone. That is the hermetic form: with fallback 'block' nothing marked can leave through another interface, at the price of the local traffic above. Set it only where a leak matters more than reaching your own networks, and expect to name the destinations you still want reachable in a bypass section instead.
Policy precedence
Section order in /etc/config/shunt, top to bottom. There is no priority option - one less value to set wrong. A packet matching two policies takes the earlier one; rule evaluation ends at the first match.
Note that domain precedence is resolved before this, at the matcher: the most specific pattern wins even if it sits in a later section.
Fallback: main or block
fallback 'main' (default) renders no default route into the policy table, so when the policy interface is down the table is empty and marked traffic falls through to main - the normal uplink. Traffic keeps flowing, unpolicied.
fallback 'block' adds a prohibit default at metric 9999 to the policy table. While the interface is up its own default has the lower metric and wins; when the interface drops, the kernel withdraws that route and the prohibit route catches everything, answering it with ICMP admin-prohibited - a connection attempt fails at once instead of on a timeout, measured. That is the killswitch: traffic belonging to the policy stops rather than leaking out of the wrong interface. It stops at the destinations the policy actually owns - see keep_local for what a marked packet to one of your own networks does, and how to make the killswitch hermetic.
Default uplink and exceptions
shunt routes only what its policies select. Everything else follows main, and which uplink main uses is decided by the interface metrics, not by shunt. A box with a normal uplink and a tunnel can therefore be set up either way round:
ISP as default, selected clients through the tunnel. The uplink keeps its default route in main, the tunnel installs none (route_allowed_ips '0'). The policy names the tunnel as its interface:
config policy 'vpn'
option interface 'wg0'
list src_mac 'aa:bb:cc:dd:ee:ff'Tunnel as default, selected clients through the ISP. The tunnel carries the default route in main (route_allowed_ips '1' and a lower metric than the uplink), and the policy names the uplink as its interface:
config policy 'isp'
option interface 'wan'
list src_mac 'aa:bb:cc:dd:ee:ff'In both forms the policy's own table decides for its clients in both address families, so the metrics matter only for traffic no policy selects. Set them explicitly on both sides anyway, including wan6 where IPv6 comes from an interface of its own: netifd's default metric 0 stays 0 for IPv4, but the kernel stores an IPv6 route with metric 0 as 1024. An interface left at the default therefore wins the IPv4 default route and loses the IPv6 one against any interface with an explicit metric - unselected clients then leave through different uplinks per family.
fallback 'block' covers the policy's clients only. In the tunnel-as-default form, unselected clients move to the uplink when the tunnel goes down, because netifd withdraws the tunnel's route from main and the uplink's is left. Preventing that is a matter of the firewall, not of shunt.
How addresses are learned
Two sources feed the same nftables sets, union with an element timeout. They are complementary, not alternative modes.
- poll resolves the configured names through whatever system resolver exists, on a fixed interval. It warms the sets before the first client packet, so first contact does not race. Wildcards are not names and cannot be polled, and names from a
domain_fileare not polled by design - see Domain lists from a file. - snoop passively observes DNS responses on the LAN side via AF_PACKET with a BPF filter matching UDP source port 53 - answers, not questions - including one level of VLAN tagging. It covers CDN variance and wildcards, which poll cannot. It reads; it never writes anything back onto the wire and never sits between a client and its resolver. If it dies, DNS keeps working and only the policy stops applying.
The two sources differ in how long an element lives. A snooped element carries the TTL of the answer it came from - the shortest one, when a message mixes several - clamped to the range 60 to entry_ttl seconds. So an address a CDN hands out for 30 seconds is gone from the set about a minute after the last answer that named it, while a name with a day-long TTL is capped at entry_ttl and re-learned on its next answer. A polled element carries entry_ttl: the resolver interface hands back addresses without their TTL, so poll has nothing better than the ceiling. Where both sources see the same address the shorter bound wins - the next snooped answer cuts a polled element down to its TTL, and the write cache only rewrites an element when the new expiry moves by at least half of the lifetime the answer carries, so a burst of identical answers costs one write.
The kernel timeout runs a few seconds past that lifetime: the current write interval plus two seconds, so four on an ordinary box. A client asks again when the TTL runs out, and the write for the new answer reaches the set up to one write interval later; an element timed out to the second would be missing for exactly that gap, and a connection opened in it would start on the normal uplink and stay there. The slack follows the interval because the gap does - on a box where writes are expensive and the interval has grown, see What shunt creates on the system, elements outlive their TTL by correspondingly more.
This is also the honest answer to the question every IP-based policy router gets: what about a CDN address that the domain stops using while some other site starts to? Nothing on layer 3 can tell two names apart once they share an address, shunt included. What shunt can do is not keep the address longer than the resolver would have, which is exactly what the TTL says.
What polling costs
"Polling" invites the assumption of waste, so here is the arithmetic. One cycle is a single call asking for A and AAAA of every listed name: two lookups per name per interval, against the local resolver. Ten names at the default 300 seconds is 240 lookups an hour - about what a dozen web page loads cost, on a network whose own DNS traffic runs to hundreds of answers in a few minutes. There is no polling of anything else: no interface scanning, no ruleset re-rendering, no periodic writes. A polled element is only rewritten when its remaining lifetime has dropped below half.
Two costs worth knowing:
- The query is synchronous. A name that does not resolve blocks the cycle until it times out (2s, one retry), which delays the service start noticeably if several are wrong. This is why an unresolvable name is reported by name.
- For names with a TTL shorter than the interval the local cache has expired, so poll does refetch upstream rather than answering from cache.
What that means in practice:
- Pick the device the answers cross on their way to the clients, normally
br-lan. It must be an Ethernet type device - a bridge, a VLAN device, a physical port, a wireless interface. A tunnel or PPP interface has no ethernet header, so neither the packet filter nor the decoder can read it, and the failure is silent: nothing matches, nothing is logged. - Only the client-to-resolver leg matters, and only whether it is encrypted. What the resolver does upstream is irrelevant: the usual OpenWrt setup - unbound or dnsmasq on the router, forwarding upstream over DoT or DoH
- is fully covered, because the client asked in plain text over the LAN and the answer comes back the same way.
- A client that speaks DoH or DoT itself is invisible, because it bypasses the local resolver. That is the one encryption case that costs coverage.
- One entry per layer 2 segment. A guest or IoT VLAN on its own device never carries the answers of the main LAN, so it needs its own
snoop_deviceentry. A device that cannot be opened costs one warning; the others keep running. - The router's own lookups are not seen. poll's queries leave through the uplink, not the LAN device.
- Not seen either: DNS over TCP, DNS on a port other than 53, and a second stacked VLAN tag.
A name in a domain list that never resolves is reported once, by name and policy:
poll: www.example.com (policy vpn) has no address - the policy entry has no effect until it resolvesOnce on the way in and once on recovery, never in between. The first cycle runs immediately at start, so a typo shows up within seconds. Wildcards cannot produce this message - nothing can tell whether *.example.com was ever meant to match anything.
Examples
One client, one domain family, over a wireguard tunnel
config policy 'vpn'
option enabled '1'
option interface 'wg0'
option fallback 'main'
list src '192.168.1.50'
list domain 'example.com'
list domain '*.example.com'A whole IoT VLAN over a mobile uplink, killswitch on
The client is named by MAC, so it is covered in both address families without listing a rotating IPv6 address:
config global
list snoop_device 'br-lan'
list snoop_device 'br-iot'
config policy 'iot'
option enabled '1'
option interface 'trm_wwan'
option fallback 'block'
list src_mac 'aa:bb:cc:dd:ee:ff'
list domain '*.vendor-cloud.com'The same domain for two client groups over two uplinks
Both policies claim www.example.com; each routes only its own clients:
config policy 'wwan'
option interface 'trm_wwan'
list src '10.168.30.70'
list domain 'www.example.com'
config policy 'vpn'
option interface 'wg0'
list src '10.168.1.20'
list domain 'www.example.com'An exception for one client, over a policy that covers the whole subnet
The bypass section comes first, so the packets it matches never reach the policy below it. Everything else from the subnet goes into the tunnel:
config policy 'no_vpn'
option action 'bypass'
list src '192.168.1.50'
list domain 'bank.example.com'
list domain '*.bank.example.com'
config policy 'subnet'
option interface 'wg0'
list src '192.168.1.0/24'A whole guest network into the tunnel
br-guest is the device, not the netifd interface. It covers the guest network's IPv4 and IPv6 alike, whatever prefix the provider hands out:
config policy 'guest'
option interface 'wg0'
option fallback 'block'
list src_dev 'br-guest'One port for everybody
No client, no destination - every smtp connection leaves through the second uplink:
config policy 'smtp'
option interface 'wan2'
list dport '25'A destination range without any domain
config policy 'office'
option interface 'wg0'
list src '192.168.1.0/24'
list dst '10.0.0.0/8'What it shells out to
Almost nothing. The daemon and the rpcd backend work through ucode's native bindings - fs, socket for the AF_PACKET observer, uci, uloop, ubus, resolv, rtnl and log - and rpcd carries the LuCI side, so there is no shell glue, no awk, no temporary state files. Logging goes to syslog through the binding, not through a logger process per line.
One external command remains:
| Command | Why |
|---|---|
nft |
the ruleset is applied and read as one atomic batch; ucode has no nftables binding |
Routes and rules go over rtnetlink through ucode-mod-rtnl, the same binding the rpcd backend reads them with, so there is no ip process per command and no ip dependency. A route on a device that does not exist yet is reported by the kernel's own reason rather than an exit code, and rp_filter_manage writes the /proc/sys value directly instead of calling sysctl.
Nothing is ever handed to a shell for parsing: system() takes an argument array and only ever runs nft, and popen() only ever runs fixed command lines - nft -f - in the daemon and nft -j list table in the rpcd backend - so no configuration value or captured data reaches a command line.
What shunt creates on the system
table inet shunt own table, see below
chain prerouting filter hook prerouting, priority mangle
chain output route hook output, priority mangle
set d4_<policy> / d6_<policy> learned, flags timeout, per-element counter
set s4_<policy> / s6_<policy> static dst and dst_file, flags interval, auto-merge, counter
set c4_<policy> / c6_<policy> client src selectors, interval, auto-merge, counter
set m_<policy> client MACs, no family digit, counter
set i_<policy> client ingress devices, no family digit, counter
fwmark <index> << 24, mask 0xff000000
ct mark same bits, set on a flow's first packet
ip rule pref 31000 + <index> keep_local's main lookup
31500 + <index> the policy table
routing table 8000 + <index>, for tproxy: local default dev lo
with fallback 'block': prohibit default metric 9999
/etc/iproute2/rt_tables.d/shunt.conf the table name mapping, for `ip route show` onlyA bypass policy is only a rule in the prerouting and output chains: no mark, no table, no ip rule, and it does not count against the 255. A tproxy policy has rules in prerouting only, and no keep_local rule. The mark mask is fixed at 0xff000000, which allows 255 policies. The output chain is type route so the router's own marked traffic is re-routed after the mark is set.
Every set carries per-element counters, so "is this element ever hit" is one look at nft list set inet shunt <set> rather than a tcpdump session. The two kinds count different things: nftables tests a rule left to right, so a client set counts every packet that matched the selector, whether or not the destination matched afterwards; a learned set is the last lookup in the rule, so a hit there means the packet really was marked. A busy client beside learned addresses at zero is a client that has not visited any of the routed domains, not a fault.
The table can be deleted from outside, and comes back. fw4 reload and fw4 restart touch only table inet fw4, so shunt's table survives both. fw4 flush does not: it walks nft list tables and deletes every table it finds, shunt's included - and that is exactly what /etc/init.d/firewall stop runs. Nothing in the kernel reports the loss, so the daemon checks that its table is still there once per poll cycle, and immediately whenever a write into a learned set fails. If it is gone, the ruleset is re-applied and the write cache is dropped, so poll and snoop refill the learned sets rather than suppressing addresses the kernel no longer has:
shunt: ruleset gone from the kernel - re-applying; `fw4 flush`, as run by `/etc/init.d/firewall stop`, deletes every nftables table
shunt: ruleset re-applied - learned addresses refill from the next poll cycle and from snoopThe routes and ip rules are not affected by any of this - they are re-asserted every poll cycle anyway. Until the check runs, marked traffic simply is not marked, so it takes the normal uplink; with fallback 'block' that means the killswitch is not in force either. ubus call shunt status counts the recoveries as reapplied.
Writes are batched, and the interval adapts. nft -f reads the entire ruleset from the kernel before it resolves a single name, so on a box that also runs a tool with very large sets - banIP with 238k elements, measured - one add element costs seconds of CPU, and nft --check alone costs the same. That is a known bug in nftables (netfilter bugzilla #1735, open since 2024), not something shunt can fix, so observed addresses are collected and applied together by a timer.
The interval follows what the last write actually cost, between 2 and 60 seconds: on an ordinary box a write takes milliseconds and the interval stays at its floor, where the batching is invisible. Where it is expensive the interval grows until nftables takes a bounded share of the machine instead of all of it, at the price of a learned address reaching its set later. Both numbers show up under debug.
A reload wipes learned state. Applying the configuration destroys and re-creates the table atomically, so the learned sets start empty. poll rewarms them within one interval and snoop refills from live traffic; expect a short window after a restart where domain policies do not apply yet.
A flow is routed once, on its first packet. The mark a route policy sets is also written to the flow's conntrack entry, and every later packet of the flow in the original direction takes its mark from there and never reaches the set lookups; flows that got no mark, or a bypass, stay settled the same way. That is what makes a set change safe for connections already running: the kernel kills a masqueraded conntrack entry whose output interface changes, so re-marking a live flow would reset it - measured, not assumed. The same rule holds across a reload. Conntrack survives it, so a connection keeps the decision it started with, including the policy index encoded in its mark; if a reload reorders the policies, that index may now belong to a different policy. Existing connections are not re-evaluated against the new configuration, new connections follow it. Reboot, or restart the client's connections, if that matters after a reorder.
Coexistence with pbr and mwan3
shunt is an independent implementation, not a fork of pbr and not a drop-in for it - there is no config migration and no attempt at feature parity. Within its scope it is a full alternative.
Running both at once during a migration is safe by construction:
| pbr | mwan3 | shunt | |
|---|---|---|---|
| fwmark and ct mark mask | 0x00ff0000 |
0x00003f00 |
0xff000000 |
| ip rule pref | 30000 counting down | ~1001-3250 | 31000 and 31500 counting up |
| routing tables | dynamic from ~256 | 1-250 | 8000+n |
| nft | chains in fw4's table | own inet shunt table |
The mark bits are disjoint and all three mask their writes. Where pbr and shunt both match, pbr's lower rule priority wins, deterministically. So move policies over one at a time and retire pbr once its config is empty.
Anything shunt cannot see is worth knowing about: marks set via SO_MARK on a daemon socket (OpenVPN's --mark) or by an eBPF program are invisible to any inspection. If a box uses those, check the mark ranges by hand.
Troubleshooting & debug options
Did the policy actually match?
The authoritative check needs no route lookups at all:
nft reset counters table inet shunt
# generate traffic from the client
nft list chain inet shunt prerouting # rule counters moved?
tcpdump -ni <policy-interface> host <addr> # the flow leaves where it shouldThe wire capture is ground truth - but capture a learned address, not everything: on a router whose policy interface is also a normal uplink, an unfiltered capture shows traffic that has nothing to do with shunt. An IP echo service is a convenient confirmation, but it only discriminates uplinks that actually have different exits.
The route lookup variant asks the kernel directly:
ip route get <addr> mark 0x1000000
ip route get <addr>The first answer must name the policy table, the second the normal uplink. With keep_local on - the default - pick a destination outside your own networks for this: an address in an attached subnet or behind a static route deliberately answers with main in both lines, and that is the feature working, not the policy failing.
The first line needs iproute2's ip, because BusyBox's route get does not understand mark - one build rejects it outright, the OpenWrt one sends an incomplete netlink request that the kernel answers with EINVAL. shunt itself no longer depends on ip, so install ip-tiny for this check: route and rule are complete there, the tiny build only strips exotic objects. The second line, without a mark, works with BusyBox too.
Adding from <client> iif br-lan makes the lookup more precise, with one further catch worth a confused test session: iif is not optional there. Without it, from a non-local address makes the kernel validate a locally originated lookup and answer ENETUNREACH regardless of any table's content, which reads like broken routing and is not.
What the observer discards, and why
Most DNS answers on a network are of no use to a routing policy, so snoop counts what it discarded and why. ubus call shunt status reports those counters, and the LuCI overview shows them with readable labels.
| Verdict | Meaning |
|---|---|
| qtype | the question was not for an address at all |
| noaddr | an address was asked for, the answer carried none |
| nomatch | the name belongs to no policy |
| dns:E_* | the message did not parse, e.g. E_RCODE for NXDOMAIN |
| frame:E_* | the packet did not decode, e.g. E_FRAG for a fragment |
qtype is usually the largest category and that is expected: current browsers and operating systems ask for HTTPS records (type 65) alongside every A and AAAA. noaddr is the other half of that distinction: the question was A or AAAA, the reply is well formed, and the answer section still holds no address - NODATA.
The checks run in order and the first one wins, so these are "first reason to discard" rather than independent counters: a PTR query for a name you route counts as qtype, never as nomatch.
A high discard count is therefore not a fault. The one number that says whether the observer is doing its job is the matched count next to them.
When the whole ruleset is gone
/etc/init.d/firewall stop runs fw4 flush, which deletes every nftables table on the box, shunt's among them - fw4 restart and fw4 reload do not. Nothing is marked from that moment, so all policy traffic takes the normal uplink and a fallback 'block' killswitch is not in force either.
The daemon notices this within one poll_interval, or immediately if a learned address is written in the meantime, and re-applies its ruleset; the log says so twice, once for the loss and once for the recovery. nft list table inet shunt and the LuCI overview both show whether the table is there now, and ubus call shunt status counts how often it had to come back:
ubus call shunt status | grep reappliedA number that keeps climbing means something on the box flushes nftables repeatedly. Look for a service that calls fw4 flush or nft flush ruleset in an init or hotplug script - shunt only puts its own table back, it cannot stop whatever removes it.
When a policy stops applying after a reconnect
The kernel removes routes from a policy table when the interface goes down, after which the fwmark rule falls through to main while every counter keeps counting. shunt handles this on two levels: a ubus listener on network.interface rebuilds the route half on ifup/ifdown, and every poll tick replays the route commands as a keeper. Learned sets survive both. If ubus is unavailable, only the keeper remains, so recovery takes up to one poll_interval.
Debug logging
uci set shunt.@global[0].debug='1'
uci commit shunt
/etc/init.d/shunt restart
logread -e shuntSet it back to 0 afterwards. Every observed answer and every set write gets a line, which on a busy network is a lot.
Known limitations
These are consequences of the design, stated rather than worked around:
- Clients that speak DoH or DoT themselves are invisible to snoop. poll still covers the names you list explicitly; wildcards do not work for those clients. A resolver forwarding upstream over DoT or DoH changes nothing.
- Wildcards require snoop. poll can only resolve names it was given, and
*.example.comis not a name. - One CDN address serves many domains. If a policy routes
example.comand the address behind it also serves a thousand other sites, those sites follow the same policy. This is unsolvable at layer 3 by anything that routes on addresses. - The first connection to a newly seen address takes the old path. snoop learns from the response the client is reading at that moment, so the client's SYN is usually out before the element reaches the set. That connection stays where it started, by design: a flow's route is decided on its first packet and kept on the conntrack entry, so the switch happens at a connection boundary and never mid-stream - see What shunt creates on the system. The next connection to the same host starts on the policy interface. Listing the entry point explicitly closes the gap, because poll warms it before any client asks.
- DNS over TCP is not observed. Port 53 over TCP needs reassembly, which is out of scope; answers large enough to force TCP are rare in the traffic shunt cares about.
- Route and rule application is best effort. At boot a tunnel interface may not exist yet. A rule over an empty table falls through to
main, so the failure mode is "policy not applied yet", never "traffic broken". Each distinct reason is one warning line. - No interface hotplug. A device that appears later is picked up on the next
ifupevent or within one poll interval, not immediately. - tproxy is detected by its module file. shunt applies a tproxy policy when
nft_tproxyis loaded or installed under/lib/modules. A custom kernel with tproxy built in has neither, and its tproxy policies are skipped as an issue. - An outside flush of nftables is repaired, not prevented. Any tool may delete shunt's table -
fw4 flushdoes. shunt re-applies it within one poll interval, or sooner, and until then nothing is marked.
Out of scope permanently: resolver-integrated set population (dnsmasq nftset, AdGuard Home etc.). Being independent of the DNS backend is the entire point of the project, so adopting a backend-specific mechanism would give up the one property that distinguishes it. Also out: DSCP tagging and user include files.
Support
Please report issues with as much detail as possible - the output of shunt check, the relevant part of logread -e shunt with debug enabled, your /etc/config/shunt, and the OpenWrt version of the device. Please join the shunt discussion in this forum thread or contact me by mail [email protected].
Removal
Stop the service with /etc/init.d/shunt stop, which also tears down the nftables table, the routing tables and the ip rules, then remove the shunt and luci-app-shunt packages if necessary. shunt flush does the teardown alone, should anything be left behind.
Donations
You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time.
If you still insist to donate some bucks ...
- I would be happy if you put your money in kind into other, social projects in your area, e.g. a children's hospice
- Let's meet and invite me for a coffee if you are in my area, the “Markgräfler Land” in southern Germany or in Switzerland (Basel)
- Send your money to my PayPal account and I will collect your donations over the year to support various social projects in my area
No matter what you decide - thank you very much for your support!
Have fun!
Dirk
Overview (`net/simple-captive-portal/README.md`)
Simple captive portal
This package intercepts/blocks traffic from 'interface' and
redirects http requests to a splash page that you can personalize,
stored in '/etc/simple-captive-portal/'.
After clicking on 'connect' the MAC of the client is allowed,
for 'timeout' seconds (24h), allowing both IPv4 and IPv6.
If your guest interface defaults to input drop or reject (recommended),
make sure to allow tcp 8888-8889 on input (and also dns and dhcp).
Here an example (ipv4) firewall configuration.
config zone
option name 'guest'
option forward 'REJECT'
option output 'ACCEPT'
option input 'REJECT'
option network 'guest'
config forwarding
option dest 'wans'
option src 'guest'
config rule
option name 'guest-dhcp'
option src 'guest'
option family 'ipv4'
option proto 'udp'
option dest_port '67'
option target 'ACCEPT'
config rule
option name 'guest-dns'
option src 'guest'
option family 'ipv4'
list proto 'tcp'
list proto 'udp'
option dest_port '53'
option target 'ACCEPT'
config rule
option name 'guest-portal'
option src 'guest'
option family 'ipv4'
list proto 'tcp'
option dest_port '8888-8889'
option target 'ACCEPT'To disable simple-captive-portal, just unset/comment 'interface' in the uci config.
Overview (`net/static-neighbor-reports/README.md`)
static-neighbor-reports
The static-neighbor-reports package allows a user to configure static neighbor reports which
are available for clients to be polled in case they support IEEE802.11k. This greatly improves
the wireless experiences in terms of mobility.
Make sure to enable ieee80211k for each Virtual Access Point (VAP) neighbor reports should be
installed to.
Configuring
The uci config name is static-neighbor-report. There's currently only the section
type neighbor.
neighbor
The following options are supported for neighbor sections:
neighbor_report
This is the binary neighbor report element from a foreign AP. It is required for each neighbor.
disabled
Values other than 0 disable the neighbor. It won't be installed into hostapd in this case.
If this option is missing, the neighbor is implicitly active.
bssid
The BSSID of the foreign AP. This option can usually be omitted, as it's implicitly present in
the first 6 bytes of the binary neighbor report element.
ssid
The SSID of the foreign AP. This option can be omitted, in case it matches the SSID used on the local AP.
iface
Space separated list of hostapd interfaces the neighbor should be installed to.
Retrieving neighbor information
To retrieve the neighbor informations of an AP to be isntalled on a foreign AP, make sure the UCI optionieee80211k is set to 1 on the VAP.
Execute ubus call hostapd. rrm_nr_get_own on the AP. To get a list of all available interfaces,
execute ubus list.
The returned information follows this format:
{
"value": [
"<BSSID>",
"<SSID>",
"<Neighbor report element>"
]
}Overview (`net/swgp-go/README.md`)
swgp-go
This readme should help you configure swgp-go.
Configuration
Edit /etc/swgp-go.json with your desired configuration (Client, Server or both).
If you're configuring a server, also properly allow that port in your firewall.
Note the pprof feature is disabled in this package. If you try to enable it
regardless, swgp-go will show an error on startup.
Start daemon
Once you have created your config, enable and run swgp-go using its init script:
/etc/init.d/swgp-go enable
/etc/init.d/swgp-go startPlease refer to the Project README for more configuration info and examples.
Overview (`net/tailscale/README.md`)
Tailscale
This readme should help you with tailscale client setup.
[!NOTE]
By default this package will use nftables. If you wish to use iptables, the config file/etc/config/tailscalecan be modfied, changing the linefw_mode 'nftables'tofw_mode 'iptables'. You can then run/etc/init.d/tailscale restartto restart tailscale using your chosen method
First setup
First, enable and run daemon
/etc/init.d/tailscale enable
/etc/init.d/tailscale startThen you should use tailscale utility to get a login link for your device.
Run command and finish device registration with the given URL.
tailscale upSee the OpenWrt wiki for more detailed setup instructions
Overview (`net/tcfilter/README.md`)
tcfilter
A thin UCI front-end for persistent tc filter ... ingress rules.
It does not model the flower match/action fields. You write that part as
raw tc syntax in option spec; the package only manages the device, thepref number, enable/disable, persistence across boot, and re-applying the
rules when the network is reconfigured.
Meant for driving hardware tc-flower offload (e.g. the Realtek DSA PIE
offload) where no higher-level config layer exists.
Config
/etc/config/tcfilter:
config tcfilter 'global'
option enabled '1'
config rule
option label 'Drop-HomePlug-AV (FRITZ!Box)'
option device 'lan1'
option enabled '1'
option pref '49152'
option spec 'protocol 0x88e1 flower skip_sw action drop'The shipped default config carries a few such rules with enabled '0'
as ready-to-use examples (FRITZ!Box powerline discovery, mDNS) — setdevice and flip enabled to 1.
label is optional and cosmetic — it only tags the log messages and the
LuCI rows.
spec is everything that would follow
tc filter add dev <device> ingress pref <pref>prefis required — it is how the rule is deleted again.- Use
skip_swso a match the hardware cannot offload fails loudly instead
of silently installing in software. - One
ruleperprefper device.
Commands
/etc/init.d/tcfilter start # apply all enabled rules
/etc/init.d/tcfilter stop # remove them
/etc/init.d/tcfilter reload # stop + start
/etc/init.d/tcfilter show # tc -s filter show for every configured device
/etc/init.d/tcfilter reapply_dev lan1Notes / limitations
- ingress /
clsactonly. - The
clsactqdisc is added if missing but never removed on stop (other
users may share it); only the individual filters are deleted. - Installed
(device, pref)pairs are tracked in/var/run/tcfilter.state
so a rule removed from the config is still torn down on the next reload. - Re-apply hooks:
hotplug.d/ifacerunsstartonifup,hotplug.d/net
runsreapply_devon netdevadd.startis idempotent. - A
procd_add_reload_triggerreloads the service when thetcfilter
config changes, so LuCI Save & Apply anduci commit tcfilter && reload_configtake effect on their own; a bareuci commitstill needs an explicit/etc/init.d/tcfilter reload. - No dry-run validation — an invalid
specis reported via logread only. - Free-form
specis passed totcby word-split (no shell). Anyone who can
edit the config can install redirect/mirror rules, i.e. tap traffic.
Hardware packet counters (Realtek rtl930x PIE offload)
Older rtl930x kernels mis-read the per-rule LOG packet counter: with more
than one offloaded flower rule, only the rule whose PIE rule id was
even-aligned reported a working tc -s hardware packet count and the
others stayed at 0 (rtl930x_packet_cntr_read() assumed the L3-route
counter layout). Dropping / trapping / redirecting was never affected.
Fixed in the kernel driver upstream (openwrt/openwrt#24994); every
offloaded rule now reports its own count. Firmware built before that
patch still shows the old behaviour.
Overview (`net/travelmate/README.md`)
Travelmate, a wlan connection manager for travel routers
Table of Contents
- Description
- Quick Start
- Main Features
- Prerequisites
- Installation and Usage
- Travelmate CLI interface
- Travelmate config options
- Examples
- Best practice and tweaks
- Troubleshooting & debug options
- Support
- Removal
- Donations
Description
If you're taking your laptop, tablet, or phone on an upcoming vacation or business trip, you'll want to connect with friends or complete work on the go. But many hotels don't have a secure wireless network setup or limit you to using a single device at a time.
Travelmate lets you use a small "travel router" to connect all of your devices at once while having total control over your own personal wireless network.
Travelmate runs on OpenWrt and provides an "uplink" to the hotel's wireless access point/hotspot. Travelmate then becomes the Access Point (AP) for you and your companions, providing secure access to the internet. It manages all the network settings, firewall settings, connections to a hotel network, etc. and automatically (re)connects to configured APs/hotspots as they become available.
Quick Start
For a typical setup these few steps are enough to get travelmate up and running — see the sections below for details:
- Install the LuCI companion package:
apk update && apk add luci-app-travelmate(this pulls in thetravelmatebackend as a dependency). - Open LuCI under
Services → Travelmateand run the Interface Wizard once. It creates the uplink interface, the firewall assignment and the required network settings. - Switch to the Wireless Stations tab, scan the radio you want to use as uplink, click
Add Uplink...for the desired SSID and enter its credentials. - Start and verify the service:
/etc/init.d/travelmate start
/etc/init.d/travelmate statusPlease note: configure your AP on a different radio than the uplink whenever your router has more than one. Sharing a single radio between AP and uplink works, but costs a noticeable amount of throughput.
Main Features
- Easy setup from the LuCI web interface with Interface Wizard and Wireless Station manager
- Display a QR code to transfer the wireless credentials to your mobile devices
- Fast uplink connections
- Supports routers with multiple radios in any order
- Supports all kinds of uplinks, including hidden and enterprise uplinks (WEP-based uplinks are no longer supported)
- Continuously checks the existing uplink quality, e.g. for conditional uplink (dis)connections
- Automatically add open uplinks to your wireless config, e.g. hotel captive portals
- Captive portal detection with a 'heartbeat' function to keep the uplink connection up and running
- Captive portal hook for auto-login configured via uci/LuCI, using an external script (see the examples below)
- VPN hook supports 'wireguard' or 'openvpn' client setups to handle VPN (re)connections automatically
- E-mail hook via 'msmtp' sends notification e-mails after every successful uplink connect
- Proactively scan and switch to a higher priority uplink, replacing an existing connection
- Check router subnet vs. uplink subnet, to show conflicts with the router LAN network
- (Optional) Generate a random unicast MAC address for each uplink connection
- (Optional) Evil twin protection by skipping access points with locally-administered (LAA) BSSIDs
- Configurable retry limit per uplink, with optional unlimited retry mode
- NTP time sync before sending e-mails
- procd init and ntp-hotplug support
- Runtime information available via LuCI and via the 'status' init command
- Log status and debug information to syslog
- STA interfaces operate in an "always off" mode, to make sure that the AP is always accessible
Prerequisites
- OpenWrt, tested/compatible with current stable and latest OpenWrt snapshot
- The
luci-app-travelmatepackage ensures these dependencies are present:- 'dnsmasq' as dns backend
- 'ubus iwinfo' for wlan scanning
- 'curl' for connection checking and all kinds of captive portal magic, e.g. cp detection and auto-logins
- a 'wpad' variant to support various WPA encrypted networks (WEP-based uplinks are no longer supported!)
- optional: 'wireguard' or 'openvpn' for vpn client connections
- optional: 'msmtp' to send out travelmate related status messages via e-mail
Installation and Usage
- Install OpenWrt on your router and set it up to allow wireless connections. Be sure to set a strong password on the wireless channel(s) so that only you and your companions can use it
- Decide which radio you'll use for the travelmate uplink (radio0, radio1, etc.). 2.4GHz allows a longer (more distant) link, 5GHz provides a faster link. Travelmate works on all radios, but for better performance configure the AP on a separate radio from the one you're planning to use as the uplink
- Install both travelmate and luci-app-travelmate, the application is located in LuCI under the
Servicesmenu - You must use the travelmate Interface Wizard one time to configure the uplink, firewall and other network settings
- Use the Wireless Stations tab to add an uplink station:
- Scan the radio you chose for the uplink
- Click
Add Uplink...for the desired SSID. If there are multiples, choose the one with the largest Strength - Enter the credentials (password, etc.)
- You should be "on the air" - test by browsing the internet
- You may add additional uplinks for different locations by repeating the previous step
- Happy traveling ...
The buttons at the bottom of the LuCI overview page behave as follows:
| Button | Description |
|---|---|
| Stop | Stops the travelmate service. Greyed out while the service is not running |
| Interface Wizard | One-time setup of the uplink interface, firewall zone and metric |
| Interface Restart | Restarts the travelmate uplink interface without touching the configuration |
| AP QR-Codes | Displays a QR code per AP to transfer the wireless credentials to your mobile devices |
| Save & Restart | Applies pending configuration changes and restarts the service, this also brings a stopped service back up |
While a run cycle is in progress (status processing), all buttons except Stop are temporarily locked to avoid overlapping actions. Stop always stays available, so an unwanted or long running cycle can be interrupted at any time - e.g. right after the Interface Wizard on a fresh setup, when no uplink station has been configured yet.
Travelmate CLI interface
- All important travelmate functions are accessible via CLI, too. If you're going to configure travelmate via CLI, edit the config file
/etc/config/travelmateand enable the service, see the options reference tables below.
~# /etc/init.d/travelmate
Syntax: /etc/init.d/travelmate [command]
Available commands:
start Start the service
stop Stop the service
restart Restart the service
reload Reload configuration files (or restart if service does not implement reload)
enable Enable service autostart
disable Disable service autostart
enabled Check if service is started on boot
scan [<radio>|<ifname>] Scan for available nearby uplinks
setup [<iface>] [<zone>] [<metric>] Setup the travelmate uplink interface, by default 'trm_wwan' with firewall zone 'wan' and metric '100'
running Check if service is running
status Service status
trace Start with syscall trace
info Dump procd service infoThe setup sub-command is the CLI equivalent of the LuCI Interface Wizard and only needs to be run once. The scan sub-command writes its result to /var/run/travelmate/travelmate.scan, sorted by signal quality, and is used by the Wireless Stations tab in LuCI as well.
The status sub-command prints the current runtime information:
~# /etc/init.d/travelmate status
::: travelmate runtime information
+ travelmate_status : connected, net ok/100
+ frontend_ver : 2.4.7-1
+ backend_ver : 2.4.7-1
+ station_id : radio0/GlutenfreiVerbunden/-
+ station_mac : 42:40:45:EC:B3:D1
+ station_interfaces : wwan, -
+ station_subnet : 10.168.20.0 (lan: 10.200.1.0)
+ run_flags : autoadd: ✘, captive: ✔, eviltwin: ✘, mail: ✔, netcheck: ✘, ntp: ✔, proactive: ✔, randomize: ✔, vpn: ✘
+ last_run : mode: start, date / time: 2026-08-06 09:08:24, memory: 412.35 MB available
+ system_info : cores: 2, fetch: curl, Cudy TR3000 v1, mediatek/filogic, OpenWrt SNAPSHOT (r32287-1c7ec8ab19)The travelmate_status field reports one of the following states:
| State | Description |
|---|---|
connected |
An uplink is connected, followed by the connection details, e.g. connected, net ok/100 |
processing |
A run cycle is currently in progress, e.g. scanning the radios or waiting for an uplink to come up |
not connected |
No uplink is connected and no run cycle is active, i.e. travelmate is idle and waiting for the next trigger |
program error |
A fatal error occurred, the daemon has stopped, please check the syslog |
A stopped service does not report a state at all, it truncates the runtime file instead - LuCI shows this as stopped.
Travelmate config options
- Usually the pre-configured travelmate setup works quite well and no manual config overrides are needed. All options listed below apply to the 'global' section:
| Option | Default | Description/Valid Values |
|---|---|---|
| trm_enabled | 0, disabled | set to 1 to enable the travelmate service (this will be done by the Interface Wizard as well!) |
| trm_debug | 0, disabled | set to 1 to get the full debug output (logread -e "trm-") |
| trm_iface | -, not set | uplink- and procd trigger network interface, configured by the 'Interface Wizard' |
| trm_laniface | -, lan | logical LAN network interface, default is 'lan' |
| trm_radio | -, not set | restrict travelmate to certain radio(s) |
| trm_revradio | 0, disabled | change the radio processing order, e.g. 'radio1 radio0' |
| trm_captive | 1, enabled | check the internet availability and handle captive portal redirections |
| trm_netcheck | 0, disabled | treat missing internet availability as an error, see the note below |
| trm_proactive | 0, disabled | proactively scan and switch to a higher prioritized uplink, despite of an already existing connection |
| trm_autoadd | 0, disabled | automatically add open uplinks like hotel captive portals to your wireless config |
| trm_ssidfilter | -, not set | list of SSID patterns for filtering/skipping specific open uplinks, e.g. 'Chromecast*' |
| trm_randomize | 0, disabled | generate a random unicast MAC address for each uplink connection |
| trm_eviltwin | 0, disabled | detect and skip access points with locally administered (LAA) BSSIDs to mitigate evil twin attacks |
| trm_triggerdelay | 2 | additional trigger delay in seconds before travelmate processing begins |
| trm_maxretry | 3 | retry limit to connect to an uplink, set to '0' for unlimited retries |
| trm_minquality | 35 | minimum signal quality threshold as percent for conditional uplink (dis-) connections |
| trm_maxwait | 30 | how long should travelmate wait for a successful wlan uplink connection |
| trm_timeout | 60 | overall retry timeout in seconds |
| trm_maxautoadd | 5 | limit the max. number of automatically added open uplinks. To disable this limitation set it to '0' |
| trm_captiveurl | http://detectportal.firefox.com | custom/pre-configured provider URLs that will be used for connectivity- and captive portal checks |
| trm_useragent | Mozilla/5.0 ... | pre-configured user agents that will be used for connectivity- and captive portal checks |
| trm_nice | 0, normal priority | change the priority of the travelmate background processing |
| trm_mail | 0, disabled | sends notification e-mails after every succesful uplink connect |
| trm_mailreceiver | -, not set | e-mail receiver address for travelmate notifications |
| trm_mailsender | no-reply@travelmate | e-mail sender address for travelmate notifications |
| trm_mailtopic | travelmate connection to ' |
topic for travelmate notification E-Mails |
| trm_mailprofile | trm_notify | profile used by 'msmtp' for travelmate notification E-Mails |
| trm_vpn | 0, disabled | VPN connections will be managed by travelmate |
| trm_stdvpnservice | -, not set | standard vpn service which will be automatically added to new STA profiles |
| trm_stdvpniface | -, not set | standard vpn interface which will be automatically added to new STA profiles |
Please note: 'trm_netcheck' is evaluated independently of 'trm_captive'. A detected captive portal does not count as an error as long as 'trm_captive' is enabled - such an uplink stays connected so that a manual or script based portal login is still possible. With 'trm_captive' disabled travelmate can neither allowlist the portal domain nor run a login script, so a detected portal is treated like a failed connectivity check. Only a failed connectivity check counts, and it is confirmed by a second probe before it is acted on. Such a failure is then handled like a failed connection attempt: after 'trm_maxretry' tries the affected uplink gets disabled in the travelmate config. A longer lasting outage of your upstream provider may therefore disable all configured uplinks, which have to be re-enabled manually.
- Per uplink there is an additional 'uplink' section in the travelmate config, with the following options:
| Option | Default | Description/Valid Values |
|---|---|---|
| enabled | 1, enabled | enable or disable the uplink, automatically set if the retry limit was reached |
| revive | 0, disabled | re-enable this uplink after n run cycles if the retry limit disabled it, minimum '10' |
| device | -, not set | match the 'device' in the wireless config section |
| ssid | -, not set | match the 'ssid' in the wireless config section |
| bssid | -, not set | match the 'bssid' in the wireless config section |
| script | -, not set | reference to an external auto login script for captive portals |
| script_args | -, not set | optional runtime args for the auto login script |
| macaddr | -, not set | use a specified MAC address for the uplink |
| vpn | 0, disabled | automatically handle VPN (re-) connections |
| vpnservice | -, not set | reference the already configured 'wireguard' or 'openvpn' client instance as vpn provider |
| vpniface | -, not set | the logical vpn interface, e.g. 'wg0' or 'tun0' |
Examples
VPN client setup
Please read one of the following guides to get a working vpn client setup on your travel router:
Make sure to uncheck the "Bring up on boot" option during vpn interface setup, so that netifd doesn't interfere with travelmate. Also prevent potential vpn protocol autostarts, e.g. add an additional 'globals' section in newer openvpn uci configs:
config globals 'globals'
option autostart '0'Once your vpn client connection setup is correct, you can reference that config in travelmate to handle VPN (re-) connections automatically.
E-Mail setup
To use e-mail notifications you have to set up the package 'msmtp'. Modify the file /etc/msmtprc, e.g. for gmail:
[...]
defaults
auth on
tls on
tls_certcheck off
timeout 5
syslog LOG_MAIL
[...]
account trm_notify
host smtp.gmail.com
port 587
from [email protected]
user yyy
password zzzFinally enable e-mail support in travelmate and add a valid e-mail receiver address.
Captive portal auto-logins
For automated captive portal logins you can reference an external shell script per uplink. All login scripts have to be executable and located in /etc/travelmate with the extension .login. A login script signals its result via the exit code: 0 means the login succeeded, any other value means it failed. Only 0 makes travelmate re-check the connectivity right away, every other value is just logged. The package ships multiple ready to run auto-login scripts:
- 'wifibahn.login' for german DB railway hotspots
- 'telekom.login' for telekom hotspots (DE)
- 'vodafone.login' for vodafone hotspots (DE)
- 'generic-user-pass.login' a template to demonstrate the optional parameter handling in login scripts
A typical and successful captive portal login looks like this:
[...]
user.info trm-2.4.7-1[26222]: captive portal domain 'www.wifibahn.de' added to dhcp rebind allowlist
user.info trm-2.4.7-1[26222]: captive portal login script for 'www.wifibahn.de' has been finished with rc '0'
user.info trm-2.4.7-1[26222]: connected to uplink 'radio1/WIFI@DB/-' with mac 'B2:9D:F5:96:86:A4' (1/3)
[...]Building your own login script
The fastest way to a working script is to record the login once by hand and then replay it with curl. Any browser's developer tools can do the recording:
- Connect a client to the hotspot - either directly, or through travelmate's own AP while the uplink is up - and open the portal page.
- Open the developer tools (usually
F12) and switch to theNetworktab. EnablePreserve log(Chromium, Edge, Safari) resp.Persist Logs(Firefox, behind the gear icon) andDisable cache. A portal login almost always ends in a redirect, and without these options the recorded entries are dropped at that point. - Perform the login manually and watch which requests are sent.
- Right-click the request that carries your credentials and choose
Copy->Copy as cURL. You now have the exact URL, method, headers, cookies and form fields as the browser sent them.Save All As HARresp.Export HARrecords the whole session if you want to study it later - note that recent Chromium versions strip cookies and authorization headers from the export unless you allow sensitive data in the devtools settings. - Strip the copied command down: the browser adds a lot of
Accept*,Sec-*andPriorityheaders that no portal cares about. Keep the request body, theContent-Typeand whatever the portal actually validates, then replace curl's flags with travelmate's${trm_fetchcmd} ${trm_fetchparm}and--user-agent "${trm_useragent}". - Look for values that are only valid for one session - CSRF tokens, session ids,
sidparameters. Those must not be copied into the script but fetched at runtime, seewifibahn.login(cookie jar plus awk) orvodafone.login(json response plus jsonfilter) for the two usual patterns. - Never hardcode credentials. Pass them via the uplink's
script_argsoption and read them as${1}and${2}, likegeneric-user-pass.logindoes. - Test the script on the router while the portal is actually in the way:
sh -x /etc/travelmate/my.login user pass; echo "rc: ${?}". Make sure it only exits0when the login really succeeded - a script that reports success too eagerly is worse than one that fails, because travelmate will happily keep the uplink.
The portal domain travelmate detected is in the system log: logread -e "trm-" shows it as captive portal domain '<domain>' added to dhcp rebind allowlist.
Hopefully more scripts for different captive portals will be provided by the community!
Best practice and tweaks
Radio assignment
If your router has more than one radio, keep the AP and the uplink on separate radios - sharing one radio between both costs a noticeable amount of throughput. Use trm_radio to restrict travelmate to the uplink radio(s) and trm_revradio to change the processing order, e.g. to prefer 5GHz over 2.4GHz.
Signal quality thresholdstrm_minquality (default 35%) decides twice: a scan result below the threshold is not considered as a connection candidate at all, and an existing connection that drops below it is torn down so that travelmate can look for something better. Raising the value makes travelmate switch away earlier, lowering it keeps weak uplinks alive longer. Values well above 50% tend to make travelmate restless in hotels.
Retry behaviourtrm_maxretry (default 3) limits the connection attempts per uplink. When the limit is reached, the affected uplink is disabled in the travelmate config and has to be re-enabled manually - which is intentional for permanently broken credentials, but worth keeping in mind in combination with trm_netcheck. Set trm_maxretry to '0' for unlimited retries if you never want an uplink to be disabled automatically.
Per uplink, revive softens that. With a value of n, an uplink disabled by the retry limit is set back to 'enabled' after n run cycles, so a network that was only temporarily unavailable comes back on its own. Values below 10 are raised to 10, since a run cycle is roughly trm_timeout seconds long and shorter intervals only produce needless reconnect attempts. The number of revivals is capped by the global trm_maxretry, which also means that revive has no effect with trm_maxretry set to '0'. The cycle and round counters are kept in the runtime file '/var/run/travelmate/travelmate.revive', they are never written to the config and are cleared when the service is stopped. The enable and disable itself is still recorded in the travelmate config, so the LuCI station list always shows the real state.
Open uplinkstrm_autoadd adds open networks to your wireless config on the fly, which is handy in hotels and on trains. Keep trm_maxautoadd at a sane value so that a busy location doesn't flood your config, and use trm_ssidfilter to skip the usual noise, e.g. Chromecast* or printer and camera SSIDs.
Privacy and evil twin protectiontrm_randomize generates a new random unicast MAC address for each uplink connection, so a hotspot operator can't trivially recognize your router across visits. trm_eviltwin skips access points with a locally administered (LAA) BSSID, which is a cheap indicator for a spoofed access point - note that some legit setups (mesh, repeaters) use LAA BSSIDs as well.
Timingtrm_maxwait (default 30s) is the budget for a single connection attempt and also scales the internal curl timeouts. On slow uplinks or with captive portals that take their time, raising it to 45-60s helps, lowering it makes travelmate give up on weak candidates faster. trm_triggerdelay adds a delay before processing starts after an ifup event, which is useful if your uplink needs a moment to settle.
Subnet conflicts
Travelmate compares the uplink subnet against your router LAN network and logs a warning if both overlap. If you run into this frequently, change your LAN network to something uncommon, e.g. 10.200.1.0/24, because most hotel networks use 192.168.0.0/24 or 192.168.1.0/24.
Troubleshooting & debug options
Travelmate provides an optional debug mode that writes detailed diagnostic information about every processing step to the system log. Under normal conditions only the relevant status messages are logged, to keep regular runs clean and silent. To enable debug mode, set the option trm_debug to 1.
Whenever you encounter travelmate related processing problems, please enable trm_debug, restart travelmate and check the Log View tab in LuCI (or the syslog via logread -e "trm-").
Typical symptoms:
- No uplink is found although it is in range: compare the reported signal quality in the debug log against
trm_minquality, and make sure the uplink is still enabled in the travelmate config - the retry limit may have disabled it - An uplink connects but has no internet: the debug output of
f_netshows the probe host, the effective url and the resulting state (net ok,net cp '<domain>'ornet nok). Anet cpstate means a captive portal was detected and is waiting for a login - A captive portal login script doesn't run: it has to be executable and located in
/etc/travelmatewith the extension.login, everything else is rejected for security reasons - The connection drops right after connecting: check for a subnet conflict warning between the uplink network and your router LAN network
Support
Please join the travelmate discussion in this forum thread or contact me by mail [email protected]
If you want to report an error, please describe it in as much detail as possible - with (debug) logs, the current travelmate status and your travelmate configuration.
Removal
Stop the travelmate daemon with /etc/init.d/travelmate stop and remove the travelmate packages if necessary.
Donations
You like this project - is there a way to donate? Generally speaking "No" - I have a well-paying full-time job and my OpenWrt projects are just a hobby of mine in my spare time.
If you still insist to donate some bucks ...
- I would be happy if you put your money in kind into other, social projects in your area, e.g. a children's hospice
- Let's meet and invite me for a coffee if you are in my area, the “Markgräfler Land” in southern Germany or in Switzerland (Basel)
- Send your money to my PayPal account and I will collect your donations over the year to support various social projects in my area
No matter what you decide - thank you very much for your support!
Have fun!
Dirk
Overview (`net/uneighbord/README.md`)
uneighbord
uneighbord synchronizes hostapd 802.11k Neighbor Reports between localhostapd.* BSSes and other OpenWrt access points on the same LAN, over
IPv6 link-local UDP multicast. Reports are only shared between BSSes with
the same SSID, and installed through hostapd's ubus API.
Requirements
ucode, ucode-mod-ubus, ucode-mod-uloop, ucode-mod-socket, ucode-mod-uci, ucode-mod-log.
Build and install
make menuconfig # select Network -> uneighbord
make package/uneighbord/compile V=sInstall the resulting package, then:
/etc/init.d/uneighbord enable
/etc/init.d/uneighbord startConfiguration
config uneighbord 'main'
option enabled '1'
option network 'lan'network is an OpenWrt logical network name (not a device name); itsl3_device is used for multicast membership and scope.
How it works
Every 30 seconds each AP announces its local Neighbor Reports (fromrrm_nr_get_own) as a small JSON packet to a fixed IPv6 multicast group on
port 32027. For each local BSS, same-SSID reports from other local BSSes
and remote peers are merged, excluding the BSS's own entry, and installed
via rrm_nr_set. Remote peers are dropped after 90 seconds without a
fresh announcement.
uneighbord assumes a trusted L2/backhaul network: announcements are
unauthenticated, so any host able to reach the multicast group can
influence the installed Neighbor Report list.
Debugging
ubus list 'hostapd.*'
ubus call hostapd.<interface> rrm_nr_list
logread -e uneighbord
ss -6 -u -a -n | grep 32027
ip -6 maddr show dev br-lanOverview (`net/wg-installer/README.md`)
Wireguard Installer
This tool can be used to automatically create wireguard tunnels. Using rpcd a new wireguard interface is created on the server where the client can connect to.
Installation
For Server
opkg install wireguard-installer-serverFor Client
opkg install wireguard-installer-clientWiregurad server automatically installs a user and associated ACL to use the wireguard-installer-server features.
The user is called wginstaller and so is the password.
Usage
Get Usage Statistics
wg-client-installer get_usage --ip 127.0.0.1 --user wginstaller --password wginstallerRegister Tunnel Interface
wg-client-installer register --ip 127.0.0.1 --user wginstaller --password wginstaller --bandwidth 10Hotplugs
- wg-installer-server-hotplug-babeld: mesh automatically via wireguard with babeld
Overview (`net/wgsd/README.md`)
Wireguard Service Discovery (Mesh)
This tool allows you to build a mesh from wireguard tunnels and also traversal NAT.
See this article: https://www.jordanwhited.com/posts/wireguard-endpoint-discovery-nat-traversal/
Also see project on github: https://github.com/jwhited/wgsd
Usage
On server edit /etc/Corefile to select on which port DNS-SD should be serving and also which interface to use.
Here is the example, where port 5353 will be used by coredns, coredns.lan. zone be used for discovery and vpn_wg interface be used to gather peers information.
.:5353 {
log
#whoami
wgsd coredns.lan. vpn_wg
}On a client you should put to the cron line like that:
1,6,11,16,21,26,31,36,41,46,51,56 * * * * /usr/bin/wgsd-client -device vpn_wg -dns your.central.node:5353 -zone coredns.lanNote
All peers that should connect to each other should know other peers.
So you should setup your central node as a first peer followed with peers.
E.g. let's say we have a Cloud-Router (CR), Alice and the Bob. Then you should configure peers for CR (with the address) and Bob on Alice's side and CR and Alice on Bob's.
Overview (`net/wifischedule/README.md`)
wifischedule
Turns WiFi on and off according to a schedule on an openwrt router
Components
- wifischedule: Shell script that creates cron jobs based on configuration provided in UCI and does all the other logic of enabling and disabling wifi with the use of
/sbin/wifiand/usr/bin/iwinfo. Can be used standalone. - luci-app-wifischedule: LUCI frontend for creating the UCI configuration and triggering the actions. Depends on wifischedule.
Use cases
You can create user-defined events when to enable or disable WiFi.
There are various use cases why you would like to do so:
- Reduce power consumption and therefore reduce CO2 emissions.
- Reduce emitted electromagnatic radiation.
- Force busincess hours when WiFi is available.
Regarding 1: Please note, that you need to unload the wireless driver modules in order to get the most effect of saving power.
In my test scenario only disabling WiFi saves about ~0.4 Watt, unloading the modules removes another ~0.4 Watt.
Regarding 2: Think of a wireless accesspoint e.g. in your bedrom, kids room where you want to remove the ammount of radiation emitted.
Regarding 3: E.g. in a company, why would wireless need to be enabled weekends if no one is there working?
Or think of an accesspoint in your kids room when you want the youngsters to sleep after 10 pm instead of facebooking...
Configuration
You can create an arbitrary number of schedule events. Please note that there is on sanity check done wheather the start / stop times overlap or make sense.
If start and stop time are equal, this leads to disabling the WiFi at the given time.
Logging if enabled is done to the file /var/log/wifi_schedule.log and can be reviewed through the "View Logfile" tab.
The cron jobs created can be reviewed through the "View Cron Jobs" tab.
Please note that the "Unload Modules" function is currently considered as experimental. You can manually add / remove modules in the text field.
The button "Determine Modules Automatically" tries to make a best guess determining regarding the driver module and its dependencies.
When un-/loading the modules, there is a certain number of retries (module_load) performed.
The option "Force disabling wifi even if stations associated" does what it says - when activated it simply shuts down WiFi.
When unchecked, its checked every recheck_interval minutes if there are still stations associated. Once the stations disconnect, WiFi is disabled. To ignore associated stations add their MAC to ignore_stations.
Please note, that the parameters module_load, recheck_interval and ignore_stations are only accessible through uci.
UCI Configuration `wifi_schedule`
UCI configuration file: /etc/config/wifi_schedule:
config global
option logging '0'
option enabled '0'
option recheck_interval '10'
option modules_retries '10'
# option ignore_stations 'AA:AA:AA:AA:AA:AA BB:BB:BB:BB:BB:BB'
config entry 'Businesshours'
option enabled '0'
option daysofweek 'Monday Tuesday Wednesday Thursday Friday'
option starttime '06:00'
option stoptime '22:00'
option forcewifidown '0'
config entry 'Weekend'
option enabled '0'
option daysofweek 'Saturday Sunday'
option starttime '00:00'
option stoptime '00:00'
option forcewifidown '1'Script: `wifi_schedule.sh`
This is the script that does the work. Make your changes to the UCI config file: /etc/config/wifi_schedule
Then call the script as follows in order to get the necessary cron jobs created:
wifi_schedule.sh cron
All commands:
wifi_schedule.sh cron|start|startup|stop|forcestop|recheck|getmodules|savemodules|help
cron: Create cronjob entries.
start: Start wifi.
startup: Checks current timewindow and enables/disables WIFI accordingly.
stop: Stop wifi gracefully, i.e. check if there are stations associated and if so keep retrying.
forcestop: Stop wifi immediately.
recheck: Recheck if wifi can be disabled now.
getmodules: Returns a list of modules used by the wireless driver(s)
savemodules: Saves a list of automatic determined modules to UCI
help: This description.Startup Script: `/etc/init.d/wifi_schedule`
Makes sure time window is checked and WIFI is enabled or disabled accordingly when powering on the router.
Overview (`net/xl2tpd/README.md`)
OpenWrt Package for xl2tpd
xl2tpd is a development from the original l2tpd package originally written by
Mark Spencer, subsequently forked by Scott Balmos and David Stipp, inherited
by Jeff McAdams, modified substantially by Jacco de Leeuw and then forked
again by Xelerance (after it was abandoned by l2tpd.org).
Rationale for inclusion in OpenWrt
l2tpd has some serious alignment problems on RISC platforms. It also runs
purely in userspace.
Some of the features added in this fork include:
IPSec SA reference tracking inconjunction with openswan's IPSec transport
mode, which adds support for multiple clients behind the same NAT router
and multiple clients on the same internal IP behind different NAT routers.Support for the pppol2tp kernel mode L2TP.
Alignment and endian problems resolved.
hcg
UCI options
server takes the form host[:port] with port defaults to 1701. It
specifies the l2tp server's address.
checkup_interval tells netifd to check after that many seconds since last
setup attempt to see if the interface is up. If not it should issue another
teardown/setup round to retry the negotiation. This option defaults to 0 and
netifd will not do the check and retry.
The following are generic ppp options and should have the same format and
semantics as with other ppp-related protocols. See
uci/network#protocol_ppp
for details.
username
password
keepalive
ipv6
mtu
pppd_optionsOverview (`utils/cache-domains/README.md`)
cache-domains
hotplug script to dynamically configure the local DNS (dnsmasq) to redirect game content servers to a LAN cache. Definitive list dynamically obtained from https://github.com/uklans/cache-domains.
Configuration
The configuration file (/etc/cache-domains.json) follows the same syntax as the upsteam file. The key for each cache_domains member matches the name of one of the .txt files in the upstream root directory, except for the default key which matches the all the unreferenced .txt files. The value of each cache_domains member maps to one of the keys of the ips members, Thus mapping a cached domain to a list of IP addresses/LAN cache server.
{
"ips": {
"server1": ["10.10.3.10", "10.10.3.11"],
"server2": "10.10.3.12",
"server3": "10.10.3.13"
},
"cache_domains": {
"default": "server1",
"blizzard": "server1",
"origin": "server1",
"steam": "server2",
"wsus": "server3",
"xboxlive": "server3"
}
}Configure/Cleanup
/usr/bin/cache-domains configure will configure the local DNS (dnsmasq) to redirect the configured cache domains. /usr/bin/cache-domains cleanup will cleanup redirection. The hotplug script calls /usr/bin/cache-domains configure when the WAN interface is brought up.
Testing
After configuring with the above example configuration, running nslookup lancache.steamcontent.com would return 10.10.3.12
Overview (`utils/pservice/README.md`)
uci
disabled, bool, default 0
name, string, name of the service instance
command, file, the service instance executable
args, list of args
stderr, bool, default 0, log stderr output of the service instance
stdout, bool, default 0, log stdout output of the service instance
env, list of environment variable settings of the form var=val
file, list of file names. Service instances will be restarted if content of
these files have changed on service reload event.
respawn_threshold, uinteger, default 3600, time in seconds the instances
have to be in running state to be considered a valid run
respawn_timeout, uinteger, default 5, time in seconds the instance should
be delayed to start again after the last crash
respawn_maxfail, uinteger, default 5, maximum times the instances can
crash/fail in a row and procd will not try to bring it up again after this
limit has been reached
notes and faq
Initial environment variables presented to service instances may be different
from what was observed on the interactive terminal. E.g. HOME=/ may affect
reading ~/.ssh/known_hosts of dropbear ssh instance.
PATH=/usr/sbin:/usr/bin:/sbin:/bin PWD=/ HOME=/If list args xxx seems to be too long causing pain, consider using /bin/sh
as the command. It is also worth noting that uci supports multi-line option
value.
Child processes will keep running when their parent process was killed. This
is especially the case and should be taken into account with option command
being /bin/sh and it is recommended to use exec as the last shell command.
Overview (`lang/python/python3-packages/README.md`)
This package allows users to package python3 modules without creating package
Makefiles for each individual module and their dependencies. It provides a
way making packaging python3 packages faster and may also facilitate the process
of developing Makefiles for new python3 packages
This is a raw DEVEL only package. Using it may entails a lot of implementation
details and you may need to resolve target dependencies and package details on
your own
- Third party python3 packages may depend on features not included in e.g.
python3-light - Some python3 modules may require host install of another module to progress,
e.g. target cryptography requires host cffi - Some python3 modules have external C library dependencies, e.g. pyOpenSSL
requires openssl libs - Some packages may have an autoconf configure script whose arguments we
cannot control with pip and has to be passed on (hacked) by overriding some
environment variables
How it works
- Install host modules required for building target modules
- Install each target module to separate directories
- Install another copy of modules for cleanup purposes to make list of
installed files to be removed from target modules installed in step 2
Why should it be so
- Installing target cryptography requires host installation of cffi module
- cryptography requires setuptools and pip will install its own copy with
--ignore-installed. When PACKAGE_python3-setuptools is also selected, opkg
will complain of data file clashes if it was not removed here.
Pip will handle dependency requirements of python3 modules, but external
dependencies like c libraries has to be prepared by the build system. The
issue is that there is currently no way to express such dependencies, thus may
cause build failure, e.g. pycrypto requires the presence of libgmp to build
successfully.
Tips
If something goes wrong, we can add additional arguments to pip command
line to check the detailed build process. Some useful arguments may be
- -v, for verbose output. Repeat this option if the current level of
verbosity is not enough - --no-clean, for preserving pip build dir on build failure
Examples
tornado (python-only module)
CONFIG_PACKAGE_python3-packages=y
CONFIG_PACKAGE_python3-packages-list="tornado==6.0.2"cryptography (requires installation of host modules and cleanup on target modules)
CONFIG_PACKAGE_python3-packages=y
CONFIG_PACKAGE_python3-packages-list-host="cffi"
CONFIG_PACKAGE_python3-packages-list="cryptography"
CONFIG_PACKAGE_python3-packages-list-cleanup="setuptools"pycrypto 2.7a1 (python module with autoconf configure script; depends on
libgmp; broken wmmintrin.h). 2.6.1 does not work because of a flaw in
the setup.py hardcoding host include directory
CONFIG_PACKAGE_libgmp=y
CONFIG_PACKAGE_python3-packages=y
CONFIG_PACKAGE_python3-packages-list="https://github.com/dlitz/pycrypto/archive/v2.7a1.tar.gz"
CONFIG_PACKAGE_python3-packages-envs="ac_cv_header_wmmintrin_h=no build_alias=$(GNU_HOST_NAME) host_alias=$(GNU_TARGET_NAME) target_alias=$(GNU_TARGET_NAME)"
CONFIG_PACKAGE_python3-packages-extra-deps="libgmp.so.10"Overview (`net/jool/files/readme.md`)
[Jool](https://nicmx.github.io/Jool/en/index.html)
Documentation
You might also want to see contact info.
Usage
Start script
This package includes a start script that will:
- Read the configuration file
/etc/config/jool - Determine what services are active
- Run
joolwith procd
For now this means that
- The services will be disabled by default in the uci config
(/etc/config/jool) - The only uci configuration support available for the package is to enable or disable each instance or the entire deamon
- There is no uci support and configuration will be saved at
/etc/jool/ - Only one instance of jool(nat64) can run with the boot script
- Only one instance of jool(siit) can run with the boot script
- For now there is no way of overriding of the configuration file's paths
The configuration files the startup script uses for each jool instance are:
- jool(nat64):
/etc/jool/jool-nat64.conf.json - jool(siit):
/etc/jool/jool-siit.conf.json
OpenWrt tutorial
For a more detailed tutorial refer to this wiki page.
Overview (`net/nebula/files/README.md`)
README
README is available at https://docs.openwrt.melmac.net/nebula/.
Overview (`net/speedtest-netperf/files/README.md`)
Network Performance Testing
Introduction
The speedtest-netperf package provides a convenient means of on-device network performance testing for OpenWrt routers. Such performance testing primarily includes characterizing the network throughput and latency, but CPU usage can also be an important secondary measurement. These aspects of network testing are motivated chiefly by the following:
Throughput: Network speed measurements can help troubleshoot transfer problems, and be used to determine the truth of an ISP's promised speed claims. Accurate throughput numbers also provide guidance for configuring other software's settings, such as SQM ingress/egress rates, or bandwidth limits for Bittorrent.
Latency: Network latency is a key factor in high-quality experiences with real-time or interactive applications such as VOIP, gaming, or video conferencing, and excessive latency can lead to undesirable dropouts, freezes and lag. Such latency problems are endemic on the Internet and often the result of bufferbloat. Systematic latency measurements are an important part of identifying and mitigating this bufferbloat.
CPU Usage: Observing CPU usage under network load gives insight into whether the router is CPU-bound, or if there is CPU "headroom" to support even higher network throughput. In addition to managing network traffic, a router actively running a speed test will also use CPU cycles to generate network load, and measuring this distinct CPU usage also helps gauge its impact.
Note: The speedtest-netperf.sh script uses servers and network bandwidth that are provided by generous volunteers. Feel free to use the script to test your SQM configuration or troubleshoot network and latency problems, but be warned that continuous or high-rate use of the servers may result in denied access. Happy testing!
Theory of Operation
When launched, speedtest-netperf.sh uses the local netperf application to run several upload and download streams (files) to a server on the Internet. This places a heavy load on the bottleneck link of your network (probably your connection to the Internet) while measuring the total bandwidth of the link during the transfers. Under this network load, the script simultaneously measures the latency of pings to see whether the file transfers affect the responsiveness of your network. Additionally, the script tracks the per-CPU processor usage, as well as the CPU usage of the netperf instances used for the test. On systems that report CPU frequency scaling, the script can also report per-CPU frequencies.
The script operates in two distict modes for network loading: sequential and concurrent. In the default sequential mode, the script emulates a web-based speed test by first downloading and then uploading network streams. In concurrent mode, the script mimics the stress test of the FLENT program by dowloading and uploading streams simultaneously.
Sequential mode is preferred when measuring peak upload and download speeds for SQM configuration or testing ISP speed claims, because the measurements are unimpacted by traffic in the opposite direction.
Concurrent mode places greater stress on the network, and can expose additional latency problems. It provides a more realistic estimate of expected bidirectional throughput. However, the download and upload speeds reported may be considerably lower than your line's rated speed. This is not a bug, nor is it a problem with your internet connection. It's because the ACK (acknowledge) messages sent back to the sender may consume a significant fraction of a link's capacity (as much as 50% with highly asymmetric links, e.g 15:1 or 20:1).
The script also supports measuring latency when idle as a baseline prior to measuring under network load. This allows better determination of induced latency under load, a critical bufferbloat parameter. The CPU details can also be used to verify idle conditions or examine CPU frequency against ping variations and jitter (e.g. tracing high jitter to occasional low CPU frequency operation).
After running speedtest-netperf.sh, if latency is seen to increase much during the data transfers, then other network activity, such as voice or video chat, gaming, and general interactive usage will likely suffer. Gamers will see this as frustrating lag when someone else uses the network, Skype and FaceTime users will see dropouts or freezes, and VOIP service may be unusable.
Installation
This package and its dependencies should be installed from the official OpenWrt software repository with the command:opkg install speedtest-netperf
If unavailable, search for and try to directly download the same package for a newer OpenWrt release, since it is architecture-independent and very portable.
As a last resort, you may download and install the latest version directly from the author's personal repo: e.g.
cd /tmp
uclient-fetch https://github.com/guidosarducci/papal-repo/raw/master/speedtest-netperf_1.0.0-1_all.ipk
opkg install speedtest-netperf_1.0.0-1_all.ipkUsage
The speedtest-netperf.sh script measures throughput, latency and CPU usage during file transfers and when idle. To invoke it:
speedtest-netperf.sh [-4 | -6] [-H netperf-server] [-t duration] [-p host-to-ping] [-n simultaneous-streams ] [-s | -c [duration] ] [ -i [duration] ]Options, if present, are:
-4 | -6: Enable ipv4 or ipv6 testing (default - ipv4)
-H | --host: DNS or Address of a netperf server (default - netperf.bufferbloat.net)
Alternate servers are netperf-east (US, east coast),
netperf-west (US, California), and netperf-eu (Denmark).
-t | --time: Duration for how long each direction's test should run - (default - 30 seconds)
-p | --ping: Host to ping to measure latency (default - one.one.one.one)
-n | --number: Number of simultaneous sessions (default - 5 sessions)
-s | --sequential: Sequential download/upload (default - disabled)
-c | --concurrent: Concurrent download/upload (default - disabled)
-i | --idle: Measure idle latency before speed test (default - disabled)The primary script output shows download and upload speeds, together with the percent packet loss, and a summary of latencies, including min, max, average, median, and 10th and 90th percentiles so you can get a sense of the distribution.
The tool also summarizes CPU usage statistics during the test, to highlight whether speeds may be CPU-bound during testing, and to provide a better sense of how much CPU "headroom" would be available during normal operation. The data includes per-CPU load and frequency (if supported), and CPU usage of the netperf test programs.
Examples
Below is a comparison of sequential speed testing runs showing the benefits of SQM. On the left is a test without SQM. Note that the latency gets large (greater than half a second), meaning that network performance would be poor for anyone else using the network. On the right is a test using SQM: the latency goes up a little (less than 21 msec under load), and network performance remains good.
Notice also that the activation of SQM requires greater CPU, but that in both cases the router is not CPU-bound and likely capable of supporting higher throughputs.
/* Detailed source-code truncated for AI context efficiency. */Below is another comparison of SQM, but now using a concurrent speedtest. Notice that without SQM, the total throughput drops nearly 11 Mbps compared to the above sequential test without SQM. This is due to both poorer latencies and the consumption of bandwidth by ACK messages. As before, the use of SQM on the right not only yields a marked improvement in latencies, but also recovers almost 6 Mbps in throughput (with SQM using CAKE's ACK filtering).
/* Detailed source-code truncated for AI context efficiency. */Provenance
The speedtest-netperf.sh utility leverages earlier scripts from the CeroWrt project used to measure network throughput and latency: betterspeedtest.sh and netperfrunner.sh. Both scripts are gratefully used with the permission of their author, Rich Brown.
Overview (`net/stubby/files/README.md`)
Stubby for OpenWrt
Stubby Description
Stubby is
an application that acts as a local DNS Privacy stub resolver (using
DNS-over-TLS). Stubby encrypts DNS queries sent from a client machine to a DNS
Privacy resolver increasing end user privacy.
Stubby is useful on an OpenWrt device, because it can sit between the usual DNS
resolver (dnsmasq by default) and the upstream DNS resolver and be used to
ensure that DNS traffic is encrypted between the OpenWrt device and the
resolver.
Stubby is developed by the getdns project.
For more background and FAQ see the About
Stubby page.
Installation
Installation of this package can be achieved at the command line using opkg install stubby, or via the LUCI Web Interface. Installing the stubby package
will also install the required dependency packages, including theca-bundle package.
Configuration
The default configuration of the package has been chosen to ensure that stubby
should work after installation.
By default, configuration of stubby is integrated with the OpenWrt UCI system
using the file /etc/config/stubby. The configuration options available are
also documented in that file. If for some reason you wish to configure stubby
using the /etc/stubby/stubby.yml file, then you simply need to set option manual '1' in /etc/config/stubby and all other settings in/etc/config/stubby will be ignored.
Stubby port and addresses
The default configuration ensures that stubby listens on port 5453 on the
loopback interfaces for IPv4 and IPv6. As such, by default, stubby will respond
only to lookups from the OpenWrt device itself.
By setting the listening ports to non-standard values, this allows users to keep
the main name server daemon in place (dnsmasq/unbound/etc.) and have that name
server forward to stubby.
Upstream resolvers
The default package configuration uses the CloudFlare resolvers, configured for
both IPv4 and IPv6.
CloudFlare have not published SPKI pinsets, and even though they are available,
they have made no commitment to maintaining them. Using the currently known SPKI
pinsets for CloudFlare brings the risk that in the future they may be changed by
CloudFlare, and DNS would stop working. The default configuration has those SPKI
entries commented out for this reason.
CloudFlare's privacy
statement
details how they treat data from DNS requests.
More resolvers are available in the upstream stubby example
configuration
and the DNS Privacy
list.
Integration of stubby with dnsmasq
The recommended way to use stubby on an OpenWrt device is to integrate it with a
caching resolver. The default caching resolver in OpenWrt is dnsmasq.
Set dnsmasq to send DNS requests to stubby
Since dnsmasq responds to LAN DNS requests on port 53 of the OpenWrt device by
default, all that is required is to have dnsmasq forward those requests to
stubby which is listening on port 5453 of the OpenWrt device. To achieve this,
we need to set the server option in the dnsmasq configuration in the/etc/config/dhcp file to '127.0.0.1#5453'. We also need to tell dnsmasq not
to use resolvers found in /etc/resolv.conf by setting the dnsmasq optionnoresolv to 1 in the same file. This can be achieved by editing the/etc/config/dhcp file directly or executing the following commands at the
command line:
uci add_list dhcp.@dnsmasq[-1].server='127.0.0.1#5453'
uci set dhcp.@dnsmasq[-1].noresolv=1
uci commit && reload_configThe same outcome can be achieved in the LUCI web interface as follows:
- Select the Network->DHCP and DNS menu entry.
- In the "General Settings" tab, enter the address
127.0.0.1#5453as the only
entry in the "DNS Forwardings" dialogue. - In the "Resolv and Host files" tab tick the "Ignore resolve file" checkbox.
Disable sending DNS requests to ISP provided DNS servers
The configuration changes in the previous section ensure that DNS queries are
sent over TLS encrypted connections once dnsmasq and stubby are started. When
the OpenWrt device is first brought up, there is a possibility that DNS queries
can go to ISP provided DNS servers ahead of dnsmasq and stubby being active. In
order to mitigate this leakage, it's necessary to ensure that upstream resolvers
aren't available, and the only DNS resolver used by the system is
dnsmasq+stubby.
This requires setting the option peerdns to 0 and the option dns to the
loopback address for both the wan and wan6 interfaces in the/etc/config/network file. This can be achieved by editing the/etc/config/network file directly, or by executing the following commands:
uci set network.wan.peerdns='0'
uci set network.wan.dns='127.0.0.1'
uci set network.wan6.peerdns='0'
uci set network.wan6.dns='0::1'
uci commit && reload_configThe same outcome can also be achieved using the LUCI web interface as follows:
- Select the Network->Interfaces menu entry.
- Click on Edit for the WAN interfaces.
- Choose the Advanced Settings tab.
- Unselect the "Use DNS servers advertised by peer" checkbox
- Enter
127.0.0.1in the "Use custom DNS servers" dialogue box. - Repeat the above steps for the WAN6 interface, but use the address
0::1
instead of127.0.0.1.
Enabling DNSSEC
The configuration described above ensures that DNS queries are executed over TLS
encrypted links. However, the responses themselves are not validated; DNSSEC
provides the ability to validate returned DNS responses, and mitigate against
DNS poisoning risks.
With the combination of stubby+dnsmasq there are two possible ways to enable
DNSSEC:
- Configure stubby to perform DNSSEC validation, and configure dnsmasq to proxy
the DNSSEC data to clients. - Configure stubby not to perform DNSSEC validation and configure dnsmasq to
require DNSSEC validation.
Either option achieves the same outcome, and there appears to be little reason
for choosing one over the other other than that the second option is easier to
configure in the LUCI web interface. Both options are detailed below, and both
require that the dnsmasq package on the OpenWrt device is replaced with thednsmasq-full package. That can be achieved by running the following command:
opkg install dnsmasq-full --download-only && opkg remove dnsmasq && opkg install dnsmasq-full --cache . && rm *.ipkDNSSEC by stubby
Configuring stubby to perform DNSSEC validation requires setting the stubby
configuration option dnssec_return_status to '1' in /etc/config/stubby,
which can be done by editing the file directly or by executing the commands:
uci set stubby.global.dnssec_return_status=1
uci commit && reload_configWith stubby performing DNSSEC validation, dnsmasq needs to be configured to
proxy the DNSSEC data to clients. This requires setting the option proxydnssec
to 1 in the dnsmasq configuration in /etc/config/dhcp. That can be achieved by
the following commands:
uci set dhcp.@dnsmasq[-1].proxydnssec=1
uci commit && reload_configDNSSEC by dnsmasq
Configuring dnsmasq to perform DNSSEC validation requires setting the dnsmasq
option dnssec to 1 in the /etc/config/dhcp file. In addition, it is
advisable to also set the dnsmasq option dnsseccheckunsigned to 1. this can
be achieved by editing the file /etc/config/dhcp or by executing the following
commands:
uci set dhcp.@dnsmasq[-1].dnssec=1
uci set dhcp.@dnsmasq[-1].dnsseccheckunsigned=1
uci commit && reload_configThe same options can be set in the LUCI web interface as follows:
- Select the "Network->DHCP and DNS" menu entry.
- Select the "Advanced Settings" tab.
- Ensure both the "DNSSEC" and "DNSSEC check unsigned" check boxes are ticked.
Validating DNSSEC operation
Having configured DNSSEC validation using one of the two approaches above, it's
important to check it's actually working. The following command can be used:
dig dnssectest.sidn.nl +dnssec +multi @192.168.1.1This command should return output like the following:
; <<>> DiG 9.11.4-P1-RedHat-9.11.4-5.P1.fc28 <<>> dnssectest.sidn.nl +dnssec +multi @192.168.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26579
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;dnssectest.sidn.nl. IN A
;; ANSWER SECTION:
dnssectest.sidn.nl. 14399 IN A 213.136.9.12
dnssectest.sidn.nl. 14399 IN RRSIG A 8 3 14400 (
20181104071058 20181005071058 42033 sidn.nl.
YAQl3tef36M9EQUOmCneHKCCkxox3csLpfUOql5i/6ND
zPrQFsNr3g32HPoxOsi+hD2BE5+bEsnARayDSVLyx0qU
6Hpi2rzQ0zGNZZkCJhCsdp3wnM1BWlMgPrCD0iIsJDok
+DH5zu+yYufVUdSLQrMqA3MZDFUIqDUqSZuYDF4= )
;; Query time: 77 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Sat Oct 06 20:36:25 BST 2018
;; MSG SIZE rcvd: 230The key thing to note is the flags: qr rd ra ad part - the ad flag signifies
that DNSSEC validation is working. If that flag is absent DNSSEC validation is
not working.
Appendix: stubby configuration options
This section details the options available for use in the /etc/config/stubby
file. The global configuration section specifies the configuration parameters
for the stubby daemon. One or more resolver sections are used to configure
upstream resolvers for the stubby daemon to use.
`global` section options
`option manual`
Specify whether to use this file to configure the stubby service. If this is set
to '1' stubby will be configured using the file /etc/stubby/stubby.yml. If this
is set to '0', configuration options will be taken from this file, and the service
will be managed through UCI.
`option trigger`
This specifies an interface to trigger stubby start up on; stubby startup will
be triggered by a procd signal associated with this interface being ready. If
this interface is restarted, stubby will also be restarted.
This option can also be set to 'timed', in which case a time, specified by the
option triggerdelay, will be waited before starting stubby.
`option triggerdelay`
If the trigger option specifies an interface, this option sets the time that
is waited after the procd signal is received before starting stubby.
If trigger is set to 'timed' then this is the delay before starting stubby.
This option is specified in seconds and defaults to the value '2'.
`list dns_transport`
The dns_transport list specifies the allowed transports. Allowed values are:GETDNS_TRANSPORT_UDP, GETDNS_TRANSPORT_TCP and GETDNS_TRANSPORT_TLS. The
transports are tried in the order listed.
`option tls_authentication`
This option specifies whether TLS authentication is mandatory. A value of '1'
mandates TLS authentication, and is the default.
If this is set to '0', and GETDNS_TRANSPORT_TCP or GETDNS_TRANSPORT_UDP
appears in the dns_transport list, stubby is allowed to fall back to non-TLS
authenticated lookups. You probably don't want this though.
`option tls_query_padding_blocksize`
This option specifies the block size to pad DNS queries to. You shouldn't need
to set this to anything but '128' (the default), as recommended by
https://tools.ietf.org/html/draft-ietf-dprive-padding-policy-03
`option tls_connection_retries`
This option specifies the number of connection failures stubby permits before
Stubby backs-off from using an individual upstream resolver. You shouldn't need
to change this from the default value of '2'.
`option tls_backoff_time`
This option specifies the maximum time in seconds Stubby will back-off from
using an individual upstream after failures. You shouldn't need to change this
from the default value of '3600'.
`option timeout`
This option specifies the timeout on getting a response to an individual
request. This is specified in milliseconds. You shouldn't need to change this
from the default value of '5000'.
`option dnssec_return_status`
This option specifies whether stubby should require DNSSEC validation. Specify
to '1' to turn on validation, and '0' to turn it off. By default it is off.
`option appdata_dir`
This option specifies the location for storing stubby runtime data. In
particular, if DNSSEC is turned on, stubby will store its automatically
retrieved trust anchor data here. The default value is '/var/lib/stubby'.
`option trust_anchors_backoff_time`
When Zero configuration DNSSEC failed, because of network unavailability or
failure to write to the appdata directory, stubby will backoff trying to refetch
the DNSSEC trust-anchor for a specified amount of time expressed in milliseconds
(which defaults to two and a half seconds).
`option dnssec_trust_anchors`
This option sets the location of the file containing the trust anchor data used
for DNSSEC validation. If this is not specified, stubby will automatically
retrieve a trust anchor at startup. It's unlikely you'll want to manage the
trust anchor data manually, so in most cases this is not needed. By default,
this is unset.
`option edns_client_subnet_private`
This option specifies whether to enforce ECS client privacy. The default is'1'. Set to '0' to disable client privacy.
For more details see Section 7.1.2 here.
`option idle_timeout`
This option specifies the time (in milliseconds) to hold TLS connections open to
avoid the overhead of opening a new connection for every query. You should not
normally need to change this from the default value (currently '10000').
See here for more details.
`option round_robin_upstreams`
This option specifies how stubby will use the upstream DNS resolvers. Set to'1' (the default) to instruct stubby to distribute queries across all
available name servers - this will use multiple simultaneous connections which
can give better performance in most (but not all) cases. Set to '0' to treat
the upstream resolvers as an ordered list and use a single upstream resolver
until it becomes unavailable, then use the next one.
`list listen_address`
This list sets the addresses and ports for the stubby daemon to listen for
requests on. the default configuration configures stubby to listen on port 5453
on the loopback interface for both IPv4 and IPv6.
`option log_level`
If set, this option specifies the level of logging from the stubby
daemon. By default, this option is not set.
The possible levels are:
'0': EMERG - System is unusable
'1': ALERT - Action must be taken immediately
'2': CRIT - Critical conditions
'3': ERROR - Error conditions
'4': WARN - Warning conditions
'5': NOTICE - Normal, but significant, condition
'6': INFO - Informational message
'7': DEBUG - Debug-level message`option command_line_arguments`
This option specifies additional command line arguments for
stubby daemon. By default, this is an empty string.
`option tls_cipher_list`
If set, this specifies the acceptable ciphers for DNS over TLS. With OpenSSL
1.1.1 this list is for TLS1.2 and older only. Ciphers for TLS1.3 should be set
with the tls_ciphersuites option. This option can also be given per upstream
resolver. By default, this option is not set.
`option tls_ciphersuites`
If set, this specifies the acceptable cipher for DNS over TLS1.3. OpenSSL
version 1.1.1 or greater is required for this option. This option can also be
given per upstream resolver. By default, this option is not set.
`option tls_min_version`
If set, this specifies the minimum acceptable TLS version. Works with OpenSSL
1.1.1 or greater only. This option can also be given per upstream resolver. By
default, this option is not set.
`option tls_max_version`
If set, this specifies the maximum acceptable TLS version. Works with OpenSSL
1.1.1 or greater only. This option can also be given per upstream resolver. By
default, this option is not set.
`resolver` section options
`option address`
This option specifies the resolver IP address, and can either be an IPv4 or an
IPv6 address.
`option tls_auth_name`
This option specifies the upstream domain name used for TLS authentication with
the supplied server certificate
`option tls_port`
This option specifies the TLS port for the upstream resolver. If not specified,
this defaults to 853.
`option tls_cipher_list`
If set, this specifies the acceptable ciphers for DNS over TLS. With OpenSSL
1.1.1 this list is for TLS1.2 and older only. Ciphers for TLS1.3 should be set
with the tls_ciphersuites option. By default, this option is not set. If set,
this overrides the global value.
`option tls_ciphersuites`
If set, this specifies the acceptable cipher for DNS over TLS1.3. OpenSSL
version 1.1.1 or greater is required for this option. By default, this option is
not set. If set, this overrides the global value.
`option tls_min_version`
If set, this specifies the minimum acceptable TLS version. Works with OpenSSL
1.1.1 or greater only. By default, this option is not set. If set, this
overrides the global value.
`option tls_max_version`
If set, this specifies the maximum acceptable TLS version. Works with OpenSSL
1.1.1 or greater only. By default, this options is not set. If set, this
overrides the global value.
`list spki`
This list specifies the SPKI pinset which is verified against the keys in the
server certificate. The value takes the form '<digest type>/value>', where
the digest type is the hashing algorithm used, and the value is the Base64
encoded hash of the public key. At present, only sha256 is
supported for the digest type.
This should ONLY be used if the upstream resolver has committed to maintaining
the pinset. CloudFlare have made no such commitment, and so we do not specify
the SPKI values in the default configuration, even though they are available.
Overview (`net/unbound/files/README.md`)
Unbound Recursive DNS Server with UCI
Unbound Description
Unbound is a validating, recursive, and caching DNS resolver. The C implementation of Unbound is developed and maintained by NLnet Labs. It is based on ideas and algorithms taken from a java prototype developed by Verisign labs, Nominet, Kirei and ep.net. Unbound is designed as a set of modular components, so that also DNSSEC (secure DNS) validation and stub-resolvers (that do not run as a server, but are linked into an application) are easily possible.
Package Overview
OpenWrt default build uses dnsmasq for DNS forwarding and DHCP. With a forward only resolver, dependence on the upstream recursors may be cause for concern. They are often provided by the ISP, and some users have switched to public DNS providers. Either way may result in problems due to performance, "snoop-vertising", hijacking (MiM), and other causes. Running a recursive resolver or resolver capable of TLS may be a solution.
Unbound may be useful on consumer grade embedded hardware. It is fully DNSSEC and TLS capable. It is intended to be a recursive resolver only. NLnet Labs NSD is intended for the authoritative task. This is different than ISC Bind and its inclusive functions. Unbound configuration effort and memory consumption may be easier to control. A consumer could have their own recursive resolver with 8/64 MB router, and remove potential issues from forwarding resolvers outside of their control.
This package builds on Unbounds capabilities with OpenWrt UCI. Not every Unbound option is in UCI, but rather, UCI simplifies the combination of related options. Unbounds native options are bundled and balanced within a smaller set of choices. Options include resources, DNSSEC, access control, and some TTL tweaking. The UCI also provides an escape option and works at the raw unbound.conf level.
HOW TO: Ad Blocking
The UCI scripts will work with net/adblock and/or net/adblock-fast, if they are installed and enabled. It's all detected and integrated automatically. In brief, the net/adblock or net/adblock-fast create distinct local-zone files that are simply included in the unbound conf file during UCI generation. If you don't want this, then disable or reconfigure respective package to not send these files to Unbound.
With net/adblock package specifically, a few additional tweaks may be needed to enhance the realiability and effectiveness. Ad Block option for delay time may need to be set for upto one minute (adb_triggerdelay), because of boot up race conditions with interfaces calling Unbound restarts. Also many smart devices (TV, microwave, or refigerator) will also use public DNS servers either as a bypass or for certain connections in general. If you wish to force exclusive DNS to your router, then you will need a firewall rule for example:
/etc/config/firewall:
config rule
option dest 'wan'
option dest_port '53 853 5353'
option enabled '1'
option family 'any'
option name 'Block-Public-DNS'
option proto 'tcpudp'
option src 'lan'
option target 'REJECT'HOW TO: Integrate with DHCP
Some UCI options and scripts help Unbound to work with DHCP servers to load the local DNS. The examples provided here are serial dnsmasq with unbound, parallel dnsmasq with unbound, and unbound scripted with odhcpd.
Serial dnsmasq
In this case, dnsmasq is not changed much with respect to the default OpenWrt configuration. Here dnsmasq is forced to use the local Unbound instance as the lone upstream DNS server, instead of your ISP. This may be the easiest implementation, but performance degradation can occur in high volume networks. Unbound and dnsmasq effectively have the same information in memory, and all transfers are double handled.
/etc/config/unbound:
config unbound
option add_local_fqdn '0'
option add_wan_fqdn '0'
option dhcp_link 'none'
# dnsmasq should not forward your domain to unbound, but if...
option domain 'yourdomain'
option domain_type 'refuse'
option listen_port '1053'
.../etc/config/dhcp:
config dnsmasq
option domain 'yourdomain'
option noresolv '1'
option port '53'
option resolvfile '/tmp/resolv.conf.auto'
list server '127.0.0.1#1053'
list server '::1#1053'
...Parallel dnsmasq
In this case, Unbound serves your local network directly for all purposes. It will look over to dnsmasq for DHCP-DNS resolution. Unbound is generally accessible on port 53, and dnsmasq is only accessed at 127.0.0.1#1053 by Unbound. Although you can dig/drill/nslookup remotely with the proper directives.
/etc/config/unbound:
config unbound
# likely you want to match domain option between Unbound and dnsmasq
option dhcp_link 'dnsmasq'
option domain 'yourdomain'
option listen_port '53'
.../etc/config/dhcp:
config dnsmasq
option domain 'yourdomain'
option noresolv '1'
option port '1053'
option resolvfile '/tmp/resolv.conf.auto'
...
config dhcp 'lan'
# dnsmasq may not issue DNS option if not std. configuration
list dhcp_option 'option:dns-server,0.0.0.0'
...Unbound and odhcpd
You may ask, "can Unbound replace dnsmasq?" You can have DHCP-DNS records with Unbound and odhcpd only. The UCI scripts will allow Unbound to act like dnsmasq. When odhcpd configures each DHCP lease, it will call a script. The script provided with Unbound will read the lease file for DHCP-DNS records. The unbound-control application is required, because simply rewriting conf-files and restarting unbound is too much overhead.
- Default OpenWrt has dnsmasq+odhcpd with
odhcpd-ipv6onlylimited to DHCPv6. - If you use dnsmasq+odhcpd together, then use dnsmasq serial or parallel methods above.
- You must install package
odhcpd(full) to use odhcpd alone. - You must install package
unbound-controlto load and unload leases. - Remember to uninstall (or disable) dnsmasq when you won't use it.
/etc/config/unbound:
config unbound
# name your router in DNS
option add_local_fqdn '1'
option add_wan_fqdn '1'
option dhcp_link 'odhcpd'
# add SLAAC inferred from DHCPv4
option dhcp4_slaac6 '1'
option domain 'lan'
option domain_type 'static'
option listen_port '53'
option rebind_protection '1'
# install unbound-control and set this
option unbound_control '1'
.../etc/config/dhcp:
config dhcp 'lan'
option dhcpv4 'server'
option dhcpv6 'server'
option interface 'lan'
option leasetime '12h'
option ra 'server'
option ra_management '1'
...
config odhcpd 'odhcpd'
option leasefile '/var/lib/odhcpd/dhcp.leases'
# this is where the magic happens
option leasetrigger '/usr/lib/unbound/odhcpd.sh'
option maindhcp '1'HOW TO: Manual Override
Yes, there is a UCI to disable the rest of Unbound UCI. However, OpenWrt or LEDE are targeted at embedded machines with flash ROM. The initialization scripts do a few things to protect flash ROM.
Completely Manual (almost)
All of /etc/unbound (persistent, ROM) is copied to /var/lib/unbound (tmpfs, RAM). Edit your manual /etc/unbound/unbound.conf to reference this /var/lib/unbound location for included files. Note in preparation for a jail, /var/lib/unbound is chown unbound. Configure for security in/etc/unbound/unbound.conf with options username:unbound and chroot:/var/lib/unbound.
Keep the DNSKEY updated with your choice of flash activity. root.key maintenance for DNSKEY RFC5011 would be hard on flash. Unbound natively updates frequently. It also creates and destroys working files in the process. In /var/lib/unbound this is no problem, but it would be gone at the next reboot. If you have DNSSEC (validator) active, then you should consider the age UCI option. Choose how many days to copy from /var/lib/unbound/root.key (tmpfs) to /etc/unbound/root.key (flash).
/etc/config/unbound:
config unbound
option manual_conf '1'
option root_age '9'
# endHybrid Manual/UCI
You like the UCI. Yet, you need to add some difficult to standardize options, or just are not ready to make a UCI request yet. The files /etc/unbound/unbound_srv.conf and /etc/unbound/unbound_ext.conf will be copied to Unbounds chroot directory and included during auto generation.
The file unbound_srv.conf will be added into the server: clause. The file unbound_ext.conf will be added to the end of all configuration. It is for extended forward-zone:, stub-zone:, auth-zone:, and view: clauses. You can also disable unbound-control in the UCI which only allows "localhost" connections unencrypted, and then add an encrypted remote control: clause.
HOW TO: Cache Zone Files
Unbound has the ability to AXFR a whole zone from an authoritative server to prefetch the zone. This can speed up access to common zones. Some may have special bandwidth concerns for DNSSEC overhead. The following is a generic example. UCI defaults include the root zone, but it is disabled as a ready to go example.
/etc/config/unbound:
config zone
option enabled '1'
option fallback '1'
list server 'ns1.it.example.com'
list server 'ns2.it.example.com'
option url_dir 'https://asset-management.it.example.com/zones/'
option zone_type 'auth_zone'
list zone_name 'example.com'HOW TO: TLS Over DNS
Unbound can use TLS as a client or server. UCI supports Unbound as a forwarding client with TLS. Servers are more complex and need manual configuration. This may be desired for privacy against stealth tracking. Some public DNS servers seem to advertise help in this quest. If your looking for a better understanding, then some information can be found at Cloudflare DNS 1.1.1.1. The following is a generic example. You can mix providers by using complete server specificaiton to override the zones common port and certificate domain index.
Update as of Unbound 1.9.1, all TLS functions work correctly with either OpenSSL 1.0.2 or 1.1.0. Please be sure to install ca-bundle package and use opkg to get updates regularly.
/etc/config/unbound:
config zone
option enabled '1'
# question: do you want to recurse when TLS fails or not?
option fallback '0'
option tls_index 'dns.example.net'
option tls_port '853'
option tls_upstream '1'
# these servers assume a common TLS port/index
list server '192.0.2.53'
list server '2001:db8::53'
# this alternate server is fully specified inline
list server '192.0.2.153@443#dns.alternate.example.org'
list zone_name '.'
option zone_type 'forward_zone'Optional Compile Switches
Unbound can be changed by toggling switches within make menuconfig Libraries/Network/libunbound. Disable libevent, libpthread, and ipset to attempt to gain performance and size on small single core targets. These downgrade options are well tested, but they are not needed unless Unbound will not fit. Take care before enabling subnetcache, dnscrypt, and python options. These enhancements are not fully tested within OpenWrt and python is a large dependency. These enhancements are default off and they do not have UCI. You will need to use the files /etc/unbound/unbound_srv.conf and /etc/unbound/unbound_ext.conf to configure these modules. The server: clause line module: subnetcache validator python iterator will be filled out if the modules are compiled in.
Note: if you use python, then you will need to manual configure and you cannot use chroot. The scripts are not yet enhanced enough to set up the directory binding.
Complete UCI
config unbound
One instance is supported currently.
| UCI | Default | Units | Description | Unbound |
|---|---|---|---|---|
| add_extra_dns | 0 | level | Read OpenWrt traditional options for dnsmasq.0: Disabled1: Use only domain2: Use domain, mxhost, and srvhost3: Use all cname, domain, mxhost, and srvhost |
local-data: |
| add_local_fqdn | 0 | level | Each level puts a more detailed router entry within the LAN DNS (except link).0: Disabled1: Host name on the primary address2: Host name on all addresses3: FQDN and host name on all addresses4: FQDN defined by "iface.hostname.domain" |
local-zone: local-data: |
| add_wan_fqdn | 0 | level | Same as add_local_fqdn but on WAN as listed in iface_wan |
local-zone: local-data: |
| dns64 | 0 | boolean | Enable DNS64 RFC6052 to bridge IPv4 and IPv6 networks. | module: dns64 |
| dns64_prefix | 64:ff9b::/96 | subnet | DNS64 RFC6052 IPv4 in IPv6 well known prefix. | dns64-prefix: |
| dhcp_link | none | program | Link to a DHCP server with supported scripts. See HOW TO above. | local-zone: local-data: forward-zone: |
| dhcp4_slaac6 | 0 | boolean | Infer SLAAC IE64 IPv6 addresses from DHCPv4 MAC in DHCP link scripts. | - |
| exclude_ipv6_ga | 0 | boolean | If exclude IPv6 global addresses from local data. | local-data: |
| domain | lan | domain | This will suffix DHCP host records and be the default search domain. | local-zone: |
| domain_insecure | (empty) | domain | List domains that you wish to skip DNSSEC. It is one way around NTP chicken and egg. Your DHCP domains are automatically included. | domain-insecure: |
| domain_type | static | state | This allows you to lock down or allow forwarding of the local zone.static: no forwarding like dnsmasq defaultrefuse: answer overtly with REFUSEDdeny: covertly drop all queriestransparent: may continue forwarding or recusion |
local-zone: |
| edns_size | 1232 | bytes | Extended DNS is necessary for DNSSEC. Use this to manage MTU issues. | edns-size: |
| extended_stats | 0 | boolean | Extended statistics are stored in Unbound memory for report by unbound-control. |
extended-statistics: |
| hide_binddata | 1 | boolean | Refuse possible attack queries like version.server, version.bind, id.server, and hostname.bind. | hide-identity: hide-version: |
| iface_lan | lan | interface | List to add interafaces you wish to consider to be LAN beyond those served by DHCP | interface: access-control: |
| iface_trig | lan wan | interface | List interfaces to watch IFUP to restart Unbound. This works around netifd and procd hyper activity with WAN DHCPv6 (else restart each 2-3 minutes). |
- |
| iface_wan | wan | interface | List interafaces you wish to consider to be WAN for masked local zone purposes | interface-outgoing: |
| interface_auto | 1 | boolean | RECOMMEND ENABLED otherwise Unbound answers to any attached address regardless of query in-address. This also binds Unboud to the wild card address. | interface-automatic: |
| listen_port | 53 | port | Inbound port where Unbound will listen for queries. | port: |
| localservice | 1 | boolean | Prevent DNS amplification attacks. Only answer to subnets this machine has interfaces on. | access-control: |
| manual_conf | 0 | boolean | Skip all this UCI nonsense. Manually edit the configuration in /etc/unbound/unbound.conf. |
- |
| num_threads | 1 | threads | Enable multithreading. Two are supported with only one cache slab. More becomes an industrial setup, and UCI simplificaitons may not be appropriate. | num-threads: |
| protocol | mixed | state | Limit Unbound protocols for recursion or forwarding.ip4_only: old fashioned IPv4 upstream and downstreamip6_only: test environment only; could cauase problemsip6_local: upstream IPv4 only and local network IPv4 and IPv6ip6_prefer: both IPv4 and IPv6 but try IPv6 firstmixed: both IPv4 and IPv6default: built-in defaults |
do-ip4: do-ip6: prefer-ip6: interface: |
| query_minimize | 0 | boolean | Enable a minor privacy option. Query one domain piece at a time (dot separator). | qname-minimisation: |
| query_min_strict | 0 | boolean | Query minimize may fall back to normal. This prevents the fall back, but non-standard name servers will fail. | qname-minimisation-strict: |
| rate_limit | 0 | query/s | Limit the total number of queries and queries per client by half that. Disabled by setting 0. | ratelimit: ip-ratelimit: |
| rebind_localhost | 0 | boolean | Prevent loopback addresses 127.0.0.0/8 or ::1 in upstream responses. They could be a good black hole server or a spring board attack. |
private-address: |
| rebind_protection | 1 | level | Prevent your local addresses from being allowed in upstream responses. They could be a spring board attack.0: Disabled1: Only RFC 1918 and 4193 responses blocked2: Above plus GLA /64 on machine3: Above plus DHCP-PD passed down (not implemented) |
private-address: |
| recursion | passive | state | Unbound has many options for recrusion but UCI is bundled for simplicity.passive: slower until cache fills but kind on CPU loaddefault: built-in defaultsaggressive: uses prefetching to handle more requests quickly |
(many) |
| resource | small | state | Unbound has many options for memory resources but UCI is bundled for simplicity.tiny: similar to published memory restricted configurationsmall: about half of mediummedium: similar to defaultdefault: built-in defaultslarge: about double of medium |
*-cache-size: |
| root_age | 9 | day | >90 Disables. Age limit for root data like root DNSSEC key. Scripts will copy from tmps to flash ROM with this limit to save write life. |
- |
| ttl_min | 120 | second | Minimum TTL in cache to avoid abused low TTL for snoop-vertising and non-standard load balancing. Typical to configure maybe 0~300 but 1800 is the maximum accepted. | cache-min-ttl: |
| unbound_control | 0 | level | Enables unbound-control application access ports.0: None else add your own in unbound_ext.conf1: Unencrypted Local Host Access2: SSL Local Host Access w/ auto unbound-control-setup3: SSL Network Access w/ auto unbound-control-setup4: SSL Network Access; static key/pem files must already exist |
unbound-control: ... (clause) |
| validator | 0 | boolean | Enable DNSSEC validator module. | module: validator |
| validator_ntp | 1 | boolean | Disable DNSSEC time checks at boot and restart when NTP locks in time. DNSSEC requires time validation and this breaks the chicken and egg. | - |
| verbosity | 1 | level | Logging inensity. | verbosity: |
config zone
Confingure any mix of Unbound forward-zone:, stub-zone:, or auth-zone: clauses. These sections are more compact than Unbound and will unroll into Unbound's configuration syntax.
| UCI | Default | Units | Description | Unbound |
|---|---|---|---|---|
| dns_assist | none | program | Check against local host forwarding by requiring a target program to exist and be enabled else do not permit forwarding 127.0.0.0/8 or ::1. Includes bind, dnsmasq, http-proxy-dns, ipset-dns, and nsd. |
forward-addr: |
| enabled | 0 | boolean | turn zone on or off without deleting it | - |
| fallback | 1 | boolean | Allow this zone to fall through to other zones or recursion. | forward-first: |
| port | 53 | port | Target server's target port for plain DNS operations. | (auto 192.0.2.53 #53) |
| resolv_conf | 0 | boolean | Use resolv.conf servers as it was filled by the DHCP client. For example your ISP services (mail.example.net) or co-located services (streamed-movies.example.com). | - |
| server | (empty) | address domain | Required list to target servers. Full form is accepted but not required 192.0.2.53#[email protected]. Stub zones must use an IP address. |
master: forward-host: forward-addr: stub-addr: |
| tls_index | (empty) | domain | TLS certifiicated signature index. If ommitted, Unbound encrypts but does not validate. | (auto 192.0.2.53 #853 @dns.example.net) |
| tls_port | 853 | port | Target server's target port for TLS DNS operations. | (auto 192.0.2.53 #853) |
| tls_upstream | 0 | boolean | Use TLS to contact the zone server. | forward-tls-upstream: |
| url_dir | (empty) | string | Directory only path by http or https to get authoritative zone files. | url: |
| zone_name | (empty) | domain | Required list to select domains subject to this zone section. You can forward the root . zone or just your organizaiton example.com. |
name: |
| zone_type | (empty) | state | Required option to create an Unbound zone clause.auth_zone: prefetch whole zones from authoritative serverforward_zone: forward queries by domain name (like dnsmasq)stub_zone: direct authoritative lookup by domain name |
auth-zone: forward-zone: stub-zone: ... (clauses) |
--- METRICS ---
- Files Extracted: 39
- Estimated Token Budget: ~94509 tokens
- Canonical Reference: https://codewiki.google/github.com/openwrt/packages