macOS 下使用CLI 启动和停止Wireguard GUI 配置

在macOS 下我一直使用ControlPlane 这个APP 来控制某个环境下的一些系统配置,比如到家就会自动mount 我的NAS 盘,外出就会自动静音诸如此类。长期以来我一直试图用它实现在离家的环境里自动连上家里的WireGuard 来实现内部互通。但是macOS 的WireGuard 图形界面与命令行并不会联动,当你使用wg-quick 命令去启动WireGuard 通道后,图形界面并不会跟随之改变状态。昨天忽然从macOS 的Settings/VPN 页面看到了图形界面的WireGuard 连接后,忽然意识到WireGuard 图形界面其实是调用macOS 的Network Extension 框架在工作。那么接下来就简单了:

启用和中断WireGuard 连接:

/usr/sbin/scutil --nc start "WG连接名"
/usr/sbin/scutil --nc stop "WG连接名"

而对于ControlPlane 来说,由于它只能运行某个命令并且不能带参数,所以需要单独建立2个脚本文件:
start-wg.sh

#!/bin/sh
/usr/sbin/scutil --nc start "WG连接名"

stop-wg.sh

#!/bin/sh
/usr/sbin/scutil --nc stop "WG连接名"

MikroTik RouterOS Port Knocking 安全配置教程

为什么需要 Port Knocking?

为了让你家里的私人服务端口、路由器端口暴露在公网后更加安全,不那么容易被扫描和入侵。简单点说通过RouterOS 防火墙功能里的一个小机制,实现只有按特定顺序对指定端口“敲门”后,才能访问服务。

第一步:创建敲门规则

# 第1次敲门 – 监听端口10008

/ip firewall filter add chain=input action=add-src-to-address-list \
protocol=udp address-list=knock1 address-list-timeout=5s \
in-interface-list=WAN dst-port=10008 comment="1st knock"

这里建立了一个叫做knock1 的地址列表,当udp10008 端口被访问的时候,来源公网IP 会被记录,但只保存5秒。

# 第2次敲门 – 监听端口10002

/ip firewall filter add chain=input action=add-src-to-address-list \
protocol=udp src-address-list=knock1 address-list=knock2 \
address-list-timeout=12h in-interface-list=WAN dst-port=10002 \
comment="2nd knock"

这里建立了一个叫做knock2 的地址列表,当udp10002 端口被访问,同时来源公网IP 在knock1 列表里,那么就会被记录,这个记录会保存12小时。

第二步:配置访问控制

/ip firewall filter add chain=forward action=drop protocol=tcp \
src-address-list=!knock2 in-interface-list=WAN \
dst-port=22,8291,8728 comment="block non-knocked IPs"

除了knock2 列表里的地址访问敏感端口都会被拒绝。

这是一个比较简单的配置实现,为了更加安全可以增加更多的端口knock 来确保不会被暴力破解,访问控制那里也可以按照自己的实际情况更改。

客户端部分,macOS 和iOS 推荐PortKnock 这个小巧的APP 来触发Port Knocking。

RouterOS 的Hairpin 配置

先来解释下为什么需要Hairpin NAT(环回NAT)。你在自己的路由器上配置了DNAT(映射),让内部服务可以暴露到公网访问,但是当你自己在内网环境访问的时候就会发现访问出现了问题:

假设我们的环境是这样的:

  • 内网PC地址:192.168.88.10
  • 内网服务:192.168.88.40:38783
  • 路由器网关地址:192.168.88.1
  • 外网地址:180.159.19.232:38783 ==dnat== 192.168.88.40:38783

我们先来看下外网访问会发生什么:

  1. 路由器收到公网地址1.2.3.4 访问180.159.19.232:38783 的请求。(src=1.2.3.4:随机端口,dst=180.159.19.232:38783)
  2. 路由器匹配到DNAT 策略,于是将请求forward 给服务器,服务器收到请求(src=1.2.3.4:随机端口,dst=192.168.88.40:38783)
  3. 服务器响应请求回复。(src=192.168.88.40:38783,dst=1.2.3.4:随机端口)
  4. 路由器回程NAT,会匹配之前的请求进程,通过公网180.159.19.232:38783 回复给1.2.3.4:随机端口。

但是内网PC 请求就出现了问题:

  1. 路由器收到内网地址192.168.88.10 访问180.159.19.232:38783 的请求。(src=192.168.88.10:随机端口,dst=180.159.19.232:38783)
  2. 路由器看到请求的公网地址是自己的,于是将请求直接forward 给服务器,服务器收到请求(src=192.168.80.10:随机端口,dst=192.168.88.40:38783)
  3. 服务器响应请求回复。(src=192.168.88.40:38783,dst=192.168.88.10:随机端口)
  4. 于是192.168.88.10 收到了一个直接从192.168.88.40:38783 过来的数据包,和它之前请求的180.159.19.232:38783 不匹配,于是就被丢弃了。

为了解决RouterOS 这种处理方式,我们需要识别这类请求,将请求通过masquerade 伪装来实现来回路径的一致性:

添加一个脚本来获取当前的公网IP 地址,并更新到地址列表WANs 中:

/system script add name=update-public-ip source="
/tool fetch url=\"http://ifconfig.me/ip\" dst-path=pubip.txt;
:delay 5;
:local currentIP [/file get pubip.txt contents];
:put (\"Fetched Public IP: \" . \$currentIP);
/ip firewall address-list set [/ip firewall address-list find list=WANs] address=\$currentIP;
"

添加一个定时任务,每5分钟执行上面的脚本:

/system scheduler add interval=5m name=Schedule-Public-IP \
    on-event="/system script run update-public-ip"

添加Hairpin NAT Mangle规则,当内网地址访问WANs 列表里的地址(也就是公网地址)会对连接打上标记“hairpin”:

/ip firewall mangle add chain=prerouting place-before=0 \
    comment="Hairpin NAT" src-address=192.168.88.0/24 \
    dst-address-list=WANs action=mark-connection \
    new-connection-mark=hairpin

添加Hairpin NAT源地址转换,所有打上hairpin标签的连接会先进行一次原地址转换:

/ip firewall nat add chain=srcnat place-before=0 \
    comment="Hairpin NAT" connection-mark=hairpin \
    action=masquerade

最终整个请求:

  1. 路由器收到内网地址192.168.88.10 访问180.159.19.232:38783 的请求。(src=192.168.88.10:随机端口,dst=180.159.19.232:38783)
  2. 路由器匹配到了Mangle 策略,然后对这个请求进行了伪装masquerade,伪装后的请求forward 给服务器,服务器收到请求(src=192.168.88.1:随机端口,dst=192.168.88.40:38783)
  3. 服务器响应请求回复。(src=192.168.88.40:38783,dst=192.168.88.1:随机端口)
  4. 路由器根据之前的masquerade 表匹配到回程转给192.168.88.10,于是192.168.88.10收到了正确的包(src= 180.159.19.232:38783,dst=192.168.88.10:随机端口)

最后说明下这个实现可能和大部份网络材料不太一样,主要是增加了脚本获取公网IP 这部分。由于贵国的家庭网络环境复杂,路由器并不一定会直接配置公网IP,所以脚本的加入可以完全覆盖所有可能的情况(比如通过光猫的DMZ 配置来实现DNAT)。