Skip to content
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8204839
Refactor NFQueue verdict handling and introduce TUN device management
DanielLavrushin Jun 15, 2026
2107a56
feat: enhance TUN configuration to support multiple routes
DanielLavrushin Jun 15, 2026
8dd6dd2
feat: enhance route collection logic for TUN device to support select…
DanielLavrushin Jun 15, 2026
03390b5
feat: implement TUN route management and configuration updates
DanielLavrushin Jun 15, 2026
aa60ea9
feat: add TUN mode configuration and settings to Feature component
DanielLavrushin Jun 15, 2026
7062887
feat: enhance TUN settings with route management and localization upd…
DanielLavrushin Jun 15, 2026
4e59594
feat: enhance TUN engine with error logging and forwarding metrics
DanielLavrushin Jun 15, 2026
2ec85e9
feat: clear masquerade tables on TUN engine start failure
DanielLavrushin Jun 15, 2026
e372bb9
feat: ensure route teardown on TUN engine start failure
DanielLavrushin Jun 15, 2026
06e5a44
feat: enhance IPv6 socket binding error handling and disable on failure
DanielLavrushin Jun 15, 2026
82f8090
feat: remove cleanup of DNS pending routes during pool cleanup
DanielLavrushin Jun 15, 2026
889d8c2
feat: add skip setup option for masquerade table management in TUN en…
DanielLavrushin Jun 15, 2026
8407d8d
feat: improve default route handling in setup by using IPv4 only
DanielLavrushin Jun 15, 2026
e9b9f48
feat: add validation for TUN device name length in openTUN function
DanielLavrushin Jun 15, 2026
b5d6394
feat: ensure TUN device is deleted on sender initialization failure
DanielLavrushin Jun 15, 2026
d1afcc8
feat: ensure safe stop of TUN engine using sync.Once for idempotency
DanielLavrushin Jun 15, 2026
5130a8d
feat: add interface validation in TUN engine to prevent conflicts wit…
DanielLavrushin Jun 15, 2026
c1e3f73
fix: handle error when checking existing route table in setupBypassTa…
DanielLavrushin Jun 15, 2026
3aed60a
feat: update DNS packet processing to use client MAC address for SNI …
DanielLavrushin Jun 15, 2026
80c300d
fix: reset TUNRouteFunc to nil on TUN engine start failure
DanielLavrushin Jun 15, 2026
bd9ea1a
fix: remove assignment of TUNRouteFunc to nil in Stop method
DanielLavrushin Jun 15, 2026
68d2e56
feat: add TUN engine mode migration and validation tests
DanielLavrushin Jun 15, 2026
aa9f5dc
refactor: extract delFwmarkRule method for better code reuse in setup…
DanielLavrushin Jun 15, 2026
39577fa
test: add unit tests for extractField and extractGateway functions
DanielLavrushin Jun 15, 2026
16b81d9
feat: enhance setupBypassTable to check for existing route tables and…
DanielLavrushin Jun 15, 2026
72639d7
fix: change route command from add to replace in addRoute and addBypa…
DanielLavrushin Jun 15, 2026
68ab96c
fix: add early return in ClearMasqueradeOnly if masquerade is disabled
DanielLavrushin Jun 15, 2026
9795e1d
fix: improve AddRoute method to handle quit signal before adding route
DanielLavrushin Jun 15, 2026
68abd00
fix: add conditional checks before syncing tproxy and routing configu…
DanielLavrushin Jun 15, 2026
7743e45
fix: update ApplyMasqueradeOnly to clear masquerade before applying i…
DanielLavrushin Jun 15, 2026
3e6a4de
fix: replace hardcoded constants with system-defined values for tun d…
DanielLavrushin Jun 15, 2026
89841e7
feat: add TUN-interface packet engine for devices without NFQUEUE
DanielLavrushin Jun 15, 2026
e863d51
fix: enhance default route handling and improve error logging in setu…
DanielLavrushin Jun 16, 2026
d4cae5b
feat: implement password hashing and validation, enhance web server c…
DanielLavrushin Jun 16, 2026
d74ac3c
fix: improve error handling in packet forwarding and enhance logging …
DanielLavrushin Jun 16, 2026
349792f
feat: implement NAT setup and teardown for routeManager
DanielLavrushin Jun 16, 2026
eaa734a
feat: add conntrack sysctl management functions for masquerade setup
DanielLavrushin Jun 16, 2026
9624b87
feat: enhance NAT setup with dynamic MTU configuration and improved e…
DanielLavrushin Jun 16, 2026
987f360
fix: update logging message for NOTRACK installation in NAT setup
DanielLavrushin Jun 16, 2026
660c58b
feat: implement reconcile loop for routeManager to monitor and update…
DanielLavrushin Jun 16, 2026
77b4b55
feat: add bypassMark to TUNConfig and implement connbytes bypass rule…
DanielLavrushin Jun 16, 2026
c7369e1
feat: implement token expiration cleanup and enhance login attempt ma…
DanielLavrushin Jun 16, 2026
570fa59
feat: enhance routeManager to support --skip-tables option for flexib…
DanielLavrushin Jun 16, 2026
44c7b36
refactor: simplify packet handling in readLoop by eliminating unneces…
DanielLavrushin Jun 16, 2026
41bfe80
feat: initialize current map in routeManager and enhance run function…
DanielLavrushin Jun 16, 2026
18d2ab5
refactor: streamline ClearMasquerade function by consolidating comman…
DanielLavrushin Jun 16, 2026
b308c09
fix: stop pool on TUN engine start failure to prevent resource leaks
DanielLavrushin Jun 16, 2026
06861e5
refactor: improve ruleFieldValue function for better readability and …
DanielLavrushin Jun 16, 2026
664307b
fix: handle error when setting non-blocking mode in openTUN function
DanielLavrushin Jun 16, 2026
92293d0
chore: update changelog to reflect password hashing for Web UI login
DanielLavrushin Jun 16, 2026
25ce356
refactor: remove bypassMark from TUNConfig and related logic to simpl…
DanielLavrushin Jun 16, 2026
5a1a6d8
feat: add masquerade mark-bypass rule in iptables and nftables managers
DanielLavrushin Jun 17, 2026
9ec2bf2
fix: resolve DNS redirect issues for encrypted DNS (DoH) in certain s…
DanielLavrushin Jun 17, 2026
0eed903
chore: update changelog for new TUN engine, secure password storage, …
DanielLavrushin Jun 17, 2026
eb8655f
fix: resolve routing set address update issues in certain setups dns …
DanielLavrushin Jun 18, 2026
64cd650
fix: synchronize tproxy and routing configurations in TUN mode
DanielLavrushin Jun 18, 2026
7b5f4ed
feat(tun): introduce TUN packet-processing engine with port-scoped ca…
DanielLavrushin Jun 18, 2026
fc8996d
feat(tun): implement MSS clamp functionality and enhance packet handl…
DanielLavrushin Jun 18, 2026
088cee2
feat(tun): integrate ReinjectMarkBit for enhanced packet marking in T…
DanielLavrushin Jun 18, 2026
cc84686
fix(tun): clear MSS clamp and revert conntrack sysctls on TUN engine …
DanielLavrushin Jun 18, 2026
4a7e34c
feat(tun): add iptables check in setup to ensure necessary binaries f…
DanielLavrushin Jun 18, 2026
42c7a4e
fix(tun): add validation for minimum IP header length in senderFor fu…
DanielLavrushin Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# B4 - Bye Bye Big Bro

## [1.70.0] - 2026-06-xx
Comment thread
DanielLavrushin marked this conversation as resolved.

- ADDED: **New engine for devices without NFQUEUE** - some minimal devices lack the kernel modules b4 normally needs, so it could not run on them. A new mode under Settings → Feature Flags routes traffic through a virtual interface (TUN) instead.
- CHANGED: **Web UI password is now stored securely** - it is kept only as a hash and is no longer shown on the settings page (leave the field blank to keep it, or type a new one to change it). Repeated failed logins are briefly blocked, and a session now expires after a day. Existing setups are migrated automatically.
- FIXED: **A set's encrypted DNS (DoH) redirect failed in some setups** - lookups could time out or come back empty, so pages would not load. This affected gateway and container setups (for example b4 in a container on MikroTik) using NAT masquerade.

## [1.69.1] - 2026-06-14

- FIXED: **Sets manager felt frozen after enabling, reordering, duplicating, or deleting a set** - the screen only changed once the action had been saved and the whole configuration re-fetched a second or two later, so a click looked like nothing had happened, a dragged set snapped back to its old place before jumping to the new one, and a duplicate appeared out of nowhere after a pause.
Expand Down Expand Up @@ -29,7 +35,7 @@
- FIXED: **Leftover "zombie" update processes piling up** - each update attempt left a finished helper process behind; these are now cleaned up.
- ADDED: **Update log** - every Web UI update now writes a step-by-step trace to `update.log` in the log folder (default `/var/log/b4`, reset each attempt), making failed updates much easier to diagnose.
- ADDED: **Localized changelog** - the Web UI now shows the changelog in the selected language.
- CHANGED: **Logging setting is now a folder, not a single file** - Settings → Service now asks for a log *directory* (default `/var/log/b4`) instead of a path to `errors.log`, so all of b4's log files (errors, updates, and any added later) live together and can be moved in one place. Existing configs are migrated automatically (your old folder is kept); leave the field empty to turn file logging off.
- CHANGED: **Logging setting is now a folder, not a single file** - Settings → Service now asks for a log _directory_ (default `/var/log/b4`) instead of a path to `errors.log`, so all of b4's log files (errors, updates, and any added later) live together and can be moved in one place. Existing configs are migrated automatically (your old folder is kept); leave the field empty to turn file logging off.

## [1.67.1] - 2026-06-10

Expand All @@ -50,7 +56,7 @@
## [1.66.0] - 2026-06-07

- ADDED: **Blocking stats on the Dashboard** - when a set uses Block (blackhole) mode and actually blocks something, the Dashboard now shows a "Blackhole" panel with the total number of blocked attempts, the most-blocked domains, and which devices ran into the most blocks. The panel stays hidden until there is something to show, so it never clutters the page when nothing is being blocked. Blocked connections are also tagged with a "block" label on the Traffic page so you can spot them in the live feed.
- ADDED: **Control over the Telegram server-list backup** - to reach Telegram, b4 fetches Telegram's data center list from Telegram's official address, and falls back to a backup copy hosted by the b4 author only if that is blocked (`lavrush.in`). Settings -> MTProto Proxy now has a "DC list fallback mirror" switch to turn it off or point it at your own copy.
- ADDED: **Control over the Telegram server-list backup** - to reach Telegram, b4 fetches Telegram's data center list from Telegram's official address, and falls back to a backup copy hosted by the b4 author only if that is blocked (`lavrush.in`). Settings -> MTProto Proxy now has a "DC list fallback mirror" switch to turn it off or point it at your own copy.
- FIXED: **Set Routing now respects your device choices** - if you used Settings -> Devices to make b4 work for only some devices (or to exclude some), that choice was ignored by a set's Routing tab. Whatever the set did with the traffic (send it to another network interface or an upstream proxy, run it through the Telegram bridge, or block it) happened for every device regardless. Routing now applies only to the devices you picked. You can also give an individual set its own device list in its settings, so a single set can route, bridge, or block traffic for just specific devices without affecting the rest.
- FIXED: **The router itself showed up as a wrongly-named device on the Traffic page** - b4 mistook the router's own address for a regular client and listed it as a separate device (sometimes guessed as a phone brand), filing the router's own connections under it. b4 now recognizes its own interfaces and labels that traffic simply as "Router".
- FIXED: **Discord voice and video calls could drop when blocking UDP** - on a set with UDP set to Drop or Reject, Discord calls could break because b4 did not recognize Discord's call traffic. b4 now lets it through (when "Filter STUN" is on, the default), so calls keep working.
Expand Down Expand Up @@ -725,7 +731,7 @@

## [1.10.1] - 2025-11-03

- IMPROVED: Intermittent connection failures where blocked sites would randomly fail to load in certain browsers (`Safari`, `Firefox`, `Chrome`). Connections *should* now be more stable and reliable across all browsers by optimizing packet fragmentation strategy.
- IMPROVED: Intermittent connection failures where blocked sites would randomly fail to load in certain browsers (`Safari`, `Firefox`, `Chrome`). Connections _should_ now be more stable and reliable across all browsers by optimizing packet fragmentation strategy.

## [1.10.0] - 2025-11-02

Expand Down
8 changes: 7 additions & 1 deletion changelog_ru.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# B4 - Bye Bye Big Bro

## [1.70.0] - 2026-06-xx
Comment thread
DanielLavrushin marked this conversation as resolved.

- ДОБАВЛЕНО: **Новый движок для устройств без NFQUEUE** - на некоторых минимальных устройствах нет модулей ядра, которые обычно нужны b4, поэтому он на них не запускался. Новый режим в «Настройки → Функции» пропускает трафик через виртуальный интерфейс (TUN).
- ИЗМЕНЕНО: **Пароль веб-интерфейса теперь хранится безопасно** - он хранится только в виде хеша и больше не показывается на странице настроек (оставьте поле пустым, чтобы сохранить его, или введите новый для замены). Повторные неудачные попытки входа ненадолго блокируются, а вход теперь перестаёт действовать через сутки. Существующие конфигурации мигрируют автоматически.
- ИСПРАВЛЕНО: **Перенаправление сета на шифрованный DNS (DoH) не работало в некоторых конфигурациях** - запросы могли завершаться тайм-аутом или возвращаться пустыми, из-за чего страницы не открывались. Это затрагивало шлюзы и контейнеры (например, b4 в контейнере на MikroTik) с включённым NAT masquerade.

## [1.69.1] - 2026-06-14

- ИСПРАВЛЕНО: **Менеджер сетов казался зависшим после включения, изменения порядка, дублирования или удаления сета** - экран менялся только после того, как действие сохранялось и вся конфигурация перезапрашивалась через секунду-две, поэтому клик выглядел так, будто ничего не произошло, перетащенный сет отскакивал на старое место перед прыжком на новое, а копия появлялась из ниоткуда после паузы.
Expand Down Expand Up @@ -29,7 +35,7 @@
- ИСПРАВЛЕНО: **Накопление зависших процессов обновления** - после каждой попытки обновления оставался завершённый вспомогательный процесс; теперь они очищаются.
- ДОБАВЛЕНО: **Журнал обновлений** - каждое обновление через веб-интерфейс теперь пишет пошаговую трассировку в `update.log` в папке логов (по умолчанию `/var/log/b4`, очищается при каждой попытке), что значительно упрощает диагностику неудачных обновлений.
- ДОБАВЛЕНО: **Локализованный журнал изменений** - веб-интерфейс теперь показывает журнал изменений на выбранном языке.
- ИЗМЕНЕНО: **Настройка логирования теперь папка, а не отдельный файл** - в «Настройки → Служба» теперь указывается *папка* для логов (по умолчанию `/var/log/b4`) вместо пути к `errors.log`, поэтому все файлы логов b4 (ошибки, обновления и любые добавленные позже) хранятся вместе и переносятся в одном месте. Существующие конфигурации мигрируются автоматически (ваша прежняя папка сохраняется); оставьте поле пустым, чтобы отключить запись логов в файл.
- ИЗМЕНЕНО: **Настройка логирования теперь папка, а не отдельный файл** - в «Настройки → Служба» теперь указывается _папка_ для логов (по умолчанию `/var/log/b4`) вместо пути к `errors.log`, поэтому все файлы логов b4 (ошибки, обновления и любые добавленные позже) хранятся вместе и переносятся в одном месте. Существующие конфигурации мигрируются автоматически (ваша прежняя папка сохраняется); оставьте поле пустым, чтобы отключить запись логов в файл.

## [1.67.1] - 2026-06-10

Expand Down
27 changes: 27 additions & 0 deletions src/config/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ var migrationRegistry = map[int]MigrationFunc{
44: migrateV44to45, // Add per-set DNS-over-HTTPS redirect target
45: migrateV45to46, // Replace logging.error_file with logging.directory
46: migrateV46to47, // Drop the hardcoded legacy MTProto WS endpoint host (empty now falls back to it)
47: migrateV47to48, // Add TUN engine mode and config
}

func migrateV47to48(c *Config, _ map[string]interface{}) error {
log.Tracef("Migration v47->v48: Adding TUN engine mode and config")
c.Queue.Mode = DefaultConfig.Queue.Mode
c.Queue.TUN = DefaultConfig.Queue.TUN
return nil
}

func migrateV46to47(c *Config, _ map[string]interface{}) error {
Expand Down Expand Up @@ -758,9 +766,28 @@ func (c *Config) LoadWithMigration(path string) (bool, error) {

c.System.Geo.SanitizePaths(filepath.Dir(c.ConfigPath))

if c.migratePasswordHash() {
migrated = true
}

return migrated, nil
}

func (c *Config) migratePasswordHash() bool {
p := c.System.WebServer.Password
if p == "" || IsHashedPassword(p) {
return false
}
h, err := HashPassword(p)
if err != nil {
log.Errorf("failed to hash web server password during migration: %v", err)
return false
}
c.System.WebServer.Password = h
log.Infof("Migrated plaintext web server password to bcrypt hash")
return true
}

func (c *Config) applyMigrations(startVersion int, rawJSON map[string]interface{}) error {
for v := startVersion; v < CurrentConfigVersion; v++ {
migrationFunc, exists := migrationRegistry[v]
Expand Down
30 changes: 30 additions & 0 deletions src/config/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package config

import (
"strings"

"golang.org/x/crypto/bcrypt"
)

const webPasswordCost = 12

func HashPassword(plain string) (string, error) {
h, err := bcrypt.GenerateFromPassword([]byte(plain), webPasswordCost)
if err != nil {
return "", err
}
return string(h), nil
}

func CheckPassword(hash, plain string) bool {
if hash == "" {
return false
}
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(plain)) == nil
}

func IsHashedPassword(s string) bool {
return strings.HasPrefix(s, "$2a$") ||
strings.HasPrefix(s, "$2b$") ||
strings.HasPrefix(s, "$2y$")
}
13 changes: 13 additions & 0 deletions src/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type ApiConfig struct {
}

type QueueConfig struct {
Mode string `json:"mode"`
StartNum int `json:"start_num"`
Threads int `json:"threads"`
Mark uint `json:"mark"` // Main injected packets mark
Expand All @@ -81,9 +82,20 @@ type QueueConfig struct {
Interfaces []string `json:"interfaces"`
Devices DevicesConfig `json:"devices"`
MSSClamp MSSClampConfig `json:"mss_clamp"`
TUN TUNConfig `json:"tun"`
IsDiscovery bool `json:"-"`
}

type TUNConfig struct {
DeviceName string `json:"device_name"`
Address string `json:"address"`
AddressV6 string `json:"address_v6"`
OutInterface string `json:"out_interface"`
OutGateway string `json:"out_gateway"`
RouteTable int `json:"route_table"`
Routes []string `json:"routes"`
}

type DevicesConfig struct {
Enabled bool `json:"enabled"`
VendorLookup bool `json:"vendor_lookup"`
Expand Down Expand Up @@ -299,6 +311,7 @@ type WebServerConfig struct {
TLSKey string `json:"tls_key"`
Username string `json:"username"`
Password string `json:"password"`
PasswordSet bool `json:"password_set,omitempty"`
Language string `json:"language"`
IsEnabled bool `json:"-"`
}
Expand Down
10 changes: 10 additions & 0 deletions src/config/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ func (c *Config) Validate() error {
return v.result()
}

if c.Queue.Mode != "" && c.Queue.Mode != "nfqueue" && c.Queue.Mode != "tun" {
v.add("queue.mode", "invalid", "queue mode must be 'nfqueue' or 'tun'", nil)
return v.result()
}

if c.Queue.Mode == "tun" && c.Queue.TUN.OutInterface == "" {
v.add("queue.tun.out_interface", "required", "tun out_interface is required in TUN mode (e.g. eth0, wan0, l2tp-vpn)", nil)
return v.result()
}
Comment thread
DanielLavrushin marked this conversation as resolved.
Comment on lines +253 to +261

if c.Queue.StartNum < 0 || c.Queue.StartNum > 65535 {
v.add("queue.start_num", "out_of_range", "queue-num must be between 0 and 65535", nil)
return v.result()
Expand Down
28 changes: 28 additions & 0 deletions src/config/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,34 @@ func TestValidate_QueueFields(t *testing.T) {
t.Errorf("expected params.mark=0x100, got %v", f.Params["mark"])
}
})

t.Run("invalid queue mode", func(t *testing.T) {
cfg := NewConfig()
cfg.Queue.Mode = "bogus"
ve := mustValidationErr(t, cfg.Validate())
if findField(ve, "queue.mode", "invalid") == nil {
t.Errorf("missing queue.mode invalid; got %+v", ve.Fields)
}
})

t.Run("tun mode requires out_interface", func(t *testing.T) {
cfg := NewConfig()
cfg.Queue.Mode = "tun"
cfg.Queue.TUN.OutInterface = ""
ve := mustValidationErr(t, cfg.Validate())
if findField(ve, "queue.tun.out_interface", "required") == nil {
t.Errorf("missing queue.tun.out_interface required; got %+v", ve.Fields)
}
})

t.Run("valid tun config passes", func(t *testing.T) {
cfg := NewConfig()
cfg.Queue.Mode = "tun"
cfg.Queue.TUN.OutInterface = "eth0"
if err := cfg.Validate(); err != nil {
t.Errorf("valid tun config rejected: %v", err)
}
})
}

func TestValidate_RequiredSetID(t *testing.T) {
Expand Down
12 changes: 10 additions & 2 deletions src/dns/doh.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ func MarkedDoHClient(mark int, timeout time.Duration) *http.Client {
MaxIdleConns: 100,
IdleConnTimeout: 30 * time.Second,
}
d := &net.Dialer{Timeout: timeout}
var control func(string, string, syscall.RawConn) error
if mark != 0 {
d.Control = func(_, _ string, c syscall.RawConn) error {
control = func(_, _ string, c syscall.RawConn) error {
var serr error
if cerr := c.Control(func(fd uintptr) {
serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, mark)
Expand All @@ -40,6 +40,14 @@ func MarkedDoHClient(mark int, timeout time.Duration) *http.Client {
return serr
}
}
d := &net.Dialer{Timeout: timeout, Control: control}
d.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
bd := &net.Dialer{Timeout: timeout, Control: control}
return bd.DialContext(ctx, network, address)
},
}
tr.DialContext = d.DialContext
return &http.Client{Transport: tr, Timeout: timeout}
}
Expand Down
13 changes: 13 additions & 0 deletions src/engine/engine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package engine

type PacketVerdict int

const (
VerdictAccept PacketVerdict = iota
VerdictDrop
)

type Engine interface {
Start() error
Stop()
}
Loading