家里有一台使用矿机星际蜗牛搭建的NAS,主路由使用的是Mikrotik RB750GR3,NAS是直接连在主路由LAN口上,现在各大运营商已经不给IPV4,好在电信还能给公网的IPV6,虽然是动态,但好歹也提供了一个公网访问的途径。
前提
路由器配置
防火墙配置
RouterOS的IPV6防火墙默认情况下是会拦截公网进来的流量的。因此需要先建个入站规则。在ipv6->Firewall
菜单下,新建一条Rule:
dst-address
填你NAS获取到的公网ipv6地址,一般是240开头。

注意,这条Rule一定要放在最后一条drop上面,建议comment配置为一个固定值,后续脚本会用。
这条配完后,使用手机4/5G网络应该就可以ping通NAS了。
但是NAS的ipv6是会变的,所以要写脚本动态更新这条Rule
当然也可以把防火墙直接开了,但是这样相当于子网下的所有ipv6设备都暴露在外网了,可能会有安全隐患,不建议ipv6防火墙全开。
脚本配置
因为Router自带的fetch工具实在是太弱了,完全满足不了各大域名厂商复杂的API接口,因为需要在NAS上写一个HTTP服务来完成DDNS,下面是SpringBoot Controller代码:
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
| @RequestMapping("/ddns") fun ddns(@RequestParam(name = "ipv6") ipv6: String, @RequestParam(name = "rr") rr: String?): String { log.info("update ddns:ipv6=$ipv6,rr=$rr") val noSuffixIpv6 = ipv6.split("/")[0] runCatching { val config = Config() .setAccessKeyId("***") .setAccessKeySecret("***") config.endpoint = "alidns.cn-hangzhou.aliyuncs.com" val client = Client(config) val response = client.describeDomainRecords(DescribeDomainRecordsRequest().apply { domainName = "yourdomain.top" }) for (domain in response.getBody().domainRecords.record) { val domainRR = domain.rr if (rr?.contains(domainRR) == true) if (noSuffixIpv6 != domain.value) { val request = UpdateDomainRecordRequest() request.recordId = domain.recordId request.value = noSuffixIpv6 request.type = domain.type request.rr = domain.rr request.priority = domain.priority request.ttl = domain.ttl request.line = domain.line client.updateDomainRecord(request) log.info("update ddns:${rr}.yourdomain.top ip ${domain.value} ->$noSuffixIpv6") } else { log.info("本次监听外网IP没有变更!"); } } return "ok" }.onFailure { log.error("update ddns failure", it) var msg = "获取到外网IP:$ipv6\n更新过程中出现异常!\n" val w = StringWriter() it.printStackTrace(PrintWriter(w)) msg += w.toString() msg = msg.replace("\n", "<br/>") MailHelper.sendTextMail("DDNS更新失败!", content = msg) } return "failure" }
|
其实只需要实现一个能动态更新域名厂商的http服务就行,不管是什么语言写的都可以。
下面是RouterOS脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| { :global ddnsflag; :local a [/ipv6 pool get [find name=ipv6] prefix]; :local suffix [:pick $a 0 ([:len $a]-4)]; :local rs "$(suffix)211:32ff:fe11:2233/128"; /ipv6 firewall filter; :local n [find comment ~ "nas"]; :local b [get number=$n dst-address]; :if (rs!=b) do={ :global ddnsflag true; } :if (ddnsflag!=false) do={ /ipv6 firewall filter set numbers=([find comment ~ "nas"]) dst-address=$rs; :log warning message="new nas ip $(rs)"; :local result [/tool fetch url="http://192.168.1.22:8080/cloud/debug/ddns?ipv6=$(rs)&rr=xxxx" as-value output=user]; :if ($result->"status" = "finished") do={ :if ($result->"data" = "\"ok\"") do={ :log warning message="update ddns success"; :global ddnsflag false; } } } }
|
其中第四行后面的4位需要根据NAS上已经获取到的地址同步更改,原理是ipv6有个EUI-64规定,可以根据mac来生成后面,因此动态ipv6变化的只是前64位,后64位是固定的,因此这里直接截取前面的,然后拼起来就得到了NAS的地址。我们可以在IPv6 Address选项下看到该配置。

脚本会同步修改Firewall
,实现方案是先用comment
找到那条Rule,然后再重写设置Dst Address
。可能有其他简单办法,但是这个方法工作也挺OK的。
其中fetch
的地址修改成你自己写的服务地址,把获取到的ipv6和想更新的rr传过去即可,然后具体更新逻辑都是在http服务里实现的。更新成功返回ok
,脚本会更新标志位,下次地址变化之前不会重写调用服务。
设置脚本定期运行
很简单,在System->Scheduler
建个周期任务就行了,每分钟运行一次。
最后
这个方案还有一个优点,IPv6一定是正确的,如果NAS通过自己获取自己的IPv6的话,有可能会获取到很多个IPv6地址,而不知道哪个是正确的。