Custom routing-table + mark-routing causes complete loss of internet for the marked LAN subnet (RouterOS 7.23.2, RB450Gx4)

## Hardware / Software

- RouterBOARD RB450Gx4, RouterOS **7.23.2 (stable)**, arm architecture

- Factory firmware 7.16.2, current/upgraded firmware 7.23.2

## Background / Existing Setup

We run a dual-WAN setup with ECMP PCC load-balancing that has been working reliably for a long time:

```

/ip route

add dst-address=0.0.0.0/0 gateway=100.64.0.1%ether1-ISP routing-table=*400 check-gateway=arp distance=1 comment="PCC route ether1"

add dst-address=0.0.0.0/0 gateway=100.64.0.1%ether2-ISP routing-table=*401 check-gateway=arp distance=1 comment="PCC route ether2"

```

(Existing PCC mangle rules mapping to `*400`/`*401` are omitted — standard per-connection-classifier pattern, and confirmed working.)

`ether1-ISP` and `ether2-ISP` are both DHCP clients sharing the **same gateway** `100.64.0.1/10` (CGNAT shared address space). Each interface receives a different public/CGNAT IP (e.g. `100.83.12.163` vs `100.108.62.245`).

## Goal

Force traffic originating from a specific set of LAN subnets (bridged on `ether3`, sub-VLANs `10.10.10.0/24`, `10.10.20.0/24`, `10.10.70.0/24`, and a pool named "ultimate" on `10.10.50.0/24`) to **always** egress via `ether1-ISP` — bypassing the normal ECMP 50/50 split — with automatic fallback to `ether2-ISP` if `ether1-ISP` becomes unreachable.

## Configuration Attempted (latest iteration, with local-traffic exclusion)

```

/routing/table

add name=to-ether1-dedicated fib

/ip route

add dst-address=0.0.0.0/0 gateway=100.64.0.1%ether1-ISP routing-table=to-ether1-dedicated check-gateway=arp distance=1 comment="Dedicated primary: ether1"

add dst-address=0.0.0.0/0 gateway=100.64.0.1%ether2-ISP routing-table=to-ether1-dedicated check-gateway=arp distance=2 comment="Dedicated fallback: ether2"

/routing/rule

add routing-mark=to-ether1-dedicated action=lookup-only-in-table table=to-ether1-dedicated

/ip firewall mangle

add chain=prerouting action=mark-connection src-address=10.10.10.0/24 dst-address-type=!local dst-address=!10.0.0.0/8 connection-state=new connection-mark=no-mark new-connection-mark=to-ether1-conn passthrough=yes

add chain=prerouting action=mark-connection src-address=10.10.20.0/24 dst-address-type=!local dst-address=!10.0.0.0/8 connection-state=new connection-mark=no-mark new-connection-mark=to-ether1-conn passthrough=yes

add chain=prerouting action=mark-connection src-address=10.10.70.0/24 dst-address-type=!local dst-address=!10.0.0.0/8 connection-state=new connection-mark=no-mark new-connection-mark=to-ether1-conn passthrough=yes

add chain=prerouting action=mark-connection src-address=10.10.50.0/24 dst-address-type=!local dst-address=!10.0.0.0/8 connection-state=new connection-mark=no-mark new-connection-mark=to-ether1-conn passthrough=yes

add chain=prerouting action=mark-routing connection-mark=to-ether1-conn new-routing-mark=to-ether1-dedicated passthrough=no

```

We deliberately excluded local and inter-LAN traffic (`dst-address-type=!local`, `dst-address=!10.0.0.0/8`) in the `mark-connection` rules, following the guidance in MikroTik's own documentation on the "local traffic gets blackholed" pitfall: Moving from ROSv6 to v7 with examples - RouterOS - MikroTik Documentation

## Observed Behavior

1. **Mangle counters increment normally.** Both the `mark-connection` and `mark-routing` rules show hit counts climbing into the hundreds/thousands, confirming the traffic is being matched and marked as expected.

2. **Router-originated traffic to the local LAN gateway is unaffected.** Pinging from the router itself to its own `ether3` address (`10.10.70.1`) stays healthy (~350 µs RTT) — this rules out the classic "local subnet traffic gets sent into the dedicated table and blackholed" failure mode, since we already excluded local/LAN destinations.

3. **However, real clients behind the marked subnet lose internet access entirely.** A laptop on `10.10.70.0/24` reports "no internet at all" the moment the second mangle rule (`mark-routing`) is enabled. Removing that single rule (or the entire dedicated mangle set) immediately restores internet access for that client — nothing else changes.

4. **Routes inside the custom table (`to-ether1-dedicated`) show `inactive=true` persistently**, even though `check-gateway=arp` for `100.64.0.1` on `ether1-ISP` is confirmed `reachable` in the ARP table (`/ip arp print` shows `status=reachable complete=true`). For comparison, the pre-existing PCC routes (`*400`/`*401` above, proven to work for years) **also** show `inactive=true` in this RouterOS version — so we are not confident this flag is a reliable indicator of failure here.

5. **We also tried a recursive-route variant**, probing a host route to `8.8.8.8/32` with `check-gateway=ping` (since the gateway `100.64.0.1` itself does not answer ICMP echo, despite the link/ARP being healthy). The recursive default route never resolves and stays `active=None`.

## Questions

1. Is there a known interaction between a custom routing-table + `mark-routing` and a subnet that **also has its own local DHCP server** on the same router (the `ether3` bridge runs `/ip dhcp-server` for both `10.10.10.0/24` and `10.10.70.0/24`)? Does DHCP lease-renewal traffic (broadcast/unicast to the local gateway) need an exclusion beyond `dst-address-type=!local`?

2. Is `inactive=true` on a route inside a custom table with `check-gateway=arp` actually meaningful/diagnostic in 7.23.2, or is it cosmetic noise unrelated to whether the gateway is properly resolved for that table? Why would it also show `inactive=true` on the main-table PCC routes that are demonstrably working?

3. Is there a more reliable pattern in RouterOS 7.23.2 for "force subnet X to always egress a specific interface, with automatic failover" on a topology like this (shared-gateway dual WAN, per-subnet policy routing, local DHCP on the affected bridge)?

Any pointers — including "you're doing X fundamentally wrong" — would be very much appreciated. Happy to share full `/export` output for the relevant sections if useful.

Thanks in advance.

Last stable version is 7.24.1, use that version, if not work, contact support.

Draw a network diagrams and show latest config

/export file=anynameyouwish (minus router serial number, any public WANIP info, keys, dhcp lease lists)

add chain=prerouting action=mark-routing connection-mark=to-ether1-conn new-routing-mark=to-ether1-dedicated passthrough=no

The above rule is usually an issue.

With most ROS V7 it forces all traffic with this routing mark to exit via the gateway ether1-isp or ether2-isp, the return traffic included. (I believe the actual route process ordering can be changed with very recent ROS)

You need to be a bit more careful with this marking.
Eg. only mark it if in addition to having the connection mark also, in-interface-list = LAN or maybe src-address=10.10.0.0/16 or similar
(You can also do it with routing rules)

Thanks for the pointer. Upgraded to 7.24.1 and re-tested the whole setup. Wanted to close the loop here because it turned out to be two separate problems layered on top of each other, and the second one wasn't a RouterOS bug at all.

## What I was actually trying to do

Same as the original post: force specific customers/subnets to always egress through ether1 instead of following the normal ECMP split, with automatic fallback to ether2 if ether1 goes down. The targets ended up being a mix of:

- A bridged LAN subnet (`ether3`, static IPs, no PPPoE)

- Several PPPoE customers, each pinned by their static PPPoE-assigned IP

## Round 1 (7.23.2): confirmed your suspicion, sort of

After upgrading to 7.24.1, the original symptom (routes in the custom table stuck at `inactive=true`, client on the marked subnet losing internet completely) went away. Routes in the custom table now show `active=true` even with `check-gateway=arp`, and a client actually connected to the `ether3` subnet ran a speed test that matched ether1's raw throughput almost exactly. So credit where it's due, whatever changed between 7.23.2 and 7.24.1 fixed that part.

## Round 2: a second bug I introduced myself

Once ether3 worked, I copied the same mangle setup to a PPPoE customer and it failed again, just differently. Mangle counters climbed fine, `mark-connection` and `mark-routing` were both firing, but the customer's connections kept dying in `syn-recv` for a few seconds before timing out. Classic "looks like it's working but isn't."

Turned out I was using `in-interface-list` in the `mark-routing` rule (a list containing `ether3` and the PPPoE server's parent bridge, `bridge-OLT`) to make sure the rule only caught outbound traffic from the LAN, not the reply traffic coming back from the WAN. That distinction matters a lot: if `mark-routing` also catches reply packets, they get looked up in the dedicated table, which only has a default route to the WAN, no route back to the LAN, so the reply silently disappears.

For the `ether3` subnet, `in-interface-list` worked because that traffic really does arrive on `ether3` at the firewall level. For PPPoE customers it doesn't work at all, because RouterOS reports the ingress interface as the dynamic per-session interface (``), not the parent bridge the PPPoE server is bound to. So `bridge-OLT` in the interface list never matched, and the rule I thought was "restricting to LAN-origin traffic" was actually matching nothing for PPPoE users while still technically incrementing packet counters on the earlier, broader rule.

Fix was simple once I found it: match on `src-address` (the customer's PPPoE-assigned IP or subnet) instead of `in-interface-list`. Since `src-address` only matches traffic actually originating from that customer, it naturally excludes reply traffic without needing an interface check at all. Applied that to both PPPoE customers, flushed the existing connection-tracking entries so new connections would get evaluated under the new rule right away, and connections immediately moved to `established` with `seen-reply=true`.

## Verification I actually trust

Rather than relying on counters, I disabled `ether3` entirely for a few minutes so only the PPPoE customer's marked traffic could be flowing through the dedicated table. Watched the `mark-routing` packet counter climb continuously, watched new connections resolve to ether1's WAN IP instead of ether2's, and confirmed with interface byte counters that ether1 traffic tracked the mangle counter delta in real time. Re-enabled ether3 afterward, no leftover config from the isolation test.

## Where it stands now

Both patterns (bridged LAN and PPPoE-pinned customers) route correctly through ether1 with ether2 fallback, verified with live traffic, not just counters. If anyone else is doing PBR with a mix of bridged subnets and PPPoE customers on the same router: don't assume an `in-interface`/`in-interface-list` match that works for one will work for the other. Check what interface RouterOS actually reports for PPPoE sessions in your setup before trusting it, or just skip the interface check and match on `src-address` if you can, it doesn't care what kind of session the traffic came from.

Hi,

Can please you translate that wall of text to a human readable form?

There is such a mix of passive voice, first & second person form that it's hard to parse it.

Writing proper abstract is as writing a cheatsheet for yourself. Compacted, readable and properly formatted.

You can edit the associated /ppp profile and set the interface-list property. The dynamic interfaces created by connected PPPoE users will then be put in that specified interface list, and you can use the in-interface-list condition again.

Summary generated with Google Gemini:
( When AI encodes something, it uses AI to decode it....)

The upgrade to RouterOS 7.24.1 resolved an initial issue where custom table routes were inactive, allowing successful traffic routing for the bridged LAN subnet (ether3). However, applying the mangle setup to PPPoE customers caused connection timeouts due to a misconfigured in-interface-list in the mark-routing rule that incorrectly caught reply packets. You can read the full analysis at the original source.