RouterOS 入门与最佳实践【更新中】
2023-05-17 15:17:21

RouterOS 是什么?

首先 RouterOS 诞生已经有 20 年了,不是什么新鲜事物。对于城中村黑宽带、简易公共 Wi-Fi 覆盖和网吧等用户场景,可以以较低的成本搭建一套可靠的网络,并基本满足运营需求。自从 2015 年提速降费政策出台、2017 年工信部发文《关于清理规范互联网网络接入服务市场的通知》,黑宽带和付费 Wi-Fi 热点项目基本消失殆尽了。所以你会发现简体中文互联网里关于这个东西的资料和讨论,大部分是处于 2005 至 2015 年之间。

闲鱼上成堆的 RouterBoard 从何而来?漂洋过海的洋垃圾 AP(Ruckus、Aruba 和 Xirrus)又流向何处?

答案是“黑灰产”,高情商的说法为“工作室”。RouterOS 可轻易实现多 VLAN 匹配多 VPN 出口,配合脚本控制还能实现自动重拨号、批量更换 SSID 等需求;而 Ruckus、Aruba 等品牌的高密 AP 可提供单接入点接入上百终端的能力,并将 SSID 绑定至特定 VLAN。不过如今 OTG 集成供电和网卡的方案也成熟了,传统的无线接入方案也就慢慢落幕了。

我是否应该学习 RouterOS?

RouterOS 的防火墙配置参数看着晦涩难懂,本质上是 Linux Kernel 的 Netfiler。至于 RouterOS Script 则是 MikroTik 自创的脚本语言,虽然语法完备但很奇怪,也没有语法检查或自动补全。如果是家庭用户,我不建议接触 RouterOS,你将在上面浪费非常多的时间,并且这种 domain knowledge 离开了 MikroTik 生态毫无意义。如果你自认为是一个时间和精力充沛,很多有钱有闲的极客,有着一定的网络知识,和一定的英文阅读能力,那么你可以选择它。

RouterOS 其实也有像 ArubaOS 那样完备的 User Guide,但 RouterOS 相关的中文社群一般以“伸手”、“回帖可见”为主,真正懂技术的人太少。写到这里,不禁想起一代传奇人物 Clowwindy 的一段话:

最适合这个民族的其实是一群小白围着大大转,大大通过小白的夸奖获得自我满足,然后小白的吃喝拉撒都包给大大解决的模式。通过这个项目我感觉我已经彻底认识到这个民族的前面为什么会有一堵墙了。没有墙哪来的大大。所以到处都是什么附件回帖可见,等级多少用户组可见,一个论坛一个大大供小白跪舔,不需要政府造墙,网民也会自发造墙。这尼玛连做个翻墙软件都要造墙,真是令人叹为观止。这是一个造了几千年墙的保守的农耕民族,缺乏对别人的基本尊重,不愿意分享,喜欢遮遮掩掩,喜欢小圈子抱团,大概这些传统是改不掉了吧。
Clowwindy

当然,中文互联网也有像 YuSongStarYu 这样的博主,算是一股清流了。

MikroTik Training Center(MTC)是 MikroTik 官方授权的培训机构,一般为代理商(公司)和从事弱电外包的个人。“MTCXX”只不过是 MikroTik 仿效 Cisco 的“CCXX“弄的一套培训体系,课程时长仅 2~3 天,本质上除了名称相似以外,不能一同而论。

MTC’s are not affiliated with each other and with MikroTik in any form
MTC 之间不存在任何关联,也不与 MikroTik 存在任何形式的关联。

学习路径

  1. 基本的网络背景知识是必不可少的。建议快速阅读 60 天通过 CCNA 考试,简单了解网络的基本概念和原理。
  2. 官方文档:入门配置防火墙,然后是脚本语言
  3. 第三方教程:YuSong 写的《RouterOS 入门到精通》@be-engineer 翻译的 RouterOS 中文手册
  4. 英文社群:MikroTik Forumr/mikrotik/
  5. 实用脚本:@eworm-de/routeros-scripts

Testbed

简单玩玩的话可以在虚拟机中运行 RouterOS CHR,或者之前 StarYu 发布的已激活 RouterOS v6/v7 ova 镜像。手头充裕的可以购买 hAP ax2 (C52iG-5HaxD2HaxD-TC),一款支持 PoE-in 和 DC 双路供电,双链 Wi-Fi 6 的 5 口千兆紧凑型路由。hAP ac2 (RBD52G-5HacD2HnD-TC)hEX (RB750Gr3) 也是不错的选择,性能足够应付千兆宽带场景,只不过硬件平台太老了,二手价格也不便宜,不如 600 块买新的 hAP ax2。

当然 MikroTik 和 Ubnt 这种起码正常的用户还能正常的买到正常的规格的产品,没有过分的缩水也没有过分的对私溢价,理客中一点说,确实他们的定位都算是在家用和商用中间,你的家用网络“进化”路线短期内到这里可能就是终点(毕竟就预算而言要不加个 0 已经买不到什么更贵的了),但是就长期或者对路由的性能产生足够高的需求后,你很可能会去折腾别的东西。
知乎匿名用户

MikroTik 的产品定价介于高端家用和低端商用之间,多出的那点成本你就当做是买 RouterOS 授权吧。如果是 TP-Link、小米在 2023 年以 100 块钱卖 MT7621 的路由器,人们早就骂娘了;但是 Ubnt EdgeRouter X (ER-X) 和 MikroTik hEX (RB750Gr3) 以 300 块钱的价格卖给你,你还是觉得香,这就是品牌溢价和洗脑的力量。

ER-X 这东西早些年在某 C 开头的论坛被捧成“弱电箱神器”,一堆人花 3-400 买回来发现看不懂英文、不会用,最后刷成 OpenWrt 或者 200 块钱挂闲鱼二手的比比皆是。距离 2015 年 ER-X 发布已经过去 8 年,其在淘宝等平台仍然维持 300+ 的售价。而你只需要花 200 就能买到基于高通 IPQ6000 的 Redmi AX1800,还能刷上 LEDE,高下立判。

EdgeRouter 的系统是 EdgeOS,底层基于 debian,用起来很舒适,在当年(2015 年)能提供精简的配置页面和流量可视化,确实惊艳。EdgeOS 是基于 Vyatta v6.3 的闭源产品,后来发展为了 VyOS,社区逐渐壮大,被 NTT、Vodafone 等运营商和 big tech 用于生产环境。

在 2018 年 Ubnt 发布 ER-4 后就彻底抛弃了 EdgeRouter 产品线,专注于颜值lì rùn更高的 Unifi 产品,收割高端家庭用户,实在是可惜。全球企业无线市场仍然是 Cisco、Aruba 和 Ruckus 为主,Ubiquiti 只配在 SOHO 和 SMB 市场混口饭吃。从 Gartner 每年发布的 Enterprise Wired and Wireless LAN Infrastructure 可以看到 Ubnt 和 TP-Link 在企业 WLAN 领域差不多打个平手。只有 2020 年的图是因为后来 Ubiquiti 压根就没上榜了。

其实早在 2017 年香橼机构痛批 Ubiquiti

Ubiquiti’s investor filings are full of fluff and buzz words.
Ubiquiti 的投资者文件充满了空话套话。
The company claims market leading technology despite little R&D spending.
该公司声称其技术处于市场领先地位,但研发支出很少。
Major industry players appear not to recognize Ubiquiti as a competitor.
主要的行业参与者似乎不承认 Ubiquiti 是一个竞争对手。

除此之外,被某 C 和某 S 开头的社区吹到天花乱坠的、UI 花里胡哨的 UniFi Controller 本质上只是给 MikroTik 的垃圾 CAPsMAN 配上了好看点的 CSS,仅仅起到下发配置的作用,懂得都懂。

最佳实践

RouterOS v7 开始支持 RESTful API 了,这意味着即使你不会 RouterOS Script,也可以通过 Python、Go、Rust 等语言来实现自动化操作。当然,如果你愿意花一点时间适应语法,RouterOS Script 其实也能轻松上手。

多多参考官方文档,少看中文社区。官方文档虽然有些地方不够详细,但是基本上能解决 90% 的问题。中文社区的回答往往是瞎 jb 指导,或者知其然不知其所以然。尤其需要注意 RouterOS v6 与 v7 的配置不兼容,发布时间较早的文章可能已经过时了。

网络设备是“基础设施”,它们的配置应该是“一劳永逸”的,而不是“一劳永逸”的去维护。如果你的网络设备配置需要经常变动、追版本更新,那么你的设备或者架构一定是有问题的。MikroTik 也被称为 BugTik,就是因为 bug 太多。从 RouterOS 的 ChangeLog 可以看出,每个版本都有大量的 bugfix,而且 bugfix 之后又会引入新的 bug,永无止境。所以我建议,只要没遇到明显 bug 或安全问题,就不要轻易升级版本

RouterOS Script 五分钟极速入门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 作用域内使用变量,并拼接字符串
{
:local var1 "hello"
:local var2 "world"
:put ($var1 . " " . $var2)
}

# 跨作用域访问变量
{:global var3 "每一对花括号都是一层作用域"}
:put $var3
:put "CLI 手动输入的每一行也是一个作用域"

# 声明非空数组
:local arr1 {"hello"; "world"}
:put [:pick $arr1 0]
# 声明空数组
:local arr2 [:toarray ""]
# 添加元素
:set $arr2 ($arr2, "hello")
:put $arr2
# 删除首元素
:set $arr2 [:pick $arr2 1 [:len $arr2]]

#字典
:local dict {a=1; b=2}
:put ($dict->"a")

# for 循环
:for i from=1 to=5 step=1 do={:put $i}
# 结果:1 2 3 4 5
# 注意:1. for 循环中的变量是局部变量,循环结束后会被销毁
# 2. for 循环中的 to 是闭区间,即包含 to 的值

# foreach 循环
:foreach i in={1;2;3} do={:put $i}

# while 循环
:do {} while=();
:while () do={}

# if 判断
:if (true) do={:put "true"} else={:put "false"}

# 函数
:local myFunc do={:return "hello from function"}
:local result [$myFunc]
:put $result

# 异常处理并打 log
:do {:put [:resolve google.com];} on-error={:log info "damn it!"};

# 随机数
:put [:rndnum from=1 to=99];
# 随机字符串
:put [:rndnum from="abcdef%^&" length=33];

看到这里,你已经掌握了 RouterOS 脚本的基本语法。如果你曾经有过编程经验,那么应该能快速写出实现自己各种需求的脚本了。

奇技淫巧

熟练使用 RouterOS Script,可以实现很多有意思的功能。除了基本的计划任务(Scheduler),RouterOS 还在很多点位埋了 trigger 点位,当属性状态发生变化时可以

  1. 触发脚本:例如使用 fetch 调用外部的 HTTP API,实现更新 DDNS、备份、消息通知等功能
  2. 调整防火墙的 address-list:实现动态的黑、白名单控制

列举一些常用的点位:

  • DHCP Client:当 DHCP Client 状态变化,如 IP 地址刷新时触发。
  • DHCP Server:当一个地址租约(lease)被分配(assigned)或释放(released/de-assigned)时触发脚本。
  • Netwatch:根据 target IP 的在线情况来触发,分为 up 和 down 两种状态。

下面分享一些我觉得不错的使用场景。

端口敲门 Port Knocking

在互联网上直接暴露关键服务的端口,例如 Winbox,会有很高的风险。端口敲门是种 stateful 的保护机制,只有在特定的端口顺序被连接后,才会允许访问关键服务的端口。例如,只有在 1234、5678、9012 这三个端口被敲击后,才会允许访问 Winbox 的 8291 端口。

端口敲门的一种特例是单包授权(Single Packet Authorization),只需要发送一个特定的 UDP 或 TCP 包,就可以修改 RouterOS 的 address-list,从而允许访问关键服务的端口。例如,只有在 1234 端口收到特定的 UDP 包后,才会允许访问 Winbox 的 8291 端口。

1
2
3
4
# 将来自 WAN 的敲门的源 IP 加入白名单,过期时间 30 分钟
/ip firewall mangle add chain=input in-interface-list=WAN protocol=tcp dst-port=1234 action=add-src-to-address-list address-list=login address-list-timeout=30m
# 丢弃来自 WAN 未敲门的源 IP 的访问
/ip firewall filter add chain=input in-interface-list=WAN protocol=tcp dst-port=8291 src-address-list=!login action=drop

如果想实现多个敲门端口,可以建立多个 address-list,然后在后续 filter 规则中 match 前序 address-list,例如:

1
2
3
/ip firewall mangle add chain=input in-interface-list=WAN protocol=tcp dst-port=1234 action=add-src-to-address-list address-list=login-candidate address-list-timeout=30m
/ip firewall mangle add chain=input in-interface-list=WAN src-address-list=login-candidate protocol=tcp dst-port=2345 action=add-src-to-address-list address-list=login-trusted address-list-timeout=30m
/ip firewall filter add chain=input in-interface-list=WAN protocol=tcp dst-port=8291 src-address-list=!login-trusted action=drop

注意:最好采用随机端口作为敲门序列,不要使用单调递增或递减的端口号,否则可能会被端口扫描器触发。

参考资料

Fail2ban

MikroTik Fail2Ban

Firewall/Filtercontent 字段识别 Winbox 和网页登录失败的内容,然后使用两个 address-list 依次记录失败的 IP,并最终将其加入黑名单。原理和端口敲门是完全一致的。
对于 SSH 爆破,这里使用的是 SSH 验证时的固定报文长度。

妻子检测器 Wife Detector

MikroTik Telegram bot - wife detector

MikroTik 官方整的活,使用计划任务轮询 Wi-Fi 接口的 registeration-table,如果发现妻子手机的 MAC 地址出现,就通过 Telegram Bot API 发送消息。

1
/tool fetch mode=https http-method=get url="https://api.telegram.org/bot[token]/sendMessage&chat_id=[chat_id]&text=[text]" keep-result=no

车辆追踪

Mobile Wi-Fi with GPS Tracking using MikroTik’s LtAP

集成了 GPS 和 LTE 的 MikroTik LtAP 使用计划任务上报坐标信息。
注意:中国大陆的地图服务使用了 GCJ-02 坐标系,又称为火星坐标系,需要将通过 GPS 获取的 WGS-84 坐标转换为 GCJ-02 坐标,否则会有几百米的偏移。

Wi-Fi 近场认证

家用无线路由器一般有个 WPS 按钮,按下后就可以为位于 WPS 配对模式下的终端授权接入,无需输入密码。在 hAP ac2 等产品上也可以找到这个物理按键。有没有更便捷的方案,无需用户执行额外操作,就能实现 Wi-Fi 授权呢?

我们可以借助终端的 RSSI 信号强度来判断其是否在一定物理距离内,从而实现 Wi-Fi 近场认证。具体思路如下:

  1. 开放一个无密码的 SSID,位于无网络权限的不可信 VLAN,使用 access-list 限制只有信号强度大于 -30dB 的终端才能鉴权。
  2. 使用计划任务轮询 registration-table,如果发现新的终端接入,就将其 MAC 地址加入可信的 VLAN。

Wi-Fi 近场认证仅适用于访客和公共场所的防长期蹭网场景,对于家庭网络,建议使用 WPA2/WPA3 PSK。

关于本方案能否用于异构的无线网络,即使用 MikroTik 的 AP 做认证,用其他厂商(如 Ruckus 和 Aruba)的 AP 做接入,我没有测试过,但是理论上是可行的。实现本方案需要满足以下条件:

  1. 可信与不可信的网络使用同一个 ESSID,启用 VLAN 隔离。如今的 Windows、iOS 和 Android 终端出于隐私考虑,默认使用随机 MAC 地址实现 probing 和联网,以避免跨网络跟踪。因此,如果可信和不可信的网络采用了不同 ESSID,客户端会无法匹配。
  2. RSSI 阈值(是一个负数)需要根据实际情况调整,以保证可信终端能够正常接入,同时防止不可信终端蹭网。RSSI 阈值过大,会导致可信终端无法接入;RSSI 阈值过小,会导致不可信终端接入。
  3. 客户端必须连接信号最强的 BSSID(即 AP),否则 RSSI 会不准确。如果场所内同时存在 2.4G 和 5G 两个频段的 Wi-Fi,但却使用了低端的 2.4G only 的 AP 作为认证 AP,那么客户端可能只会尝试连接 5G 的 AP,导致验证失败。

access-list 可以为不同接口单独设置,包括 CAP 的远程无线接口在内。因此可以为场所内不同位置的 AP 配置不同的 RSSI 阈值。

自动备份

RouterOS配置邮箱并自动发送备份

使用 /system backup 备份,使用 /tool e-mail 以邮件形式发送。

云平台管理

  • 思路:使用计划任务,轮询 API,执行返回的脚本
  • 鉴权:将密码等敏感信息作为 POST 请求的 body
1
2
3
:local url "https://api.example.com"
:local token "token"
:do {:execute ([/tool fetch url="$url" http-data="$token" http-method=post check-certificate=yes output=user as-value]->"data")} on-error={} else={}

每个下发的任务需要有一个独立的 id(UUID 或自增整数),当 RouterOS 执行完成或者失败后,需要将结果与任务 id 回传,也就是 Callback。

RouterOS + Telegram Bot

MikroTik 官方频道发布的手把手教学,基于 Druvis-Timma/Mikrotik

实现思路和云平台管理一样,使用 while 循环每 5 秒轮询 Telegram Bot 的 getUpdates 接口,执行特定 userID 的命令,然后使用 sendMessage 接口上报执行结果。

对于用户发送的命令,这个项目没有直接运行,而是调用了 /system/ssh-exec 执行。其实是为了获取执行结果,因为 /system/script/run 只能返回执行是否成功,而不能返回执行结果。不过在新版本的 RouterOS 中提供了 :execute 函数,可以后台获取代码并并存储结果至文件或变量。

1
:local fun ([:system ssh-exec 0.0.0.0 $command as-value]->"output");

限时 Wi-Fi 接入

将连接时间超过 1h 的终端加入黑名单。

1
2
3
4
# 拉黑
:foreach mac in=[get [/caps-man/registration-table/find uptime>1h0m] mac] do={/caps-man/access-list add action=reject mac-address-mask=$mac comment="overtime"}
# 清空黑名单
/caps-man/access-list/remove [find action=reject comment="overtime"]

中文 SSID

RouterOS 的 CLI、Web 和 Console 只允许输入 ASCII 字符,无法直接输入中文字符。但是可以接受按 Byte 输入的 UTF-8 hex 字符串,因此可以使用 Python 或第三方工具生成 SSID。

1
2
3
4
import re
ssid = '中文'
with open('ssid.txt', 'w') as f:
f.write(re.sub(r'(..)', r'\\\1', ssid.encode('utf-8').hex().upper()))
1
/interface wireless set [find name="wlan1"] ssid="\e4\b8\ad\e6\96\87"

RouterOS 可以为物理无线接口(master)添加 Virtual AP(slave)接口,实现广播多个 SSID。但是 SSID 以较低速率广播,会降低整个无线网络空口的吞吐量。Andrew von Nagy 制作了一张表,展示了不同 SSID 数量对吞吐量的影响。

免 DDNS 实现动态更新 VPN Endpoint IP

两地机器使用 Site-to-Site VPN 互联,与协议无关,可以是 IPSec、IPIP、VXLAN 中的任何一种。DDNS 的实时性是很差的,因为 DNS 的本质是递归解析,而各级 DNS 服务器都有缓存,且不一定遵守 TTL。除此之外,使用 DDNS 还会将具体的 IP 与域名关联,有隐私风险。如果用户场景比较简单,只有这一路特定协议的 VPN,那么可以巧妙利用防火墙的 address-list 功能,实现动态更新对端 IP。具体思路如下:

  1. 使用防火墙的 filter 规则,将收到的特定协议的流量的源 IP 加入 address-list,并将其标记为 endpointCandidate,并设置超时时间。
  2. 使用 Netwatch 监控接口状态,一旦接口 down 掉,就修改接口的 remote-addressendpointCandidate
1
2
3
:local endpointCandidate [/ipv6/firewall/address-list/get [find list=endpointCandidate] address]
:set $endpointCandidate [:pick $endpointCandidate 0 ([:len $endpointCandidate]-4)]
/interface/ipipv6/set ipipv6-home remote-address=$endpointCandidate

注意:从防火墙 address-list 中获取的 IP 是带有 CIDR 后缀的,需要使用 :pick 去掉后缀。

不过即使这样,也会存在短暂的业务中断。若想追求高可用,需要建立至少两条 tunnel,为每条 tunnel 按优先级配置 distance,并使用 check-gateway 选项,实现自动切换。