6

Node.js 实现阿里云域名的动态解析

 2 years ago
source link: https://hpdell.github.io/%E7%BC%96%E7%A8%8B/nodejs-ddns/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Node.js 实现阿里云域名的动态解析

家用宽带申请公网 IP 往往是动态的公网 IP ,会经常变动,只能借助域名和 DDNS 使服务器可以随时被访问。 使用 Node.js 做 DDNS 也很简单,借助阿里云的 OpenAPI Explorer 可以做一个简单实现。

在线查找域名解析记录的 RecordID

这步没有必要每次都用代码实现,因为域名解析记录的 ID 在修改的过程中是不会发生变化的。 所以直接通过 OpenAPI Explorer 来在线查询 RecordID 即可。

首先在 OpenAPI Explorer 中选择“云解析”的接口,找到 DescribeDomainRecords 接口,填入指定的参数, 点击“发起调用”,即可看到所有的域名解析记录。

参数栏中, DomainName 一处填写要修改解析记录的域名。右侧找到要修改的域名记录,记下 RecordID

动态修改域名解析记录

这里需要借助以下三个接口:

淘宝的接口用 Axios 库调用,阿里云的两个接口可以通过阿里云提供的 SDK 调用。

包的引入和对象声明

这部分引入包,并声明一些变量,以便后面调用。

参数含义newIP新的 IP 地址client阿里云 SDK 对象describeDomainRecordInfoParams调用 DescribeDomainRecordInfo 接口的参数updateDomainRecordParams调用 UpdateDomainRecord 接口的参数requestOption阿里云 SDK 请求配置
const Core = require('@alicloud/pop-core');
const axios = require('axios').default;
const moment = require('moment');

var newIP = "127.0.0.1";

var client = new Core({
accessKeyId: '<accessKeyId>',
accessKeySecret: '<accessKeySecret>',
endpoint: 'https://alidns.aliyuncs.com',
apiVersion: '2015-01-09'
});

var describeDomainRecordInfoParams = {
"RecordId": "<RecordId>"
};

var updateDomainRecordParams = {
"RecordId": "<RecordId>",
"RR": "@",
"Type": "A"
}

var requestOption = {
method: 'POST'
}

查询当前公网 IP 地址

调用淘宝接口进行查询,结果用正则表达式进行匹配。

axios.get("http://www.taobao.com/help/getip.php?t=" + moment().format("x")).then((response) => {
if (response.data) {
var matched = /(\d{1,3}\.){3}\d{1,3}/.exec(response.data);
if (matched && matched.length) {
newIP = matched[0];
}
// ......
}).catch((reason) => {
console.error("Get public IPv4 error:", reason);
process.exit(1);
});

匹配IP地址的正则表达式的写法不止 /(\d{1,3}\.){3}\d{1,3}/ 这一种。 可以用更简化但匹配范围更广的写法,或用更严格的写法,只要达到目的即可。

查询域名解析记录中记录的 IP 地址

调用阿里云接口,将结果与当前公网 IP 进行对比。

client.request("DescribeDomainRecordInfo", describeDomainRecordInfoParams, requestOption).then((result) => {
var oldIP = result.Value;
// ......
}).catch((reason) => {
console.error("DescribeDomainRecordInfo error:", reason);
process.exit(1);
})

修改解析记录

如果当前公网 IP 发生了变化,那么调用接口修改解析记录。

if (oldIP != newIP) {
updateDomainRecordParams = {
...updateDomainRecordParams,
"Value": newIP
};
client.request("UpdateDomainRecord", updateDomainRecordParams, requestOption).then(() => {
process.exit(0);
}).catch((reason) => {
console.error("UpdateDomainRecord error:", reason);
process.exit(1);
});
}

修改成功可以做一个输出,也可以不要。

如果修改前后解析记录相同,则阿里云接口会返回一个字段 Message , 值为 The DNS record already exists 。但是对于整个过程没有什么影响,因此没有对此错误进行处理。 但是为了避免频繁调用 API ,还是应该在调用接口发现 IP 地址发生变化之后再调用修改解析记录的接口。

const Core = require('@alicloud/pop-core');
const axios = require('axios').default;
const moment = require('moment');

var newIP = "127.0.0.1";

var client = new Core({
accessKeyId: '<accessKeyId>',
accessKeySecret: '<accessKeySecret>',
endpoint: 'https://alidns.aliyuncs.com',
apiVersion: '2015-01-09'
});

var describeDomainRecordInfoParams = {
"RecordId": "<RecordId>"
};

var updateDomainRecordParams = {
"RecordId": "<RecordId>",
"RR": "@",
"Type": "A"
}

var requestOption = {
method: 'POST'
}

axios.get("http://www.taobao.com/help/getip.php?t=" + moment().format("x")).then((response) => {
if (response.data) {
var matched = /(\d{1,3}\.){3}\d{1,3}/.exec(response.data);
if (matched && matched.length) {
newIP = matched[0];
}

client.request("DescribeDomainRecordInfo", describeDomainRecordInfoParams, requestOption).then((result) => {
var oldIP = result.Value;
if (oldIP != newIP) {
updateDomainRecordParams = {
...updateDomainRecordParams,
"Value": newIP
};
client.request("UpdateDomainRecord", updateDomainRecordParams, requestOption).then(() => {
process.exit(0);
}).catch((reason) => {
console.error("UpdateDomainRecord error:", reason);
process.exit(1);
});
}
}).catch((reason) => {
console.error("DescribeDomainRecordInfo error:", reason);
process.exit(1);
})
}
}).catch((reason) => {
console.error("Get public IPv4 error:", reason);
process.exit(1);
});

基于 Docker 服务化

在威联通等一些 NAS 系统上,系统本身提供了获取 IP 地址的功能,而且支持向 DDNS 服务器发送请求以更改 IP 地址。 一些路由器固件中已经内置了修改阿里云 DNS 解析记录的功能,但是威联通自带的 DDNS 并不支持直接修改阿里云 DNS。 但是威联通支持通过自定义 DDNS 请求,请求中可通过 %IP% 字符串代替当前 IP 地址,例如这样的一个请求:

https://ddns.ddd.qnap.net/?hostname=%HOST%&username=%USER%&password=%PASS%&IP=%IP%

因此我们可以将上述 DDNS 代码通过构建 Docker 镜像发布成服务,使威联通 NAS 通过该服务修改 DNS 解析记录。

服务接口实现

要实现一个网络服务接口,方法很多。因为对 Node.js 比较熟,笔者这里采用了 Express 服务器框架。 总体来说,只需要写一个路由即可。 阿里云 API 所需要的 ACCESSKEY_ID 和 ACCESSKEY_SECRET 通过环境变量进行设置。 为了支持多个域名的 DDNS 而不需要部署过多的 Docker 容器,RecordID、Type、RR、IP 等参数都从请求地址中获取。

// routes/ddns.js
const Core = require('@alicloud/pop-core');
const axios = require('axios').default;
const moment = require('moment');
const express = require("express");
var router = express.Router();

function updateDNS(ip, record, type, rr) {
var client = new Core({
accessKeyId: process.env.ACCESSKEY_ID,
accessKeySecret: process.env.ACCESSKEY_SECRET,
endpoint: 'https://alidns.aliyuncs.com',
apiVersion: '2015-01-09'
});
var describeDomainRecordInfoParams = {
"RecordId": record
};
var requestOption = {
method: "POST"
};
return new Promise((resolve, reject) => {
client.request("DescribeDomainRecordInfo", describeDomainRecordInfoParams, requestOption).then((result) => {
var oldIP = result.Value;
if (oldIP != ip) {
var updateDomainRecordParams = {
"RecordId": record,
"RR": rr,
"Type": type,
"Value": ip
};
client.request("UpdateDomainRecord", updateDomainRecordParams, requestOption).then(() => {
resolve("OK");
}).catch((reason) => {
reject("UpdateDomainRecord error: " + reason);
});
}
}).catch((reason) => {
reject("DescribeDomainRecordInfo error: " + reason);
});
});
}

router.get('/qnap', function (req, res) {
var query_keys = ["ip", "record", "type", "rr"];
var query_keys_check = query_keys.every(item => (item in req.query));
if (!query_keys_check) {
console.error("Missing:", query_keys.filter(item => !(item in req.query)).join(","));
res.sendStatus(500);
return;
}
console.log(req.query);
var newIP = req.query.ip;
var recordID = req.query.record;
var recordType = req.query.type;
var recordRR = req.query.rr;
updateDNS(newIP, recordID, recordType, recordRR).then((response) => {
console.log(response);
res.sendStatus(200);
}).catch((reason) => {
console.error(reason);
res.sendStatus(500);
});
})

module.exports = router;

当然这里也可以添加一个自动获取 IP 地址并更新 DNS 解析记录的接口。

实现服务器后,只需要写一个简单的 Dockerfile 即可。

FROM node:10

COPY . /srv
WORKDIR /srv
RUN npm install
EXPOSE 3000

CMD [ "node", "bin/www" ]

具体实现请参考 Github 仓库


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK