Mikrotik RouterOS 防火墙配置参考---通过 MAC 地址获取IPv6并更新防火墙设置

  在我将主路由改为RouterOS后,顺道开启了IPV6的直通访问,但是初始配置一直有一个坑没填完;那就是内网设备获取的V6地址如何在防火墙策略上自动更新,找了下有大神实现了在Route人OS上用脚本统一更新DDNS和防火墙策略,看着比较简洁,但是我个人倾向于网关设备最小化功能的需求,所以将DDNS服务还是部署在了内网发布服务的主机上,暂时还不完美凑活着先用吧。以后有时间再改
那先说这个脚本的需求:

  • 运行在RouterOS上,自动获取指定内网设备的IPv6地址并自动配置对应防火墙策略;具有自动更新、定时检查等功能;

1.开始之前

  RouterOS 脚本使用的是专为 MikroTik 路由器设计的特定脚本语言,旨在配置和自动化各种任务。这种语言与常规编程语言有所不同,其语法和功能集专为满足 RouterOS 的需求而设计。

在开始使用之前,了解基本语法是非常重要的。可以参考 Scripting - RouterOS 来获取详细信息。幸运的是,这种语法相对简单易懂。

2. 脚本思路及编写

  • 首先要获取到内网设备的IPv6地址;
  • 更新防火墙策略;
  • 定时检查及更新数据。

2.1 获取内网指定MAC地址机器的V6地址列表

通过查找文档找到了获取所有内网设备的IPv6地址的位置是:IPv6 -> Neighbors目录。参考命令如下

首先指定MAC地址

:local targetMac "11:22:33:44:55:66"

通过MAC地址筛选内网IPv6地址

:local ipAddressList [/ipv6 neighbor find mac-address=$targetMac]

以上,我们获取到指定设备的Neighbor Id,这是个数组,因为设备的IPv6可能是多个。

2.2 获取设备公网IPv6地址

由于公网IPv6地址的下发是基于运营商提供的IPv6前缀,重启路由器后,之前通过老前缀下发的公网IPv6地址并不会立即消失,而是会存活一段时间。在这种情况下,我们需要确保上报的是新前缀下发的地址,而不是已经不再使用的IPv6地址。

此外,路由器还会下发一个以 fe80 开头的内网地址(也可能是其他前缀)。因此,我们需要仔细甄别出正确的公网地址,以避免混淆。

获取下发的IPv6地址前缀

首先定义自己的需更新的拨号接口

:local wanInterface "pppoe-out1"
:local ipv6Prefix [/ipv6 dhcp-client get [find interface=$wanInterface status=bound] prefix]

获取IPv6 并截取前缀

:local cidrNetwork [:pick $ipv6Prefix 0 ([:find $ipv6Prefix "::" -1] + 1)]

循环遍历查找对应的ipv6地址

:foreach neighborId in=$ipAddressList do={
    # 获取邻居的IPv6地址和MAC地址
    :local ipv6Address [/ipv6 neighbor get $neighborId address]
    :local ipv6Status [/ipv6 neighbor get $neighborId status]
    

    :local foundPosition [:find $ipv6Address $cidrNetwork]

    :if ($foundPosition >= 0 && $ipv6Status = "reachable") do={
        :set ipv6Report $ipv6Address;
        :log info ("use: " . $ipv6Address)
    } else={
        :log info ("discard: " . $ipv6Address)
    }
}

到此,得到了内网设备IPv6的地址$ipv6Address;

}

3. 防火墙设置

  • 定义一个对应内网设备的Address Lists;使用Comment区分设备;
  • 设置了一个 forward 规则,并允许目标地址为 allow_device 的列表,action 为 accept 即为允许。
  • 更新内网指定comment的IPV6地址。
    参考代码如下:
# 自定义设备的注释
:local commentName "FNAS"

# 获取防火墙IPv6地址
:local firewallAddressList [/ipv6 firewall address-list find]


# 需要替换的防火墙列表序号
:local firewallAddressIdx ""

# 循环查找到需要替换的序号
:foreach idxId in=$firewallAddressList do={
    # 获取邻居的IPv6地址和MAC地址
    # :local ipv6Address [/ipv6 firewall address-list get $idxId address]
    :local comment [/ipv6 firewall address-list get $idxId comment]

    :if ($comment = $commentName && [:len $commentName] > 0 && [:len $comment] > 0) do={
        #:set firewallAddressIdx $idxId;
    }
}

通过以上步骤就能拿到我们刚刚配置的注释为 FNAS 的条目序号

获取更新地址时同时更新防火墙地址列表就行。


:if ([:len $firewallAddressIdx] > 0) do={
    # 更新防火墙列表
    /ipv6 firewall address-list set numbers=$firewallAddressIdx address=$ipv6Report
    :log info ("Firewall list has been updated: " . $commentName)
}

4.完整代码

####定义区,仅需在此配置下需要定义的内容######################
# 在这里替换成你需要的内网设备的MAC地址
:local targetMac "11:22:33:44:AA:BB"

# 定义自己的需更新的拨号接口
:local wanInterface "pppoe-out1"

# 自定义设备的注释
:local commentName "FNAS"

###### 分隔符,以下不需要做任何操作 ######

# 获取防火墙IPv6地址
:local firewallAddressList [/ipv6 firewall address-list find]

# 获取下发的IPv6地址前缀
:local ipv6Prefix [/ipv6 dhcp-client get [find interface=$wanInterface status=bound] prefix]

# 获取IPv6 并截取前缀
:local cidrNetwork [:pick $ipv6Prefix 0 ([:find $ipv6Prefix "::" -1] + 1)]

# 存储最终上报的IPv6地址
:local ipv6Report ""

# 查找指定MAC地址的设备
:local ipAddressList [/ipv6 neighbor find mac-address=$targetMac]

# 循环遍历查找对应的ipv6地址
:foreach neighborId in=$ipAddressList do={
    # 获取邻居的IPv6地址和MAC地址
    :local ipv6Address [/ipv6 neighbor get $neighborId address]
    :local ipv6Status [/ipv6 neighbor get $neighborId status]
    

    :local foundPosition [:find $ipv6Address $cidrNetwork]

    :if ($foundPosition >= 0 && $ipv6Status = "reachable") do={
        :set ipv6Report $ipv6Address;
        :log info ("use: " . $ipv6Address)
    } else={
        :log info ("discard: " . $ipv6Address)
    }
}


# 需要替换的防火墙列表序号
:local firewallAddressIdx ""

# 循环查找到需要替换的序号
:foreach idxId in=$firewallAddressList do={
    # 获取邻居的IPv6地址和MAC地址
    # :local ipv6Address [/ipv6 firewall address-list get $idxId address]
    :local comment [/ipv6 firewall address-list get $idxId comment]

    :if ($comment = $commentName && [:len $commentName] > 0 && [:len $comment] > 0) do={
        # 获取到序号
        :set firewallAddressIdx $idxId;
    }
}

# 更新ip地址
:if ([:len $firewallAddressIdx] > 0 && [:len $ipv6Report] > 0) do={
    # 更新防火墙列表
    /ipv6 firewall address-list set numbers=$firewallAddressIdx address=$ipv6Report
    :log info ("Firewall list has been updated: " . $commentName)
} else={
    :log warning "No valid IPv6 address found for updating the firewall list."
}