My home-network design has one purpose: every household device should use policy-based routing without installing a VPN app or configuring a proxy. Domestic traffic goes directly to the internet, while selected destinations use an encrypted VLESS/Reality tunnel to a VPS. A device should only need to join Wi-Fi.
The design worked in a Docker simulation quickly, but reliable operation on an ASUS router running ASUSWRT-Merlin required much more work. The difficult parts were not VLESS credentials or basic routing logic. The router combined a 64-bit kernel with a 32-bit userland, exposed incomplete nftables support, reserved unexpected policy-routing tables, allowed IPv6 to bypass IPv4 rules, and ran boot hooks before storage and WAN connectivity were ready.
This article records the engineering process rather than presenting one universal installation script. Router models, firmware builds, kernel options, interfaces, and ISP behavior differ. Understand each rule and preserve console recovery before applying the pattern to a gateway. The Linux proxy guide explains proxy layers and environment-wide routing in a broader context.
1. Architecture and Predeployment Testing
1.1 Three parts of the system
The system contains three independently testable parts:
- A VPS runs sing-box with a VLESS inbound and Reality, using
www.bing.comas the camouflage target. Nginx accepts port 443 and routes connections by SNI so the tunnel can coexist with other HTTPS services. - A Docker topology contains a router container and a device container. It validates credentials, DNS policy, route selection, and firewall logic without changing the household gateway.
- The production target is an ASUS router running ASUSWRT-Merlin. It intercepts LAN traffic and forwards each connection directly or through the tunnel.
Every logic change must pass the Docker topology before it reaches the router. The simulation provides a known-good reference, but it cannot reproduce proprietary kernel builds, mixed userspace architectures, early boot timing, hardware acceleration, or firmware-owned routing tables. Understanding Docker covers the image, container, network, and volume concepts behind the test topology.
1.2 Test the assumptions the simulator cannot represent
Before deployment, record uname -m, inspect an existing executable with file, inspect shared libraries, read /proc/config.gz or the firmware kernel configuration when available, list loaded netfilter modules, and dump ip rule, every relevant route table, iptables-save, and ip6tables-save. The baseline makes hidden firmware state visible and later gives rollback tests an exact comparison target.
The production checklist must also include a second client device, IPv4 and IPv6 DNS queries, direct and proxied destinations, reboot testing, and a way to reach the router without the modified forwarding path. A successful process status alone cannot prove that LAN packets enter the intended chain.
2. Hardware and Kernel Constraints
2.1 A 64-bit kernel can host a 32-bit userland
The router reported aarch64 through uname -a, so the first deployment used the linux-arm64 sing-box archive. Starting it failed with libdl.so.2: cannot open shared object file, followed by wrong ELF class: ELFCLASS32 during diagnosis.
The kernel was 64-bit ARM, but the firmware’s executables and shared libraries were 32-bit ARM. Broadcom router platforms can retain a 32-bit vendor userland even after the kernel moves to a 64-bit build. uname -m describes the kernel architecture, not necessarily the ABI required by dynamically linked user programs.
Inspecting the router’s existing binaries and libraries showed the real ABI. Replacing the archive with the linux-armv7 build fixed the startup error. Binary selection on embedded systems should therefore use both kernel architecture and userland ELF class.
2.2 A loadable module does not prove a usable subsystem
sing-box offers auto_redirect for a TUN inbound. On a supported Linux system it can install nftables rules, intercept LAN traffic, and configure routing automatically. On this firmware, startup first failed with missing nftables support. The nf_tables.ko module existed and loaded without an obvious error, but the next attempt failed while creating an IPv4 address set with operation not supported.
The kernel configuration did not include the required IPv4 nftables implementation. A generic netlink endpoint was present, so superficial feature detection passed, but the operations required by sing-box were unavailable. The tested sing-box version did not fall back from auto_redirect to iptables.
Repeatedly loading modules cannot recover functionality omitted at kernel compile time. Once the kernel configuration confirmed the missing feature, the deployment switched to the older but available iptables TPROXY path.
3. Manual TPROXY and Policy Routing
3.1 How TPROXY preserves the original destination
TPROXY intercepts a packet without replacing its original destination. A rule in the mangle table’s PREROUTING path redirects selected TCP or UDP traffic to a local transparent socket and assigns a firewall mark. A sing-box tproxy inbound accepts the redirected connection and can still inspect the original destination. A policy-routing rule sends marked packets to a local route rather than forwarding them normally.
The router provided xt_TPROXY, nf_tproxy_ipv4, and nf_tproxy_ipv6, so the manual path did not require nftables or a TUN device. The essential IPv4 pattern was:
iptables -t mangle -N SB_TPROXY
# Intercept DNS so direct and proxied policies use the intended resolver path.
iptables -t mangle -A SB_TPROXY -p udp --dport 53 -j TPROXY \
--on-port 7893 --on-ip 127.0.0.1 --tproxy-mark 0x1/0xffffffff
# Keep router management and LAN/reserved destinations outside the tunnel.
iptables -t mangle -A SB_TPROXY -m addrtype --dst-type LOCAL -j RETURN
iptables -t mangle -A SB_TPROXY -d 10.0.0.0/8 -j RETURN
# Add every other local, RFC 1918, multicast, and reserved range required here.
iptables -t mangle -A SB_TPROXY -p tcp -j TPROXY \
--on-port 7893 --on-ip 127.0.0.1 --tproxy-mark 0x1/0xffffffff
iptables -t mangle -I PREROUTING 1 -i br0 -j SB_TPROXY
ip rule add fwmark 0x1 lookup 100 priority 100
ip route add local 0.0.0.0/0 dev lo table 100
The actual deployment also handles UDP policy, exclusions, duplicate-rule detection, and cleanup. Interface name br0, mark 0x1, table 100, and port 7893 are site-specific choices, not universal constants. The next section explains why table 100 had to be replaced.
3.2 Preserve local traffic and source semantics
Traffic addressed to the router itself must bypass the transparent proxy so SSH and the management interface remain reachable. LAN and reserved destinations also need explicit exclusions. Sending a client-to-client LAN connection through sing-box’s direct outbound would create a new connection from the router and lose the original client source address.
DNS needs a deliberate policy. Intercepting port 53 can keep domain classification and upstream selection consistent, but clients using DNS over HTTPS or DNS over TLS do not generate ordinary port-53 traffic. A transparent-proxy design must document which encrypted-DNS behavior it allows, blocks, or routes.
3.3 Do not guess a free routing-table number
The first implementation used table 100 because no visible ip rule referenced it. ASUSWRT-Merlin had already named and reserved low-numbered tables for VPN clients and dual-WAN functions. ip route show table 100 displayed the symbolic name wan0, revealing the collision.
Choose marks, priorities, ports, and table IDs after inspecting /etc/iproute2/rt_tables, existing rules, and firmware scripts. Deriving the table ID from a deployment-specific port made the choice easier to track. Cleanup code should match the immutable fwmark or normalize numeric output rather than grep for lookup 100, because iproute2 may print a symbolic table name instead of its number.
4. Safe Changes and Complete IPv6 Coverage
4.1 Implement rollback before risky changes
A gateway firewall mistake disconnects every household user and can remove the administrator’s SSH path. Before enabling automatic startup, the deployment used a commit-confirmed workflow:
startsaves the current IPv4 and IPv6 firewall state, policy rules, and affected route tables before changing anything.startlaunches an independent three-minute watchdog.confirmcancels the watchdog only after external tests succeed.revertstops sing-box and restores the saved state immediately.- Until boot persistence is enabled, power cycling returns the router to its firmware-managed baseline.
The rollback was tested by inserting a fake firewall rule, policy rule, and orphan route table, then comparing the restored state byte-for-byte with the saved baseline. The test found a real ordering bug: the cleanup routine deleted an orphan policy rule before recording the table to which it pointed, so the now-unreferenced table survived. Rollback code must capture every dependency before deleting any object.
4.2 IPv4-only interception leaves a silent bypass
The IPv4 setup ran correctly for several days before one laptop reported intermittent ERR_CONNECTION_CLOSED. An nslookup on the failing client showed that it was querying the router’s IPv6 DNS address. The network assigned globally routable IPv6 addresses, and modern clients preferred IPv6 for DNS and application connections. Every packet bypassed the IPv4-only iptables chain.
The fix mirrored the IPv4 rules with ip6tables, added a sing-box tproxy inbound bound to ::1, and configured IPv6 policy routing with the same mark and table number. IPv4 and IPv6 route-table namespaces are separate, so using the same number did not create a collision.
IPv6 LAN exclusions require different logic. IPv4 can exclude RFC 1918 ranges, while household IPv6 addresses may use a globally routed delegated prefix. The firewall script reads the currently routed LAN prefix from the kernel rather than hard-coding a value that the ISP may change after renewal.
Validation used live clients and counters: IPv6 chain counters had to increase, conntrack had to show local interception rather than ordinary WAN NAT, and sing-box logs had to show the same connection entering the correct route. Testing only process health or IPv4 requests would have missed the bypass.
5. Boot Ordering Is Part of the Network Design
5.1 Separate interactive start from unattended boot
The interactive start command always arms a watchdog and waits for a human to run confirm. Calling the same command from a boot hook would start successfully and then roll back three minutes later because nobody was present to confirm it.
A separate boot-start path starts the service, arms the watchdog, performs automatic health checks, and confirms only after prerequisites and connectivity pass. Router-originated traffic does not traverse the LAN PREROUTING rule, so a simple VPS connection cannot prove client interception; it is still useful as a minimum upstream check. Failure leaves the watchdog armed so the router returns to its baseline automatically.
5.2 Wait for storage and the real WAN route
The first reboot showed that services-start ran before the USB storage containing sing-box was mounted. System logs still showed January 1 because time synchronization had not occurred. boot-start therefore waits until the executable actually exists rather than assuming external storage is ready.
The second reboot exposed a more subtle race: PPPoE and the default route were not ready when sing-box started. With route.auto_detect_interface enabled, sing-box detected the routing environment once at startup. Starting before ppp0 and its default route existed left some client flows bypassing interception for the lifetime of the process without a fatal status error. Restarting sing-box after WAN convergence fixed every client.
The boot sequence now waits for a valid default route, adds a short stabilization interval, starts sing-box, installs rules, runs health checks, and confirms. Four real reboots verified storage mounting, unattended confirmation, multi-device interception, and tunnel forwarding. Boot reliability cannot be established by running the startup script manually on an already stable router.
5.3 Diagnose across clean client devices
One test laptop also ran Tailscale and several Docker bridges, which produced misleading symptoms during diagnosis. Reports from clean household devices proved that the router had an independent fault. When a network symptom appears on several devices, compare a clean client with the complex development machine and inspect packet paths on the gateway. A convenient test device can contain enough extra networking state to send an investigation in the wrong direction.
6. Latency, Queue Management, and Operational Lessons
6.1 VPS location changes interactive latency
The first tunnel endpoint was on the US east coast. Connection establishment paid the long round-trip time during transport setup, tunnel authentication, and subsequent requests. Moving the endpoint to the west coast roughly halved observed page-load delay. ICMP ping alone is not a reliable measurement because providers may filter it; test TCP or application-level round trips to the actual service port.
When a proxy feels slow, measure the real route to the server before changing cryptographic or routing parameters. Geographic and peering distance can dominate the software overhead.
6.2 CAKE SQM addresses bufferbloat, not routing
Transparent routing does not solve bufferbloat. When an uplink or downlink queue fills, interactive traffic can wait behind bulk transfers even when average bandwidth looks sufficient. Supported ASUSWRT-Merlin releases provide CAKE SQM separately from Adaptive QoS and Traditional QoS.
CAKE works as an interface queueing discipline and performs flow isolation. Configure shaping rates below sustained measured line rates, often beginning around 85–95%, so the controllable CAKE queue becomes the bottleneck before an unmanaged ISP or modem queue fills. Measure under load and adjust rather than treating one percentage as universal.
The TPROXY fwmark selects a policy-routing table; CAKE does not need that mark for its basic flow queueing. Older CONNMARK-based QoS engines may reserve mark bits, so inspect firmware behavior before enabling them alongside another fwmark user. Use one intentional queue-management system rather than stacking competing QoS modes.
6.3 What made the deployment reliable
The final deployment depended on a few repeatable practices: validate protocol logic in an inexpensive simulation, inspect real kernel and userland capabilities, allocate firmware-owned resources only after enumerating them, implement and deliberately test rollback before persistence, cover IPv4 and IPv6 together, treat boot prerequisites as asynchronous, verify packet flow instead of trusting process status, and compare more than one client environment.
Documentation usually describes a complete reference Linux platform. Embedded firmware may expose only part of that platform. Reliable gateway engineering begins by measuring the exact hardware and firmware state, then keeping every change reversible until repeated cold-boot and live-traffic tests prove otherwise.