如果想从外网访问家里的树莓派,除了家里要有外网 IP 外,还需要配置动态域名解析。
首先是外网 IP,这个必须要有。否则不能采用直连的方案,也就不能用动态域名解析的方案了。
但这不代表外网一定访问不了。比如花生壳穿透,ngrok 等之类内网穿透软件,还是可以实现的。不过穿透方案,有很大局限性,比如 gnrok 国内环境不稳定,还有各种收费等。
本文就单说有外网 IP,但是会经常变化的情况。
使用光猫、路由实现
如果光猫、路由支持 DDNS(动态域名解析),可以考虑直接使用。
我家里联通光猫支持 TZO、Oray、DynDNS,而网件路由支持 NetGear、NO-IP、3322、Oray、DynDNS。
反正我都不熟悉,或者有的根本就是收费服务。
使用支持 API 的域名解析服务商
首先要明确一点,基本上支持域名解析 API 调用的服务商,都支持类似 https://api.xxx.com/ddns?token=xxx&ip=xxx
这种通过连接形式修改的方法。而且上述光猫、路由中,可能还会支持自定义服务商,相当于定义好相关字段,当外网 IP 改变后,自动实现调用。
我的域名托管在阿里云。也有 API 调用的方法。方法思路也一样,定时检测自己的 IP 地址,发现阿里云填写的地址不同,则重新变更下域名解析就行了。
使用阿里云 DDNS API 实现动态域名解析
创建账号,获取 KEY
首先,要创建一个新账号,并开通 API 相应权限权限。
到阿里云 RAM 访问控制
中,新建账号,权限的话,分配 AliyunDNSFullAccess
就够了。之后会得到 AccessKeyId、accessKeySecret。留好备用。
Node 官方实现
参考文档:https://help.aliyun.com/document_detail/124923.html?spm=a2c4g.11186623.6.621.5ad930b1uJsGI4#title-fbv-si0-ict
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
| const Core = require('@alicloud/pop-core');
var client = new Core({ accessKeyId: '<accessKeyId>', accessKeySecret: '<accessSecret>', endpoint: 'https://alidns.aliyuncs.com', apiVersion: '2015-01-09' });
var params = { "DomainName": "dns-example.com", "RR": "apitest1", "Type": "A", "Value": "3.0.3.0" }
var requestOption = { method: 'POST' };
client.request('AddDomainRecord', params, requestOption).then((result) => { console.log(JSON.stringify(result)); }, (ex) => { console.log(ex); })
|
Node 我的实现
参考代码:https://github.com/yukapril/network-service/blob/master/app/service/net.js
我用的是 eggjs,配置好了定时任务。以下代码为核心逻辑:
首先是完成查询自己的 IP,我用的 ip-api 的接口:
1 2 3 4 5 6 7 8 9 10 11 12
| function myIp () { const { ctx } = this return new Promise(async (resolve, reject) => { try { const result = await ctx.curl('http://ip-api.com/json', { dataType: 'json' }) const json = result.data resolve(json) } catch (e) { reject(e) } }) }
|
再写一个查询阿里云当前 DNS 配置的方法。通过此方法,我们可以查到指定子域名的相关数据。后续更新的时候要用到的:
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
| const Core = require('@alicloud/pop-core')
const getDnsRecord = (subdomain) => { const client = new Core({ accessKeyId: 'xxx', accessKeySecret: 'xxx', endpoint: 'https://alidns.aliyuncs.com', apiVersion: '2015-01-09' })
const requestOption = { method: 'POST' }
const params = { RegionId: 'cn-hangzhou', DomainName: 'abc.com', PageSize: 500 }
return new Promise(async (resolve, reject) => { try { const result = await client.request('DescribeDomainRecords', params, requestOption) const record = result.DomainRecords.Record.filter(item => { return item.RR === subdomain })[0] resolve(record) } catch (ex) { reject(ex) } }) }
|
如果发现当前 IP 和阿里云中查询的 IP 不同,则需要更新。写一个更新方法:
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
| const Core = require('@alicloud/pop-core')
const updateDnsRecord = (recordId, rr, ip) => { const client = new Core({ accessKeyId: 'xxx', accessKeySecret: 'xxx', endpoint: 'https://alidns.aliyuncs.com', apiVersion: '2015-01-09' }) const requestOption = { method: 'POST' }
const params = { RegionId: 'cn-hangzhou', RecordId: recordId, RR: rr, Type: 'A', Value: ip }
return new Promise(async (resolve, reject) => { try { const result = await client.request('UpdateDomainRecord', params, requestOption) resolve() } catch (ex) { reject(ex) } }) }
|
最后,对以上的方法,进行组合:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const rr = 'test'
const ipData = await myIp() const ip = ipData.query
const record = await getDnsRecord(rr)
if(record.Value === ip) return
await updateDnsRecord(record.RecordId, rr, ip)
|
整体写完后,其实也很简单的嘛~没有那么复杂。
–END–