Skip to content

Commit da6122e

Browse files
committed
Monitoring Interfaces: sweep the stale main-table route when alias mode deploys
A row deployed plain and later switched to alias via "Save without deploying" + the row-level Deploy button (which, unlike Save & Deploy, never tears down the old config) kept its old main-table host route. In alias mode that route still captures the shared TARGET_IP in the main table ahead of the other WAN's device - the exact hijack alias mode exists to prevent. The boot script's alias branch now deletes this interface's main-table host route (dev-scoped, so another row legitimately routing the same TARGET_IP via its own interface is untouched), and the idempotency guard requires its absence - so the cron watchdog also self-heals state left behind by earlier builds. Verified end-to-end in a NET_ADMIN container: plain deploy, alias deploy over it, idempotent re-run.
1 parent ab0b0ae commit da6122e

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

src/NetworkOptimizer.Web/Services/MonitoringInterfaceDeploymentService.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -779,25 +779,35 @@ ip addr flush dev ""$IFACE"" 2>/dev/null
779779
# 3. host route to the modem/ONT. Aliased interfaces skip the main-table route entirely
780780
# (ambiguous when two WANs share TARGET_IP) and instead route via a private per-interface
781781
# table, selected by an fwmark set on ALIAS_IP before DNAT rewrites it to TARGET_IP. All
782-
# four alias artifacts are checked together before touching any of them: tearing down and
783-
# re-adding on every tick (rather than only when something's actually missing or wrong)
782+
# four alias artifacts - plus the absence of a stale main-table route left by a pre-alias
783+
# deployment of this row - are checked together before touching any of them: tearing down
784+
# and re-adding on every tick (rather than only when something's actually missing or wrong)
784785
# would defeat the ""only log when changed"" eMMC guard below AND open a window, every
785786
# single tick, where the mark rule is briefly absent - during which an in-flight flow to
786787
# the alias can leak via the main table toward the OTHER WAN's device, the exact hijack
787788
# this feature exists to prevent.
788789
if [ ""$ALIAS_ENABLED"" = ""1"" ]; then
789790
if ip route show table ""$TABLE"" ""$TARGET_IP/32"" 2>/dev/null | grep -q ""dev $IFACE"" &&
790791
ip route show table ""$TABLE"" ""$TARGET_IP/32"" 2>/dev/null | grep -q ""src $LOCAL_IP"" &&
792+
! ip route show ""$TARGET_IP/32"" 2>/dev/null | grep -q ""dev $IFACE"" &&
791793
ip rule show | grep -qF ""fwmark $MARK/$MASK lookup $TABLE"" &&
792794
iptables -w 5 -t mangle -C PREROUTING -d ""$ALIAS_IP"" -j MARK --set-xmark ""$MARK/$MASK"" 2>/dev/null &&
793795
iptables -w 5 -t nat -C PREROUTING -m mark --mark ""$MARK/$MASK"" -j DNAT --to-destination ""$TARGET_IP"" 2>/dev/null; then
794-
: # all four alias artifacts already present and correct - nothing to do this tick
796+
: # all four alias artifacts present and no stale main-table route - nothing to do this tick
795797
else
796798
cleanup_marked_rules mangle
797799
cleanup_marked_rules nat
798800
ip rule show | grep -q ""lookup $TABLE\b"" && ip rule del fwmark ""$MARK/$MASK"" lookup ""$TABLE"" 2>/dev/null
799801
ip route flush table ""$TABLE"" 2>/dev/null
800802
803+
# A pre-alias plain deployment of this row left its host route in the MAIN table,
804+
# where it keeps capturing the shared TARGET_IP ahead of the other WAN's device -
805+
# the exact hijack alias mode exists to prevent. dev-scoped delete: another row may
806+
# legitimately route the same TARGET_IP via ITS OWN interface.
807+
if ip route show ""$TARGET_IP/32"" 2>/dev/null | grep -q ""dev $IFACE""; then
808+
ip route del ""$TARGET_IP/32"" dev ""$IFACE"" && changed=1 || fail=1
809+
fi
810+
801811
ip route replace ""$TARGET_IP/32"" dev ""$IFACE"" src ""$LOCAL_IP"" table ""$TABLE"" && changed=1 || fail=1
802812
ip rule add fwmark ""$MARK/$MASK"" lookup ""$TABLE"" && changed=1 || fail=1
803813
iptables -w 5 -t mangle -A PREROUTING -d ""$ALIAS_IP"" -j MARK --set-xmark ""$MARK/$MASK"" && changed=1 || fail=1

tests/NetworkOptimizer.Web.Tests/MonitoringInterfaceDeploymentServiceTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ public void BootScript_IdempotencyGuard_AlsoChecksRouteSrc()
150150
script.Should().Contain("ip route show table \"$TABLE\" \"$TARGET_IP/32\" 2>/dev/null | grep -q \"src $LOCAL_IP\"");
151151
}
152152

153+
[Fact]
154+
public void BootScript_Aliased_SweepsStaleMainTableRoute()
155+
{
156+
// A row deployed plain and later switched to alias mode via "Save without
157+
// deploying" + the row-level Deploy button (which, unlike SaveAndDeploy, never
158+
// tears down the old config) keeps its old main-table host route. In alias mode
159+
// that route still captures the shared TARGET_IP in the main table - the exact
160+
// other-WAN hijack alias mode exists to prevent. The alias branch must delete it,
161+
// and the idempotency guard must require its absence (a fast path that only checks
162+
// the four alias artifacts would skip the sweep forever).
163+
var script = MonitoringInterfaceDeploymentService.GenerateBootScript(ValidAliased());
164+
165+
script.Should().Contain("! ip route show \"$TARGET_IP/32\" 2>/dev/null | grep -q \"dev $IFACE\" &&");
166+
script.Should().Contain("ip route del \"$TARGET_IP/32\" dev \"$IFACE\" && changed=1 || fail=1");
167+
}
168+
153169
[Fact]
154170
public void BootScript_CleanupMarkedRules_LoopReadsFromFileNotPipe_SoFailureFlagSurvives()
155171
{

0 commit comments

Comments
 (0)