0、背景介绍

家里的宽带有动态公网IP,需要DDNS服务实现域名动态解析。

常规方式通过路由器自带的DDNS服务(例如花生壳)就可以了。这种方式要求WAN口必须是公网IP,如果你是用光猫拨号,然后无线路由器连接在光猫上,这种情况你需要在光猫上配置DDNS,而不是你的无线路由器。

如果想要使用自己的域名,可以通过CNAME解析的方式,将自定义域名解析到DDNS提供商提供的域名。

所以此篇文章针对使用RouterOS,但是并不是用RouterOS拨号的情况下DDNS的更新。以我自己为例,家里有多个无线路由器,但是我的RouterOS在我的房间,所以只能使用光猫拨号的方法,所有路由器连接在光猫上。然后将我的RouterOS设置为DMZ主机。这样所有公网的流量都会转发到我的ReouterOS,也就相当于拥有了公网IP。

1、配置思路

最开始,我计划使用在RouterOS较新的版本中,提供的一个Cloud功能,位于/ip cloud,但是这个功能似乎需要正版授权版本(我在使用的是试用版),因此不了了之。

而后我又尝试使用/tool dns-update命令更新解析记录,但是反复实验了很多次,更换了很多参数,都没有成功。最后也放弃了。

最终决定使用这个服务提供商:ChangeIP,它提供了HTTP/HTTPS接口,可以直接通过RouterOS的脚本发送请求。

2、RouterOS脚本

/system scripts添加一个脚本,名称我定义为check-and-update-ddns

ddnshost对应申请的免费DDNS域名,ddnsuser和ddnspass分别是ChangeIP的用户名和密码。interval表示检测本机公网IP间隔。

这个脚本在运行时,会判断此次检测距离上次检测是否大于3分钟,如果不是就终止此次运行,避免频繁更新。

如果大于3分钟,继续判断此次检测IP和上次检测结果是否一致,一致就无需更新,不一致则将新IP解析记录发送给ChangeIP的接口。

点击运行脚本,测试是否能够完成DNS解析记录的更新。

/systems script add name=au source={脚本内容见下}

脚本内容:

:local ddnshost "example.dynamic-dns.net"
:local ddnsuser "username@mail.com"
:local ddnspass "password"
:local interval [:totime 180s]

:global ddnscheckpoint
:local currenttime [/system clock get time]
:if ([:typeof $ddnscheckpoint] = "time") do={
    :log info "DDNS: Last check was $ddnscheckpoint"
} else={
    :log info "DDNS: Cannot determine checkpoint, set now."
    :global ddnscheckpoint ( $currenttime - 1d )
}

# Get the current IP
:if ($currenttime - $ddnscheckpoint > $interval || $currenttime - $ddnscheckpoint < [:totime 0s]) do={
    :log info "DDNS: Performing remote IP detection."
    /tool fetch address="ip.changeip.com" host="ip.changeip.com" src-path=("/?" . [/int eth get 0 mac-address ]) dst-path="ip.changeip.com.txt" mode=http port=80
    :global ddnscheckpoint $currenttime
} else={
    :log info "DDNS: Please be considerate and wait a few seconds longer."
    :break
}

# Parse the IP address received from fetch script.
:global ddnslastip
:local html [/file get "ip.changeip.com.txt" contents]
:local ddnsip [:pick $html ([:find $html "<!--IPADDR="] + 11) [:find $html "-->"] ]

# Is it a valid IP and is it different than the last one?
:if ([:typeof [:toip $ddnsip]] = "ip" AND $ddnsip != $ddnslastip ) do={
    :log info "DDNS: Sending UPDATE with $ddnsip"
    :local ddnsurl ("https://nic.changeip.com/nic/update\3Fip=" . $ddnsip . "&hostname=" . $ddnshost)
    /tool fetch url=$ddnsurl user=$ddnsuser password=$ddnspass dst-path=ddns-res.txt
    :global ddnslastip $ddnsip
    :local resultcode [:pick [/file get ddns-res.txt contents] 0 3]
    :if ($resultcode = "200") do={
        :log info "DDNS: Updated with $ddnsip"
    } else={
        :log info "DDNS: Update with $ddnsip failed"
    }
} else={
    :log info "DDNS: No update required."
}

3、RouterOS定时任务

编写完成上述脚本之后,还需要定期执行这个脚本。添加一个调度任务,每3分钟执行一次脚本。

/system/schedeuler add name=auto-update-ddns \
on-event=check-and-update-ddns \
interval=3m \
comment="check ip and update ddns every 3min"

4、后记

DDNS实现方式太多,使用RouterOS来进行这个操作的一个考虑是作为网关,它很稳定。

另外给大家的建议还有阿里云的CLI,也有封装好的Docker镜像,可以在群晖中使用。

我目前也在这个Docker镜像,但是存在的一个问题是,群晖位于OpenWrt的代理后,有时候获取到的不是我家里的IP,而是代理服务器的IP。现在使用RouterOS脚本就完全没有这种苦恼了。

另外使用计划任务也没有必要检测执行时间。可以精简一下:

:local ddnshost "example.dynamic-dns.net"
:local ddnsuser "username"
:local ddnspass "password"

# Get the current IP
:log info "DDNS: Performing remote IP detection"
/tool fetch address="ip.changeip.com" host="ip.changeip.com" src-path=("/?" . [/int eth get 0 mac-address ]) dst-path="ip.changeip.com.txt" mode=http port=80

# Parse the IP address received from fetch script.
:global ddnslastip
:local html [/file get "ip.changeip.com.txt" contents]
:local ddnsip [:pick $html ([:find $html "<!--IPADDR="] + 11) [:find $html "-->"] ]

# Is it a valid IP and is it different than the last one?
:if ([:typeof [:toip $ddnsip]] = "ip" AND $ddnsip != $ddnslastip ) do={
    :log info "DDNS: Sending UPDATE with $ddnsip"
    :local ddnsurl ("https://nic.changeip.com/nic/update\3Fip=" . $ddnsip . "&hostname=" . $ddnshost)
    /tool fetch url=$ddnsurl user=$ddnsuser password=$ddnspass dst-path=ddns-res.txt
    :global ddnslastip $ddnsip
    :local resultcode [:pick [/file get ddns-res.txt contents] 0 3]
    :if ($resultcode = "200") do={
        :log info "DDNS: Updated with $ddnsip"
    } else={
        :log info "DDNS: Update with $ddnsip failed"
    }
} else={
    :log info "DDNS: No update required."
}
最后修改:2024 年 02 月 27 日
如果觉得我的文章对你有用,请随意赞赏