用 RouterOS 路由表設定同網段的 Surge 旁路由

/ , ,
簡體中文正體中文
摘要:
家裡以 RouterOS 當主路由器,搭配 mangle 標記與自訂路由表,讓部分裝置在同一網段內透過 Surge 上網。

早期折騰網路的時候,家裡用 OpenWrt 當主路由器,撥號、上網、代理全都由它負責。這樣的穩定性很有問題,只要我動點設定,全家的網路就可能一起掛掉。就算我什麼都沒動,代理連線出了問題,也會影響不少原本可以正常開啟的網站。而且家裡有很多裝置根本不需要代理,例如使用某些遊戲服務的裝置,所以我還得另外設定分流,把它們排除在代理之外。

後來 Surge 推出了 Apple TV 版,我就打算改用 Surge 當旁路由,把代理功能從主路由器獨立出來。

早期的方案

旁路由一般有幾種做法:手動把需要代理的裝置的閘道和 DNS 改成旁路由的位址、劃分 VLAN,或由主路由器透過 DHCP 配發旁路由的位址。如果手動設定,我就得逐一管理家人的裝置,出了問題也還是得找我,所以一開始用的是 DHCP 配發的方式。

DHCP 可以透過編號選項提供不同的網路設定,其中就包括閘道(option 3)和 DNS(option 6),用來取代原本配發的設定。只要對需要代理的裝置配發這兩個選項,它們的流量就會經過旁路由。

但用了一段時間,還是遇到之前的問題:家人沒辦法自行切換。裝置拿到什麼設定由路由器決定,家人在裝置上沒有方便的切換方式,想恢復不經過代理的連線,還是只能找我。

我也想過加入自動偵測,在旁路由斷線時停止配發這些設定。不過 DHCP 有租約期限,裝置要等到續租時才會重新取得設定,無法立刻恢復,效果還是不理想。

兩個 WiFi

當時家裡正好有兩台小米路由器當 AP,可以設定不同的 WiFi 名稱。所以我想,能不能設兩個 WiFi,一個不經過代理,另一個經過代理?家裡人想用哪個就連哪個,要恢復直接連線,換個 WiFi 就好,不用再等 DHCP 續租。

最直接的做法,是讓一個 WiFi 連到旁路由,另一個連到主閘道。但我希望家裡所有裝置都在同一個網段,這樣不論是管理、智慧家庭,還是區域網路裡的各種服務,都能照常使用。我不想把網路分成互不相通,或需要透過 NAT 轉換才能互通的多個網段。

好在家裡的主路由器已經換成 MikroTik RB5009U,具備一般路由器都有的路由表功能。這樣就能把之前折騰 BGP 和 ASN 的經驗用上了。

思路

加了一台執行 Surge 的 Apple TV 當旁路由,就等於多了一個網路出口,但所有裝置的預設出口仍指向主路由器。要讓旁路由發揮作用,只需要用防火牆規則,將特定流量改送到旁路由,例如從某個 AP 連接埠進來的流量,或來自指定 IP、MAC 位址的流量。

讓流量改走另一個閘道,本質上就是改用另一套路由。因此,我需要建立一張路由表,替需要經過旁路由的封包加上標記。帶有標記的封包不再查預設的 main 表,而是改查新表。新表只需要一條預設路由,下一跳指向 Surge。

這其實和 BGP 依策略選擇下一跳的想法相同,差別在於這裡選擇的依據是封包來源。

RouterOS 會分階段處理封包,每條規則只在特定階段生效。所以在寫規則之前,得先弄清楚封包經過哪些階段,以及它們的先後順序。

封包在 RouterOS 裡的流向

RouterOS 的防火牆分成幾條鏈,一個 IP 封包進來之後,大致會經過以下流程。

Mermaid diagram

這是我依照官方文件的 Packet Flow in RouterOS 整理的簡化版。文件有完整的流程圖,包含橋接、硬體卸載、IPsec 等情況。路由轉送的部分,在官方圖中是這樣的。

路由轉送時各條鏈的順序,圖出自 MikroTik 文件
路由轉送時各條鏈的順序,圖出自 MikroTik 文件

其中幾個階段的先後順序,對這個需求特別重要。

路由決策在 prerouting 和 dstnat 之後,因此在決定封包要走哪條路之前,還有兩次調整機會:在 mangle prerouting 加上標記,以及在 dstnat 改寫目的位址。路由器查路由表時,使用的已經是改寫後的目的位址。

srcnat 在路由決策之後,是封包離開前的最後一步。此時路由已經選定,所以它只能改寫封包的來源位址。

也就是說,這些從裝置進來的封包,必須在路由決策前的 mangle prerouting 加上路由標記。如果放在 forward 裡,此時路由已經選定,標記就來不及生效。

路由器自己產生的流量走 output 鏈,會先做一次路由決策,再經過 mangle output。文件在這之後還畫了 routing adjustment,並將它稱為 workaround,讓 output 鏈裡的 mangle 也能做策略路由。不過本文處理的是裝置送出、經過路由器的流量,用不到這段流程。

Note

以下位址和規則只處理 IPv4,不會自動涵蓋 IPv6。如果用戶端也有 IPv6 連線,需要另行規劃對應策略;不能把這些規則當成同時代理 IPv4 和 IPv6 的設定。

路由表

首先建立一張路由表。

BASH
/routing/table/add name=rsurge fib

建立路由表時加上 fib,和官方策略路由範例中的自訂表寫法一致,讓表裡的路由進入轉送時使用的 FIB。另外,RouterOS v7 的路由標記必須對應到已建立的路由表;如果沒有這張表,mangle 的標記就無法發揮作用。

接著在這張表裡加入一條預設路由,指向 Surge 的位址。

BASH
/ip/route/add dst-address=0.0.0.0/0 gateway=10.10.77.4 routing-table=rsurge distance=1

我的主路由器是 10.10.77.1,Surge 是 10.10.77.4,整個網段是 10.10.77.0/24。

現在家裡有兩張路由表:main 的預設路由仍走撥號介面,rsurge 的預設路由則指向 Surge。

後面的規則會引用 lan-ip 位址清單,先把內部網段加入。如果還有其他需要直接互通的內網網段,也要加入這份清單。

BASH
/ip/firewall/address-list/add list=lan-ip address=10.10.77.0/24

打標記

接著設定 mangle 規則,決定哪些流量要查 rsurge。

Important

下方用來比對實體橋接埠的 in-bridge-port,需要先啟用橋接介面的 use-ip-firewall,預設值是 no。這會讓橋接流量經過 IP 防火牆,影響 Bridge FastPath 和 CPU 負載,啟用前要評估路由器效能。如果改用後面的來源 IP 位址清單方式,就不需要這個橋接埠比對條件。詳細規則請參考 MikroTik 的 Common Firewall Matchers and Actions 和 Bridging and Switching。

BASH
/interface/bridge/settings/set use-ip-firewall=yes
BASH
/ip/firewall/mangle/add chain=prerouting action=mark-routing \
  new-routing-mark=rsurge passthrough=yes \
  src-address=!10.10.77.4 dst-address-list=!lan-ip \
  in-bridge-port=ether8

這條規則裡有三個條件。

in-bridge-port=ether8 決定哪些裝置走代理。我後來接了一台下游交換器,將需要代理的 AP 和裝置都接到這個埠,因此從這個埠進來的流量都會加上標記,不必替每台裝置單獨寫規則。mangle 可以直接比對 bridge 裡的實體埠,用起來很方便。

dst-address-list=!lan-ip 會排除內網流量。lan-ip 是一份位址清單,列出內部網段。目的地在內網的流量不應該送到 Surge,而應沿用正常路由,讓智慧家庭和區域網路裡的各種服務繼續運作。

src-address=!10.10.77.4 會排除 Surge 本身。Surge 處理完流量後,會將對外流量送給主路由器。若不排除,這些流量也會加上標記,再次送回 Surge,形成路由迴圈。

如果裝置沒有集中在同一個埠,可以改用位址清單,將需要代理的裝置 IP 放進清單,再透過 src-address-list 比對。

BASH
/ip/firewall/address-list/add list=surge-devices address=10.10.77.50
/ip/firewall/mangle/add chain=prerouting action=mark-routing \
  new-routing-mark=rsurge passthrough=yes \
  src-address-list=surge-devices dst-address-list=!lan-ip

FastTrack

RouterOS 預設設定裡有一條 FastTrack 規則,讓已建立的連線略過後續大部分處理,以提高轉送速度。官方文件的 FastTrack 章節提到,它會在設定路由標記前查路由,因此只適用於 main 路由表。也就是說,使用 mark-routing 的連線如果進入 FastTrack,封包就會沿著 main 表走,不會送到 Surge。

如果有這條規則,就必須將經過 Surge 的連線排除在 FastTrack 之外。做法是在 mangle 多加一條規則,用相同條件替這些連線加上連線標記,再於 FastTrack 規則加上 connection-mark=no-mark,只讓沒有標記的連線使用 FastTrack。

BASH
/ip/firewall/mangle/add chain=prerouting action=mark-connection \
  new-connection-mark=surge passthrough=yes \
  src-address=!10.10.77.4 dst-address-list=!lan-ip \
  in-bridge-port=ether8
/ip/firewall/filter/set [find action=fasttrack-connection] connection-mark=no-mark

這條 mark-connection 規則可以放在前面那條 mark-routing 規則之前或之後,因為兩條的 passthrough 都是 yes,封包會繼續向下比對規則。

DNS

到這裡,流量已經能送到 Surge,但還得處理 DNS。

Surge 在增強模式下使用 Fake IP。用戶端查詢網域名稱時,Surge 會回傳一個位於 198.18.0.0/15 的虛擬位址。用戶端再以這個位址連線,Surge 就能知道它對應的網域名稱,再依規則處理。因此,用戶端的 DNS 查詢必須送到 Surge;否則拿到的是真實 IP 位址,就用不到 Fake IP 機制。

我在 dstnat 裡,將這些裝置的 DNS 查詢重新導向 Surge 的 Fake IP DNS 位址 198.18.0.2。

BASH
/ip/firewall/nat/add chain=dstnat action=dst-nat \
  protocol=udp dst-port=53 \
  src-address=!10.10.77.4 in-bridge-port=ether8 \
  to-addresses=198.18.0.2

兩條 DNS 規則也用 src-address=!10.10.77.4 排除 Surge 本身,避免它連在同一個橋接埠時,上游 DNS 查詢又被導向自己。DNS 也會使用 TCP,所以要再加一條條件相同的 TCP 53 規則。這兩條規則不處理 DoH、DoT 等加密 DNS,必須另外在用戶端或網路策略中管理。如果前面改用來源位址清單選擇裝置,這裡的兩條 DNS 規則也要同步調整。

BASH
/ip/firewall/nat/add chain=dstnat action=dst-nat \
  protocol=tcp dst-port=53 \
  src-address=!10.10.77.4 in-bridge-port=ether8 \
  to-addresses=198.18.0.2

這裡又得注意順序:DNS 查詢會先經過 mangle prerouting,再到 dstnat,最後才做路由決策。

如果用戶端使用外部 DNS 伺服器,目的位址不在 lan-ip 裡,封包在 mangle 階段就已加上標記。dstnat 只會把目的位址改成 198.18.0.2,之後查 rsurge,透過預設路由送到 Surge。

如果用戶端使用路由器本身作為 DNS 伺服器,目的位址就在內網,因此在 mangle 階段會被 lan-ip 排除,沒有路由標記。到了 dstnat,目的位址改成 198.18.0.2,接著做路由決策。此時仍然沒有標記,所以會查 main。因此,main 也需要一條路由,將 198.18.0.0/16 指向 Surge。

BASH
/ip/route/add dst-address=198.18.0.0/16 gateway=10.10.77.4

最後還需要一條 srcnat 規則。

BASH
/ip/firewall/nat/add chain=srcnat action=src-nat \
  dst-address=198.18.0.2 \
  to-addresses=10.10.77.1

用戶端向路由器的 10.10.77.1 送出 DNS 查詢時,預期回覆也來自這個位址。若查詢改送給 Surge,Surge 會直接回覆用戶端,來源位址卻是 198.18.0.2,用戶端就會因為來源不符而丟棄回覆。加上 srcnat 後,查詢的來源位址會換成路由器本身,Surge 先回覆路由器,再由路由器透過連線追蹤還原位址轉換,將回覆送給用戶端。

效果

現在家裡有兩個 WiFi,一個直接上網,另一個經過 Surge。兩者位於同一網段,裝置之間仍能互相存取。家人遇到網路問題,換個 WiFi 就行,不用再找我。就算 Surge 出了問題,也不會影響主路由器和不需要代理的裝置。

以後若有服務需要特殊路由,只要在 rsurge 加一條路由即可。例如讓某個網段繞過 Surge,改走實際的 WAN 下一跳。這裡不能把閘道填成主路由器自己的 LAN 位址 10.10.77.1,因為那不是有效的上游出口。若使用固定的 WAN 閘道,可參考下面的寫法;@main 表示在 main 表中解析這個下一跳。如果是 PPPoE 撥號,則依實際設定使用撥號介面。

BASH
/ip/route/add dst-address=<网段> gateway=<实际WAN网关>@main routing-table=rsurge

這裡的 <网段>(網段)和 <实际WAN网关>(實際 WAN 閘道)都是預留值,請換成自己網路裡的值。

OpenWrt 上也能做同樣的事,用 ip rule 和 ip route table,原理相同:先替封包加上標記,再查不同的路由表。

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...