Find a file
Daniel Lavrushin c48fa3eef6
issue-429-432 (#438)
* Enhance nftables rules to include connection tracking direction

- Added rules to track the direction of original and reply packets in the nftables configuration for better handling of TCP traffic on port 443.
- Updated existing rules to incorporate connection tracking direction, ensuring that original and reply packets are processed correctly.
- Adjusted the conditions for packet acceptance and queuing to improve traffic management and performance.
A TCP port filter that covers client source ports makes b4 skip outgoing TCP
Fixes #429
Domain-only sets leave the first segment of Chrome's split ClientHello untouched
Fixes #430
A TCP Fast Open SYN that carries a ClientHello is fragmented like a data segment
Fixes #431
b4's own fakes and fragments count toward the per-connection queue window
Fixes #432

* issue-427-428 (#435)

* feat(config): add TCP port overlap detection with ephemeral ranges

* feat(direction): implement queue direction handling and add tests

* Enhance packet direction handling and flow management in TUN

- Introduced a new `Direction` type in the `engine` package to represent packet directions.
- Updated `firstNLimiter` to return packet direction alongside admission status.
- Enhanced flow counting logic to track the direction of TCP flows (original vs reply).
- Implemented direction-based packet forwarding in the `Engine` to ensure correct routing.
- Added tests to verify the correct handling of packet directions and flow records.
- Refactored existing tests to utilize the new direction handling logic.
- Introduced new utility functions for determining TCP direction based on conntrack rules.

* fix(queue): queue router traffic once, clamp MSS before the queue, give b4's own packets their own window

- Router-local packets were queued twice, at OUTPUT and again at POSTROUTING
  (regression since 1.26.1): b4 now jumps from OUTPUT only when device
  filtering moves the B4 jump to FORWARD.
- nftables: the MSS clamp rules ran after the queue rules, so queued SYN-ACKs
  and router SYNs were never clamped (since 1.39.1); they now lead prerouting,
  output and forward, as on iptables.
- Packets b4 sends into a connection carry a mark bit of their own (0x2000000).
  The OUTPUT CONNMARK save no longer tags those connections with the queue
  mark (regression since 1.71.1): their SYN-ACKs and RSTs reach b4 again, so
  RST protection, RST escalation and TTL learning work on them; their server
  data stays unqueued unless a set uses an incoming mode.
- #432: connections b4 has sent into get the window again on top (2N TCP, 2M
  UDP), so its own fakes and fragments no longer use up the client's packets.
  Diagnostics and the installer compare a flowtable guard with 2*max(N,M).
- TCP to private destinations is accepted unprocessed again (regression since
  1.22.0): inbound connections to port-forwarded LAN servers broke when a set
  matched.
- nftables keeps counting the reply window in original packets; the port
  classifier warning gets its own dashboard event.

* fix(quic): a QUIC drop holds for the whole connection, not only the queue window (#428)

When a set drops or rejects a QUIC connection (udp.mode drop or reject, or a
block set), b4 now keeps it dropped past the first datagrams the queue rules
hand it. Before, a connection whose first datagram had been let through (a
ClientHello split over two Initials, as Chrome sends it, decided on the
second) used up its queue window on the dropped datagrams and the rest
reached the server, so a blocked site could open over QUIC.

- nftables: the flow goes into a timed set (b4_udp_drop4/6, 120 s) dropped
  in b4_chain ahead of the UDP window rule.
- iptables, TUN with xt_connbytes, and alongside the nftables set: b4
  deletes the flow's conntrack entry through ctnetlink, so its later
  datagrams start a fresh entry inside the window and reach b4 again. Every
  delete names a complete tuple and carries a mark guard that turns a
  tuple-less request into a no-op instead of a table flush; a self-test
  decides once whether conntrack netlink is usable.
- TUN without xt_connbytes: the engine's own packet count lets a dropped
  connection through to b4 past its limit.
- System Info shows whether drops hold (nf_conntrack_netlink,
  kmod-nf-conntrack-netlink on OpenWrt, which the installer recommends).

* fix(queue): drop the wider window, hold only real QUIC, keep held flows decidable, docs

- Revert the #432 window extension (2N/2M on connections b4 sent into) and
  the TUN doubling for tracked re-sends: letting the strategies reach client
  data past about 10 KB put the default pastseq fakes inside the stream and
  broke TLS uploads through a default-strategy set (measured 21-26 of 80),
  and the combo delay hit the first request after the handshake. #432 stays
  open until fakes cannot land inside the stream.
- The return for connections b4 sent into matches bit 0x2000000 with the
  queue mark clear, so foreign whole-value marks that carry both bits are not
  taken for b4's.
- A QUIC drop holds a flow only when the datagram is QUIC or belongs to a
  QUIC flow b4 named; other UDP to a learned address is dropped as before.
- nftables: the drop set is written from a background writer over one
  netlink socket, waiting behind another nf_tables transaction is no longer
  a failure, and a settings change empties the drop sets (with conntrack
  netlink) so held connections are decided again.
- RST protection judges the first connection to an address b4 has not
  learned yet by the SYN-ACK it saw before the connection matched a set.
- A queue mark containing bit 0x2000000 is warned about.
- The iptables monitor checks for a TCP queue rule on one listing line;
  System Info lists nf_conntrack_netlink only for iptables; make test-netns
  runs the conntrack netlink tests after the tables tests.
- Docs EN/RU, AI topics and changelog for the queue direction, the marks,
  router traffic, the MSS clamp order, QUIC drops and private destinations.

* fix(queue): direction match on duplication rules, private guard only without an address match, refuse a new queue mark with bit 25

From the Copilot review of #438.

- Duplication rules carry the ORIGINAL direction match wherever the
  kernel has it: B4 (-m conntrack --ctdir ORIGINAL), b4_chain (ct
  direction original) and TUN's duplication steer rules. Without it a
  wide port filter let a reply from a duplication address, such as a
  local service answering a client in that range, be queued at
  POSTROUTING or steered into the TUN and duplicated as client
  traffic, since the hook and the TUN take such packets as original.
  The TUN tests that kept these rules port-only now require the match.
- The private-destination guard in handleTCPPacket applies only when
  no set's IP targets cover the address on that port. A set that lists
  10.0.0.0/8 keeps its strategy, as before this branch; SNI-matched
  and port-only sets still leave port-forwarded LAN servers alone.
- Saving a queue mark with bit 0x2000000 as a new value is refused
  (queue.mark, mark_conflict). A configuration that already holds one
  keeps loading and saving with the warning, so an upgrade does not
  stop the service.
- Docs (EN, RU): capture.md and marks.md drop the duplication
  exemption from the direction text, the zapret note follows (its
  untracked packets are no longer queued toward duplication
  addresses), marks.md lists the refusal, matching.md the exception
  for listed private addresses.

* fix(config): refuse a mark with bit 25 only where NFQUEUE mode newly takes it

From Copilot's review of 50e71425: the save-time refusal also turned
down a TUN configuration's mark such as 0x2008000, although TUN adds
0x10000000 to its packets and never 0x2000000, and loading such a
configuration from disk only warns in NFQUEUE mode. The refusal now
applies when a save leaves NFQUEUE mode with a mark that has bit
0x2000000 and the running configuration did not already run NFQUEUE
mode with that mark, so leaving TUN mode with one is still refused.
marks.md (EN, RU) says NFQUEUE mode.

* docs: what an ASUS connection-mark rewrite does to b4's replies

Measured on an ASUS RT-BE88U with AiProtection on: the firmware writes
its own value into the connection mark of device connections from their
start, and the values of all 24 test connections had bit 15, the
default queue mark. The return for b4's own connections in
B4_PREROUTING then skipped all 235 replies of 6 HTTPS connections and
all 6 answers of DNS queries to an outside resolver. marks.md and the
three AI topics said the server's packets are queued as on any other
connection in that case, which holds only for values without the queue
bit, such as the 0x10C3D seen with QoS on. They now say the firmware
can remove bit 0x2000000, and that a value with every queue-mark bit
hides the connection's replies, the SYN-ACK and resets included.

marks.md (EN, RU) also says that b4 refuses a queue mark with bit
0x2000000 when a save would put it on NFQUEUE mode for the first time,
instead of accepting it with a warning.

* test(ctnl): refuse to flush conntrack when the namespace check cannot prove isolation

From Copilot's review of 35cea3d9: with B4_NETNS_TEST=1 and root, a
failed lookup of /proc/self/ns/net or /proc/1/ns/net skipped the
isolation check, and the tuple-less delete test could then flush the
conntrack table of the namespace it ran in, the host's included.

Failing closed on any failed lookup would refuse make test-netns
itself: inside unshare -Urnm, /proc/1/ns/net answers EACCES. When a
lookup fails, the tests now refuse to run if any interface other than
loopback is up. A fresh namespace passes: the kernel's fallback tunnel
devices (gre0, sit0, tunl0 and the like) are created down. Checked: the
rig passes, and the same rig with a dummy interface up is refused.

* docs(capture): the drop set holds by addresses and ports

From Copilot's review of 0dcc9308: on nftables, a new QUIC connection
that a device opens from the same UDP port to the same server address
and port within the 2 minutes is dropped by the same set element,
whatever name it carries. It takes a domain-only set and a client that
reuses one UDP socket for several connections. Exempting Initials from
the drop set and deleting the element when b4 accepts a replacement
would change the kernel rules and weaken the hold on routers without
nf_conntrack_netlink, so the code stays and internals/capture.md (EN,
RU) states the limit.

* fix(nfq): Match any IP address does not count as listing a private address

From the final review of #438: since 50e71425 the private-destination
guard skipped TCP to a private address whenever any address target of
a set matched it. A set with Match any IP address (0.0.0.0/0, ::/0)
and no source devices matches every private address, so a ClientHello
from the internet to a port-forwarded LAN server got that set's
strategy again, and a name match by another set could learn the LAN
server's address.

The guard now lets TCP to a private address through to processing only
when the matched set's most specific network holding it lies inside
the private ranges (SuffixSet.MatchedIPNet, utils.IsPrivateNet): a set
that lists 10.0.0.0/8 or 192.168.1.50 keeps its strategy, the catch-all
does not.

The test cases cover the catch-all, a private range next to the
catch-all, and assert that an untouched ClientHello is neither logged
nor read, so the "port the set leaves out" case now fails when the
guard moves above the port check (checked with go test -overlay on
both mutations).

Docs (EN, RU): matching.md states the exception and what b4 learns,
dscp.md and routing.md name the private-address exception for learned
destinations, and the dscp.enabled topic follows. The port-forward
changelog bullet drops its last sentence, a caveat on a FIXED line.

* fix(nfq): an overlapping private target on another port no longer hides the set that takes the port

From Copilot's review of dde38ff0: the private-destination guard asked
only about the set the address match picked, after the port check had
cleared it. With set A listing 10.1.2.3/32 on port 8443 and set B
listing 10.0.0.0/8 and the ClientHello's domain on 443, A won the
address match, the port check cleared it, and the guard accepted the
packet before the name match could pick B. Without the guard (main) B
handled it.

SuffixSet.ListsPrivateTCPTarget answers the guard's question directly:
does any set that applies to the device (source devices, IP family)
list the destination through a private network and take the port. If
so, b4 handles the connection as usual; Match any IP address still
lists no private address. It replaces MatchedIPNet.

Tests: the matcher case table (overlap, port, catch-all, source device,
IP family, IPv6) and the handler overlap case; the overlap and
catch-all cases both fail with the guard reverted to "no address
match" (go test -overlay). matching.md (EN, RU) says a set that
applies to the device lists the address, and that b4, not
necessarily that set, then handles the connection as usual.
2026-10-17 18:57:28 +02:00
.github core settings rework (#396) 2026-10-04 17:15:25 +02:00
.vscode chore: update TypeScript version and replace ESLint with Oxlint (#405) 2026-10-09 19:05:04 +02:00
docs issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
hub Add ECH support and DNS nameserver configuration (#420) 2026-10-13 10:03:36 +02:00
installer issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
shared/design feat(routing): per-set egress gateway (finishes #401) (#439) 2026-10-16 11:09:43 +02:00
src issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
toolkit Refactor TUN engine to improve packet handling and reply capture logic (#328) 2026-08-29 11:14:31 +02:00
.dockerignore 1-82-0-fixes (#355) 2026-09-16 23:46:35 +02:00
.env.example 1-82-0-fixes (#355) 2026-09-16 23:46:35 +02:00
.gitignore chore: update TypeScript version and replace ESLint with Oxlint (#405) 2026-10-09 19:05:04 +02:00
.mcp.json.template feat(routing): add Egress IP support for traffic source rewriting (#315) 2026-08-22 18:05:34 +02:00
.worktreeinclude feat: add .worktreeinclude to track essential project files (#383) 2026-09-30 16:35:13 +02:00
changelog.md issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
changelog_ru.md issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
docker-compose.yml feat: add Docker support with workflows, Dockerfile, and docker-compose 2026-02-10 20:08:07 +01:00
Dockerfile feat: add routing diagnostics to system info (#400) 2026-10-04 21:05:29 +02:00
install.sh issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
LICENSE Initial commit 2025-08-07 12:15:59 +02:00
Makefile issue-429-432 (#438) 2026-10-17 18:57:28 +02:00
package.json chore: update TypeScript version and replace ESLint with Oxlint (#405) 2026-10-09 19:05:04 +02:00
pnpm-lock.yaml chore: update TypeScript version and replace ESLint with Oxlint (#405) 2026-10-09 19:05:04 +02:00
pnpm-workspace.yaml 1-82-0-fixes (#355) 2026-09-16 23:46:35 +02:00
readme.md Добавлены новые разделы документации на русском языке: (#425) 2026-10-15 15:38:06 +02:00

B4

GitHub Release GitHub Downloads Docker Pulls

[Documentation] [Документация (RU)] [Telegram]

B4 (Bye Bye Big Bro) is a Linux service that bypasses DPI-based blocking. It runs on routers, servers and desktops and rewrites packets so DPI equipment cannot classify the connection, while the destination server still gets valid data. Installed on a router, it covers every device on the network with nothing configured on the clients.

Configuration is built from sets. A set is a list of targets (domains, IPs/CIDRs, geosite/geoip categories, ports, LAN devices) plus the bypass techniques, DNS handling and routing applied to them. Sets are edited in the built-in web UI on port 7000 and applied to the running service without a restart.

If you don't know which technique your provider needs, Discovery tests strategies against your own connection and saves the working one as a set. The Watchdog re-runs it when a site breaks.

image

Features

  • Ten fragmentation strategies: combo, hybrid, tcp, ip, tls, oob, disorder, extsplit, firstbyte, none, plus a random strategy pool
  • Fake packet injection: decoy ClientHellos, TCP desync bursts, window manipulation, fake SYN, SACK stripping, ClientHello mutation
  • RST protection - detects forged resets by TTL/flag/option fingerprint and drops them
  • DNS: per-set redirect to a plain resolver or DoH, hosts-style pins, fragmented queries, DNS-over-TCP interception, healing of answers pointing at dead addresses
  • Routing: send a set's traffic out a chosen interface (VPN, WireGuard, second WAN), through an upstream SOCKS5 proxy, or block it
  • Block mode for network-wide ad and tracker blocking
  • Telegram: built-in MTProto proxy, plus a transparent Telegram-over-WebSocket bridge for stock clients
  • Discovery finds a working strategy, Watchdog re-heals broken domains, DPI Detector reports what the provider is doing
  • Escalation chains: when a set stops working for a host, that host moves to a heavier backup set
  • Per-device rules by MAC
  • Works on both iptables and nftables, picking the backend automatically, and handles IPv4 and IPv6
  • Web UI in English and Russian

Requirements

  • Linux, kernel 3.13+, root access
  • NFQUEUE support in the kernel, or TUN mode if it has none
  • iptables or nft, plus ip. ipset is needed for routing features on iptables-only systems
  • ~64 MB free RAM, ~30 MB disk

Important

On OpenWrt and most router firmware, netfilter flow offloading puts established connections on a fast path that skips the hooks B4 uses. If bypass appears to do nothing, run install.sh --sysinfo.

Installation

Run as root (sudo -i, or pipe into sudo sh).

curl -fsSL https://raw.githubusercontent.com/DanielLavrushin/b4/main/install.sh | sudo sh
wget -qO- https://raw.githubusercontent.com/DanielLavrushin/b4/main/install.sh | sudo sh

Then open http://<device-ip>:7000.

Config /etc/b4/b4.json, or /opt/etc/b4/b4.json on Entware and OpenWrt-with-extroot
Logs /var/log/b4/errors.log
Diagnostics install.sh --sysinfo

Installer options

./install.sh                         # interactive wizard
./install.sh v1.77.0                 # specific version
./install.sh --quiet                 # non-interactive, all defaults
./install.sh --arch=mipsle_softfloat # force architecture
./install.sh --platform=openwrt      # force platform
./install.sh --sysinfo               # system diagnostics
./install.sh --help                  # full usage

Warning

--remove --quiet deletes the config directory and geodata without asking. A run without a terminal on standard input behaves as --quiet, except --remove, which refuses unless --quiet is given explicitly.

First run

  1. Open the web UI, go to Discovery, type a blocked site and press Start. Takes 1-10 minutes.
  2. On a successful result click Use this configuration, then Create a new set.
  3. Open the site. On the Traffic page, connections to that domain now show the set name.

Docs: Quickstart

Supported platforms

Platform Service control
Generic Linux (systemd) systemctl start|stop|restart b4
Generic Linux (OpenRC / SysV) rc-service b4 start, /etc/init.d/b4 start|stop|restart|status
OpenWrt 19.07+ /etc/init.d/b4 start|stop|restart|enable
Asus Merlin, Keenetic /opt/etc/init.d/S99b4 start|stop|restart (needs Entware)
MikroTik RouterOS 7.21.1+ via the Docker container
Docker Linux hosts only, see below

Architectures: amd64, 386, arm64, armv5, armv6, armv7, mips, mipsle, mips_softfloat, mipsle_softfloat, mips64, mips64le, loong64, ppc64, ppc64le, riscv64, s390x.

Docs: Linux · OpenWrt · Merlin · Keenetic · MikroTik · Docker

Docker

docker run -d --name b4 --network host \
  --cap-add NET_ADMIN --cap-add NET_RAW --cap-add SYS_MODULE \
  -v /etc/b4:/etc/b4 \
  lavrushin/b4:latest
services:
  b4:
    image: lavrushin/b4:latest
    container_name: b4
    network_mode: host
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - SYS_MODULE
    volumes:
      - ./config:/etc/b4
    restart: unless-stopped

Linux hosts only, and --network host is mandatory. The config is auto-discovered at /etc/b4/b4.json - do not override it with --config or it gets written inside the container and lost on recreation. Self-update and restart from the web UI do not work in Docker; pull a new image instead.

Images: lavrushin/b4 and ghcr.io/daniellavrushin/b4.

Sets

A set is a list of targets plus what to do with traffic matching them. A set can target any combination of domains, IPs and CIDRs, GeoSite and GeoIP categories, destination ports, LAN devices by MAC, and TLS or IP version.

Sets are evaluated top-down and the first match wins, so specific sets go above general ones. Two sets listing the same domain is a silent conflict - only the first applies.

Docs: Sets · Targets

GeoSite / GeoIP

Rather than listing domains by hand, a set can target whole categories - youtube, category-ads-all, a country's address ranges - from the v2ray/xray geosite and geoip databases. This is how most people cover a service and everything it talks to.

Download the databases from Settings → Geodat Settings (Loyalsoldier, RUNET Freedom, b4geoip, or your own URL), or let the installer fetch them during setup, then add categories under Sets → Targets. They can be refreshed on a schedule.

Docs: Geo data

Bypass techniques

Strategy
combo (default) multi-point split, shuffled segments, paced
hybrid picks combo, disorder, extsplit or firstbyte per ClientHello
tcp plain TCP segmentation
ip real IP-layer fragments
tls split relative to the TLS record header
oob two halves with a one-byte URG packet between them
disorder out-of-order delivery
extsplit cuts at the extension boundary before server_name
firstbyte first byte, wait, then the rest
none no fragmentation, for faking- or desync-only sets

On top of the split: fake ClientHello injection, TCP desync, window manipulation, fake SYN, TCP-MD5, SACK stripping, ClientHello mutation, sequence overlap, packet duplication and MSS clamping.

Matched UDP and QUIC is either fragmented and faked like TCP, dropped, or rejected with an ICMP unreachable so the browser falls back to TCP straight away. QUIC Initial packets are decrypted to read the SNI. STUN and Discord voice packets are left alone by default.

Docs: Splitting · Faking · RST Protection · UDP

DNS

B4 intercepts plain DNS on port 53 and answers matched domains itself. Per set: redirect to a plain resolver or DoH endpoint, hosts-style pins, fragmented queries, and HealDNS to strip unreachable addresses out of answers.

A device running its own DoH/DoT (Chrome Secure DNS, Android Private DNS) bypasses all of it - turn that off on the client or block it.

Docs: DNS

Routing and blocking

Besides mangling packets, a set can pick a routing mode:

Mode
Network interface route out a chosen interface (VPN, WireGuard, second WAN)
Upstream SOCKS5 proxy hand matched traffic to an external proxy via TPROXY
Block (blackhole) DNS NXDOMAIN, TCP RST, ICMP unreachable, or silent drop
Telegram over WebSocket carry Telegram traffic over B4's WebSocket bridge

Destination IPs are learned from DNS answers and live traffic, so CDNs that rotate DNS keep working. Block mode with a geosite ads category gives network-wide ad blocking.

The two proxy modes need TPROXY support in the kernel.

Docs: Routing · Blocking

Telegram

Two independent features, under Settings → Telegram.

MTProto proxy - a fake-TLS proxy Telegram clients connect to directly, with named revocable secrets and a tg://proxy share link. Disabled by default, port 3128.

Telegram over WebSocket - a routing mode you pick on a set, working as a transparent bridge. Clients keep using stock Telegram with no proxy configured, and B4 carries their traffic over WebSocket, Cloudflare-proxied domains, or your own Cloudflare Worker when direct TCP is blocked. It runs on its own; the proxy above does not need to be enabled.

Docs: MTProto / Telegram

Discovery, Watchdog, DPI Detector

Discovery tests bypass strategies against your real network on an isolated queue, tunes the parameters of whatever works, and checks for DNS poisoning and IP-level blocks along the way, asking other regions' DNS answers for an address the block does not cover. Results apply as sets without a restart, and the set is previewed before it is created.

Watchdog fetches the domains you list periodically. After enough consecutive failures it re-runs Discovery for that domain, verifies the result, and rolls back if it does not hold. Disabled by default.

DPI Detector runs read-only probes and reports what the provider is doing: DNS integrity and availability, domain reachability, where connections get throttled, which SNI values pass, and Telegram speed.

Web interface and REST API

The web UI and the REST API share one port, 7000 by default.

Page
Dashboard live metrics in reorderable panels
Sets create, edit, compare, reorder and import/export sets
Discovery run strategy search and apply results
Watchdog monitored domain health, force a check
DPI Detector run diagnostics, with history
Traffic live connections with set/domain/TLS/device/ASN, click-to-add into a set
Logs live log stream, filters, downloadable trace bundle
Settings Core, System, Geodat, Discovery, Telegram, Integrations, Payloads

Most changes apply on save. Some settings on the Core and System tabs take effect only after a restart, and the UI marks them.

Caution

The web UI and REST API are unauthenticated until you set both a username and a password under Settings → System → Web Server. Do not expose port 7000 to the internet without them; the Expose to internet switch on that screen refuses to open the port until both are set, and HTTPS keeps them off the wire in plain text.

HTTPS is enabled by pointing the same screen at a certificate and key. The installer detects router certificates on OpenWrt and Asus Merlin and offers to turn it on.

The API reference is on the docs site: https://docs.b4core.app/swagger

Docs: Security · Dashboard · Connections (RU) · Logs (RU)

SOCKS5 proxy

B4 also ships a SOCKS5 server for applications that support it. Enable it under Settings → Core → SOCKS5; it listens on port 1080 by default and routes through the bypass engine.

curl --socks5 127.0.0.1:1080 https://example.com

Leaving the username and password empty means no authentication, and it binds all interfaces by default. SOCKS5 changes apply on save, without a restart.

Import from zapret or byedpi

Paste an existing nfqws or ciadpi command line into Sets → Import and B4 translates it into equivalent sets, with a per-option report of what was mapped exactly, approximated or lost. Only zapret and byedpi are supported.

Docs: Import from another tool

Configuration file

Everything lives in one JSON file, created on first run and migrated automatically on upgrade. Back it up from Settings → System → Backup.

Important

B4 does not watch the config file. Editing it by hand while the service runs has no effect and gets overwritten. Edit while stopped, or use the web UI.

Docs: Configuration file (RU) · Core settings · System settings

TUN mode

For kernels without NFQUEUE support, switch the engine mode to TUN interface under Settings → Core → Packet Engine. B4 creates its own TUN device and steers traffic into it with policy routing, running the same packet engine.

Important

TUN mode is a fallback, not an equal alternative. Roughly 60% of the feature set works there: B4 installs none of its usual firewall rules, so anything built on them is unavailable. Discovery and the Watchdog's auto-healing do not run, and IPv6 is not forwarded. Use NFQUEUE where the kernel supports it.

Command line

Domains, sets and geosite categories are config-file and web UI only; there are no flags for them.

b4 --help                    # print help
b4 --version                 # version, commit, build date
b4 --config /path/b4.json    # use a specific config file
b4 --verbose debug           # debug | trace | info | silent
b4 --web-port 8080           # override the web UI port (0 disables it)
b4 --clear-tables            # remove b4's firewall rules and exit

Also accepted: --queue-num, --threads, --mark, --ipv4, --ipv6, --tables-monitor-interval, --log-dir, --syslog, -i/--instaflush.

Docs: CLI parameters (RU)

Updating and uninstalling

# to the latest version
curl -fsSL https://raw.githubusercontent.com/DanielLavrushin/b4/main/install.sh | sudo sh -s -- --update

# to a specific version
curl -fsSL https://raw.githubusercontent.com/DanielLavrushin/b4/main/install.sh | sudo sh -s -- --update v1.43.0

# uninstall
curl -fsSL https://raw.githubusercontent.com/DanielLavrushin/b4/main/install.sh | sudo sh -s -- --remove

Updating from the web UI also works, except in Docker.

Troubleshooting

install.sh --sysinfo reports the kernel, platform, service status, firewall backend, NFQUEUE and conntrack probes, flow-offload state and missing tools. The same data is in the web UI under Settings → System → Service → System Info.

Common causes when bypass appears to do nothing:

  • Flow offloading (OpenWrt and most router firmware). Established flows skip B4's hooks. Disable it, or guard it with ct original packets ge 40 flow offload @ft in /usr/share/firewall4/templates/ruleset.uc followed by fw4 restart.
  • Client-side DoH/DoT. A device resolving names itself bypasses every DNS feature.
  • Missing kernel modules. B4 names the module and package it needs. If NFQUEUE cannot be made to work at all, fall back to TUN mode and its limitations.
  • Set order. First match wins. The Traffic page shows which set actually matched.

For bug reports: on the Logs page click Start trace, reproduce the problem, then stop and download. The bundle has the build version, system diagnostics and a redacted summary of the enabled sets.

Building from source

Requires Go 1.25.14, Node 22 and pnpm 10.

git clone https://github.com/daniellavrushin/b4.git
cd b4

# Build the web UI first - it gets embedded into the binary
make build-ui

# Build for the current platform (VERSION is required)
make build VERSION=1.77.0

make build-all      # all release architectures into out/assets/
make linux-arm64    # a single architecture
make help           # all targets

install.sh is generated. Edit the sources under installer/ and run make build-installer.

Documentation

https://docs.b4core.app/ · Русская версия

A few English pages (DPI Detector, Connections, Logs and the Advanced section) are still being translated. Use the language switcher for the Russian originals.

Contributing

Contributions are accepted through GitHub pull requests.

Community projects

Maintained outside this repository:

  • luci-app-b4 by BugOldfag - adds b4 management to the LuCI interface on OpenWrt
  • b4-mikrotik by wiktorbgu - container image packaged for MikroTik RouterOS, with an OpenRC-supervised service, startup selection of the firewall backend and a fix for the routing rule priorities RouterOS 7.22 passes into containers

Credits

Based on research from:

  • youtubeUnblock - C-based DPI bypass
  • GoodbyeDPI - Windows DPI circumvention
  • zapret - Advanced DPI bypass techniques
  • byedpi - proxy-based DPI bypass
  • dpi-detector - DPI/TSPU detection techniques and test target lists (the DPI Detector feature is based on this project)
  • Ladon - reactive Anti-DPI detection engine; its failure-classification approach (telling server-side rejection apart from DPI interference) informed the Watchdog feature
  • tg-ws-proxy - Telegram MTProto-over-WebSocket bridging
  • ori - GoodCheck ideas in Discovery

License

This project is provided for educational purposes. Users are responsible for compliance with applicable laws and regulations. The authors are not responsible for misuse of this software.