Using RouterOS Routing Tables for a Same-Subnet Surge Gateway
/ , ,
AI TranslationSimplified ChineseEnglish
Summary:
With RouterOS as my home router, mangle marks and a custom routing table send selected devices through Surge while keeping everything on the same subnet.
When I first started tinkering with networking, an OpenWrt box was the main router at home. It handled everything: the WAN connection, internet access, and proxying. That was not a great arrangement for reliability. Whenever I experimented with something, the entire household lost its internet connection. Even when I left it alone, trouble with a proxy server could break access to plenty of sites that would otherwise have worked directly. Many devices at home did not need a proxy at all, including those using certain gaming services, so I even had to maintain separate routing exclusions to keep them off it.
When Surge released its Apple TV version, I decided to use it as a secondary gateway and take proxying off the main router.
The First Approach
There are a few common ways to use a secondary gateway: manually set the gateway and DNS server on devices that need the proxy, put them in a separate VLAN, or have the main router advertise the secondary gateway through DHCP. Manual configuration would leave me managing settings for everyone else and every device, with any problems coming straight back to me. I started with the DHCP approach instead.
DHCP can include numbered options that specify particular network settings. These include the default gateway (option 3) and DNS servers (option 6), which can override the settings normally advertised. Send those two options to the devices that need proxy access, and their traffic goes to the secondary gateway.
After using it for a while, though, the same problem remained: nobody else at home could choose whether to use the proxy. The router decided which settings a device received, and the person using it had no convenient way to change them. Getting back to a direct connection still meant asking me.
I considered adding a health check so the router would stop advertising those options if the secondary gateway lost connectivity. But DHCP settings come with a lease, and clients do not fetch new settings until they renew it. That left a delay before devices could recover, so the result was still not good enough.
Two Wi-Fi Networks
At the time, I happened to have two Xiaomi routers acting as access points, each of which could use a different Wi-Fi name. What if I made one Wi-Fi network direct and sent the other through the proxy? Everyone at home could choose the connection they wanted. To get back to a direct connection, they could simply switch Wi-Fi networks without waiting for a DHCP lease to renew.
The most straightforward setup would connect one Wi-Fi network to the secondary gateway and the other to the main gateway. But I wanted everything at home to stay on the same subnet, so management, smart-home devices, and local services would all continue to work normally. I did not want separate networks that either could not reach each other or needed NAT between them.
Fortunately, I had already replaced the main router with a MikroTik RB5009U. It had the routing tables you would expect from a router, so I could put some of my experience with BGP and ASNs to use.
The Idea
Adding an Apple TV running Surge gave the network another way out, while every device still used the main router as its default gateway. To make use of Surge, I only needed firewall rules that sent selected traffic to it: for example, traffic arriving through a particular AP port, or traffic from a specified IP or MAC address.
Sending traffic through a different gateway really means giving it a different set of routes. I needed to create a routing table and mark the packets that should go through Surge. Rather than looking up the default main table, marked packets would use the new table. That table would contain just one default route, with Surge as the next hop.
This is the same general idea as choosing a next hop through BGP policy, except that here the decision is based on where the packet comes from.
RouterOS processes packets in stages, and each rule only applies at a particular stage. Before writing the rules, we need to understand which stages a packet passes through and in what order.
Packet Flow in RouterOS
The RouterOS firewall has several chains. An incoming IP packet takes roughly the following path.
This is a simplified version I put together from the official Packet Flow in RouterOS documentation. The documentation has the full diagrams, including bridging, hardware offloading, and IPsec. Its diagram of the routed forwarding path looks like this.
A few parts of this ordering are particularly important for this setup.
The routing decision comes after prerouting and dstnat. Before the router decides where a packet should go, there are two opportunities to change it: mark it in mangle prerouting, or rewrite its destination in dstnat. The route lookup then uses the rewritten destination address.
srcnat comes after the routing decision, near the end of the path before the packet leaves. Since the route has already been chosen, it can only change the source address.
In other words, a routing mark for this incoming traffic has to be applied in mangle prerouting, before the routing decision. Applying it in forward would be too late: the route has already been selected.
Traffic generated by the router itself goes through the output chain. It gets an initial routing decision before mangle output. The documentation shows a routing adjustment after that, described as a workaround that allows mangle in the output chain to do policy routing as well. The traffic in this article comes from client devices and passes through the router, so we do not need that part of the path.
The addresses and rules below handle IPv4 only; they do not automatically cover IPv6. If clients also have IPv6 connectivity, that needs its own policy. Do not treat these rules as a configuration that proxies both IP versions.
The Routing Table
First, create a routing table.
Include fib when creating the table, as the official policy-routing examples do. That installs its routes in the FIB used for forwarding. Also, in RouterOS v7, a routing mark must refer to a routing table that already exists. Without that table, the mark in mangle will not do what we need.
Then add a default route to the table, pointing to Surge.
My main router is 10.10.77.1, Surge is 10.10.77.4, and the subnet is 10.10.77.0/24.
There are now two routing tables at home. The default route in main still points to the WAN interface, while the default route in rsurge points to Surge.
The rules below refer to an address list named lan-ip. Add the local subnet to it first. If you have other internal subnets that should remain directly reachable, add those to the list as well.
Marking the Packets
Next, add mangle rules to decide whose traffic should use rsurge.
The in-bridge-port matcher used below selects a physical bridge port. It requires the bridge's use-ip-firewall setting to be enabled first; the default is no. This sends bridged traffic through the IP firewall and affects Bridge FastPath and CPU usage, so assess the performance impact before enabling it. The source-IP address-list approach shown later does not depend on this bridge-port matcher. For details, see MikroTik's Common Firewall Matchers and Actions and Bridging and Switching.
This rule has three conditions.
in-bridge-port=ether8 selects which devices use the proxy. I later added a downstream switch and connected all the APs and devices that needed proxy access through this port. That way, all traffic arriving through it gets marked, without a separate rule for each device. mangle can match a specific physical port within a bridge, which makes this convenient.
dst-address-list=!lan-ip excludes local traffic. lan-ip is an address list containing the internal subnets. Traffic destined for those subnets should follow its normal routes, not go to Surge, so smart-home devices and local services can continue to work.
src-address=!10.10.77.4 excludes Surge itself. After processing a request, Surge sends its outbound traffic to the main router. Without this exclusion, that traffic would get marked and sent straight back to Surge, creating a loop.
If the devices are not all behind one port, put their IP addresses in an address list instead and match them with src-address-list.
FastTrack
The default RouterOS configuration includes a FastTrack rule. It speeds up forwarding by letting established connections bypass most of the remaining processing. The FastTrack section of the official documentation notes that its route lookup happens before routing marks are set, so it only works with the main routing table. If a connection that uses mark-routing gets FastTracked, its packets will follow main rather than go to Surge.
If that rule is present, connections using Surge need to be excluded from FastTrack. Add another mangle rule with the same matching conditions to give those connections a connection mark. Then add connection-mark=no-mark to the FastTrack rule, so only unmarked connections can use it.
This mark-connection rule can go before or after the earlier mark-routing rule. Both have passthrough set to yes, so packets continue on to the following rules.
DNS
Traffic can now reach Surge, but DNS still needs to be handled.
In Enhanced Mode, Surge uses Fake IP. When a client looks up a domain, Surge returns a synthetic address from 198.18.0.0/15. When the client connects to that address, Surge knows which domain it represents and handles the connection according to its rules. Client DNS requests therefore need to reach Surge. Otherwise, clients receive real IP addresses and the Fake IP mechanism is not used.
In dstnat, I redirect these devices' DNS requests to Surge's Fake IP DNS address, 198.18.0.2.
Both DNS rules also use src-address=!10.10.77.4 to exclude Surge itself. If Surge is connected through the same bridge port, this keeps its upstream DNS queries from being redirected back to itself. DNS also uses TCP, so add a TCP port 53 rule with the same conditions. Encrypted DNS such as DoH and DoT is outside the scope of these two rules and needs to be handled separately on the clients or through network policy. If you changed the earlier device-selection rules to use a source address list, update both DNS rules to match that selection as well.
The order matters again here: DNS requests pass through mangle prerouting first, then dstnat, and only then reach the routing decision.
If a client uses an external DNS server, its destination is not in lan-ip, so mangle has already marked the request. dstnat simply rewrites its destination to 198.18.0.2. The router then looks up rsurge and sends it to Surge through the default route.
If a client uses the router itself as its DNS server, the destination is local. lan-ip excludes it in mangle, so it has no routing mark. dstnat then changes the destination to 198.18.0.2. When the request reaches the routing decision, it still has no mark and therefore uses main. That means main also needs a route sending 198.18.0.0/16 to Surge.
Finally, add a srcnat rule.
When a client sends a DNS request to the router at 10.10.77.1, it expects the reply to come from that same address. If the request is redirected to Surge, Surge can reply directly to the client with 198.18.0.2 as the source address. The client does not recognize that as the expected reply and drops it. srcnat changes the request's source address to the router's own address, so Surge replies to the router first. Connection tracking then lets the router reverse the translations and deliver the reply to the client as expected.
The Result
There are now two Wi-Fi networks at home: one connects directly, and the other goes through Surge. Both are on the same subnet, so devices can still reach each other. If someone has a connection problem, they can switch Wi-Fi networks without asking me. A problem with Surge no longer affects the main router or devices that do not need the proxy.
If a service needs special routing later, I can just add a route to rsurge. For example, a particular subnet could bypass Surge and use the actual WAN next hop instead. Do not set the gateway to the main router's own LAN address, 10.10.77.1: that is not a valid upstream next hop. For a static WAN gateway, use the form below. @main tells RouterOS to resolve the next hop in main; for PPPoE, use the actual PPPoE interface as appropriate for your configuration.
Here, <网段> (subnet) and <实际WAN网关> (actual WAN gateway) are placeholders. Replace both with the appropriate values for your network.
The same approach also works on OpenWrt with ip rule and ip route table: mark the packets first, then have them look up different routing tables.