Policy routing - #492
Draft
kp-antonio-yang wants to merge 7 commits into
Draft
Conversation
|
Code coverage summary for 04fd17d: ✅ Region coverage 69% passes |
kp-antonio-yang
force-pushed
the
policy-routing
branch
20 times, most recently
from
July 30, 2026 08:54
2d54621 to
2a98efd
Compare
kp-antonio-yang
marked this pull request as ready for review
July 30, 2026 10:17
kp-antonio-yang
force-pushed
the
policy-routing
branch
from
August 3, 2026 06:17
dc191ce to
c738801
Compare
6 tasks
kp-antonio-yang
marked this pull request as draft
August 4, 2026 06:41
kp-antonio-yang
force-pushed
the
policy-routing
branch
2 times, most recently
from
August 4, 2026 09:19
afa6a96 to
c454693
Compare
kp-antonio-yang
force-pushed
the
policy-routing
branch
3 times, most recently
from
August 4, 2026 09:49
f63efa6 to
51c68eb
Compare
kp-antonio-yang
marked this pull request as ready for review
August 4, 2026 09:55
kp-antonio-yang
marked this pull request as draft
August 6, 2026 06:12
kp-antonio-yang
force-pushed
the
policy-routing
branch
4 times, most recently
from
August 12, 2026 02:37
7f6a2df to
4410022
Compare
Diagnostic `debug!` log in `lightway-core/src/connection.rs` to surface DTLS silent drops. 0 frames decoded and treated as a replay attack in server side. It is useful for diagnosing UDP session recovery failures after NAT rebinds.
Without fwmark mode, the tunnel keeps its own encrypted packets out of the tunnel via a Lightway-managed /32 host route for the VPN server. After a network change there is a window where that route is gone but not yet replaced: the server IP matches the tunnel catch-all, packets are re-encapsulated each lap, and the link saturates. RouteMode::Fwmark (Linux only) fixes this by installing four ip rules before any tunnel route is added: MARKED: fwmark <MARK> → main # tunnel socket uses WAN route SERVER: to <SERVER_IP>/32 → main # rp_filter: accept server replies MARKED_FALLBACK: fwmark <MARK> → unreachable # drop marked traffic if main is empty TUNNEL: (any) → <TABLE> # everything else goes through tunnel The outside socket is stamped with SO_MARK so rule MARKED always applies. The kernel owns the main table default route, so no host route is installed and there is nothing to race against. Rule SERVER prevents strict rp_filter from dropping inbound server packets when main is the only table that routes the server IP correctly. Rule MARKED_FALLBACK breaks the encapsulation loop during a roam when main has no route at all: ENETUNREACH is treated as a transient send failure.
- Expose fwmark, fwmark_route_table, rule_priority_server, rule_priority_fwmark_fallback, and rule_priority_tunnel as config and CLI options. Defaults - Config::validate() enforces the priorities are in required ordering
Add register_rt_table/unregister_rt_table helpers so `ip rule show`
displays `from all lookup lightway-tunnel` instead of a bare numeric
table id. Only the tunnel routing table is registered, since ip rules
themselves have no name field and are identified by priority.
register_rt_table:
- In debug builds, reads rt_tables first: no-ops if the entry is
already present with the correct id, errors if it exists with a
different id.
- In release builds, appends the line unconditionally — duplicates
are harmless because the kernel never reads this file.
- Called during install(); failures are logged as error but do not
abort rule installation, since the numeric table id works fine
without a registered name.
unregister_rt_table:
- Strips every line matching the table name on cleanup.
- Read/write errors are warned and swallowed so cleanup never aborts.
set up Fwmark as RouteMode::Default on Linux to avoid roaming issue
Add a new docs/policy_routing.md covering the firewall-mark-based policy
routing that Lightway uses on Linux to eliminate the encapsulation loop
race condition during Wi-Fi roams.
The document explains:
- The race condition inherent to the reactive /32 host-route approach
- The four ip rules (MARKED, SERVER, MARKED_FALLBACK, TUNNEL) and how
each steers packets via fwmark and the private tunnel routing table
- A full packet lifetime trace (outbound and inbound) for a ping
- RouteMode comparison (Fwmark vs Default vs Lan vs NoExec)
- FWMarkConfig parameters, defaults, and validation constraints
- Initialization/shutdown ordering and key source files
Also link the new page in the docs README table of contents.
…e loss Adds a Docker Compose stack and test script that simulate a client switching WiFi networks by deleting the active physical interface while the VPN is running, then verifying the client reconnects over the surviving interface. The compose file (docker-compose.wifi-switch.yml) attaches both the server and client to two bridge networks (frontend 192.168.200.0/24 and frontend2 192.168.201.0/24), giving the client container two physical interfaces that can each independently reach the server. The test script (run-wifi-switch-test.sh): - Verifies VPN masquerade is working before the switch - Dynamically detects the active interface via `ip route get` rather than hardcoding eth0, since Docker does not guarantee network ordering - Deletes that interface to trigger a network change event in the client - Polls for reconnection (up to 60s) and fails fast once it succeeds - Re-verifies VPN masquerade is working after reconnection
kp-antonio-yang
force-pushed
the
policy-routing
branch
from
August 12, 2026 02:55
4410022 to
e264dfe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Introduces
RouteMode::Fwmark(Linux only) and makes it the default route mode on Linux. Instead of the reactive /32 host-route approach, fourip ruleentries are installed before any tunnel route is added:On Linux (non-mobile), the outside UDP socket is always stamped with
SO_MARKwhenfwmark != 0, regardless of route mode. UnderRouteMode::Fwmarkthis causes Rule MARKED to steer the socket's packets viamain; because the kernel owns the main-table default route, no host route is installed and there is nothing to race against during a roam. InRouteMode::NoExecthe four ip rules are never installed, so the mark is present on the socket but has no routing effect, and in this modeSO_MARKcan be overwriten and turn it off by--fwmark=0in command line.Additional changes in this PR:
FWMarkConfigparameters are user-configurable —fwmark,fwmark_route_table,rule_priority_server,rule_priority_fwmark_fallback, andrule_priority_tunnelare exposed as config file and CLI options.Config::validate()enforces that priorities are in the required ordering./etc/iproute2/rt_tables—register_rt_table/unregister_rt_tablehelpers register the tunnel routing table under the human-readable namelightway-tunnelso thatip rule showdisplaysfrom all lookup lightway-tunnelinstead of a bare numeric id. Registration failures are logged but do not abort rule installation.debuglog inlightway-core/src/connection.rssurfaces silent DTLS record drops (0 frames decoded, treated as a replay attack) to aid diagnosis of UDP session recovery failures after NAT rebinds.docs/policy_routing.mdcovers the race condition, the four ip rules and their roles, a full packet lifetime trace,RouteModecomparison,FWMarkConfigparameters, and initialization/shutdown ordering.Motivation and Context
Without fwmark mode, the tunnel avoids routing its own encrypted packets back into itself via a Lightway-managed /32 host route for the VPN server. After a network change (Wi-Fi roam, NAT rebind) there is a window where that host route is gone but not yet replaced: the server IP matches the tunnel catch-all, packets are re-encapsulated each lap, and the link saturates.
RouteMode::Fwmarkeliminates this race entirely — the ip rules are independent of route table state and require no teardown/reinstall during roams. Rule MARKED_FALLBACK additionally breaks any residual encapsulation loop during a roam when the main table has no route at all, returningENETUNREACHand treating it as a transient send failure.How Has This Been Tested?
ip rule showdisplays named table entries (lightway-tunnel) after connection, and that they are cleaned up on disconnect.RouteMode::Fwmarkactive.Types of changes
Checklist:
mainxenon/libxenon-srcis accompanied by a reference to the specific commit in the git changelog