一、前言:DNS和DDNS概念,需求场景
DNS(Domain Name System)域名解析系统,各大DNS服务商提供的dns解析,方便大家将朗朗上口的域名与公网ip绑定,让网站访问更加人性化;
普通DNS解析规则,无法时刻检测内网NAT映射的机器的公网ip变动,所以DDNS(Dynamic DNS)动态DNS解析应运而生,主要原理就是NAT内网vps机器通过代码获得自己当前映射的外网(公网)IP,然后通过各大DNS服务商提供的DNS 解析API接口,将当前公网IP传回DNS服务商,动态修改对应的域名解析IP,达到动态公网IP绑定域名效果,使得NAT小鸡也可以绑定域名建站。
二、腾讯云DNS为例,通过API接口创建DDNS服务
请求腾讯云API前,建议你按着官方文档申请安全凭证,参考:
腾讯云官方文档之API部分
申请时,建议新建一个专用的API访问子账号,如果你账号资产不重要,直接用你的主号来操作就行,此段可以略过
- A_1.首先登录API密钥页面:
API密钥页面
选择新建子账号
- A_2.这里我选择自定义创建:
- A_3.选择可访问资源类型:
- A_4.填写子账号用户细节
用户名,手机号,邮箱等信息,注意勾选“编程访问”方式
- A_5.授权子账号策略为完整的云资源授权
如果需要其他授权,可以搜索对应关键字快速寻找
授权:QCloudResourceFullAccess
或者留空,创建完毕后,再去
控制台用户预览
用户列表选择授权需要的权限也行
- A_6.最后确认用户细节和策略清单
如果没问题就提交下一步
- A_7.保存你的API Secret ID信息
我们在此页面可以拿到对应的API ID和KEY密钥相关信息,请务必妥善保存,如果怕遗忘可以点选发送到可靠邮箱备份。
创建完毕后在你的控制台访问管理>概览页面,标记着子账号登录信息备忘:
控制台概览页面
复制保存好你的API信息:
UserName AKxxxxxxxxxxxxx Qwxxxxxxxxxxxxxxx
我们再次参阅API接口之解析记录相关接口中关于修改解析记录的文档描述:
修改解析记录官方文档说明
可以尝试着使用域名API,达到修改域名解析目的:
- 节选公共请求参数示例:
https://cvm.api.qcloud.com/v2/index.php?
Action=DescribeInstances
&SecretId=AKIDkIcmkTqWqshHcghUhr8C1d5h4A3tA2kF
&Region=ap-guangzhou
&Timestamp=1465055529
&Nonce=59485
&Signature=mysignature
&SignatureMethod=HmacSHA256
&<接口请求参数>
注意两点:
- 参数严格按照字母排列顺序排序;
- 大小写敏感
那么我们的脚本第一步功能:获取解析记录可以这样写:
#!/bin/bash
domain='你的域名,例如qq.com'
subDomain='你希望解析的子域名,例如wx.qq.com,则只输入wx'
sId='你的云API秘钥SecretId'
sKey='你的云API秘钥SecretKey'
signatureMethod='HmacSHA1'#常用的HmacSHA256和HmascSHA1加密都可以的
timestamp=`date +%s`#当前Unix系统时间戳,不得与腾讯云服务器时间相差超过2小时
nonce=`head -200 /dev/urandom | cksum | cut -f2 -d" "`#随机正整数,配合时间戳配对,验证每次请求的身份,防止重放攻击,类似阅后即焚,防止截取请求数据再次发送到腾讯服务器获得返回的敏感参数。
region=bj
url="https://cns.api.qcloud.com/v2/index.php"
#获取域名解析条目ID:recordId
action='RecordList'
src=`printf "GETcns.api.qcloud.com/v2/index.php?Action=%s&Nonce=%s&Region=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s" $action $nonce $region $sId $signatureMethod $timestamp $domain`
#echo 'src: ' $src
signature=`echo -n $src|openssl dgst -sha1 -hmac $sKey -binary |base64`
#echo 'signature: ' $signature
params=`printf "Action=%s&domain=%s&Nonce=%s&Region=%s&SecretId=%s&Signature=%s&SignatureMethod=%s&Timestamp=%s" $action $domain $nonce $region $sId "$signature" $signatureMethod $timestamp `
#echo 'params: ' $params
curl -G -d "$params" --data-urlencode "Signature=$signature" "$url"
参考:https://blog.csdn.net/dragon2k/article/details/88016755
- 提供我的脚本范例供参阅:
#!/bin/bash
domain='你的域名'#查询功能,不需要设置二级域名
sId='AKIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
sKey='Qwxxxxxxxxxxxxxxxxxxxxxxxxxx'
signatureMethod='HmacSHA1'
timestamp=`date +%s`
nonce=`head -200 /dev/urandom | cksum | cut -f2 -d" "`
url="https://cns.api.qcloud.com/v2/index.php"
#获取域名解析条目ID:recordId
action='RecordList'
src=`printf "GETcns.api.qcloud.com/v2/index.php?Action=%s&Nonce=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s" $action $nonce $sId $signatureMethod $timestamp $domain`#公共请求参数中的region可以省略,不是cvm系统,域名没有region值
#echo 'src: ' $src
signature=`echo -n $src|openssl dgst -sha1 -hmac $sKey -binary |base64`
#echo 'signature: ' $signature
params=`printf "Action=%s&domain=%s&Nonce=%s&SecretId=%s&Signature=%s&SignatureMethod=%s&Timestamp=%s" $action $domain $nonce $sId "$signature" $signatureMethod $timestamp`
#echo 'params: ' $params
curl -G -d "$params" --data-urlencode "Signature=$signature" "$url"
vi test.sh
新建sh脚本文件,按i键编辑,粘贴,然后按esc
,输入:wq
保存,再通过bash test.sh
执行,获得返回数据,拿到你需要修改的域名解析条目对应的9位数字ID,保存后,接下来的脚本需要用到。 如果出现curl: (3) Illegal characters found in URL
代表你windows系统下保存的换行符是\n\r
,而linux系统下是\n
需要转换一下,需要将脚本里多余的\r
转义删除: tr -d '\r' < getRecord.sh > getRecordNew.sh
然后运行bash getRecordNew.sh
应该就可以了
第二个功能,修改域名解析记录,则可以这样:
#!/bin/bash
#/usr/bin/ddns
recordId='根据上面返回结果9位数字'
domain='你的域名,例如qq.com'
subDomain='你希望解析的子域名,例如wx.qq.com,则只输入wx'
sId='你的云API秘钥SecretId'
sKey='你的云API秘钥SecretKey'
signatureMethod='HmacSHA1'
timestamp=`date +%s`
nonce=`head -200 /dev/urandom | cksum | cut -f2 -d" "`#从urandom设备获取随机数,取前200位,整型输出后,取field2区块的五位数
#关于随机数产生有多种方式,可以参考https://blog.csdn.net/wy1550365215/article/details/77446501
region=bj
url="https://cns.api.qcloud.com/v2/index.php"
#获取ip
ip=`curl ip.sb`
action='RecordModify'
recordType='A'
recordLine='默认'
value=$ip
src=`printf "GETcns.api.qcloud.com/v2/index.php?Action=%s&Nonce=%s&Region=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s&recordId=%s&recordLine=%s&recordType=%s&subDomain=%s&value=%s" $action $nonce $region $sId $signatureMethod $timestamp $domain $recordId $recordLine $recordType $subDomain $value`
#echo 'src: ' $src
signature=`echo -n $src|openssl dgst -sha1 -hmac $sKey -binary |base64`
#echo 'signature: ' $signature
params=`printf "Action=%s&Nonce=%s&Region=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s&recordId=%s&recordLine=%s&recordType=%s&subDomain=%s&value=%s" $action $nonce $region $sId $signatureMethod $timestamp $domain $recordId $recordLine $recordType $subDomain $value`
#echo 'params: ' $params
curl -G -d "$params" --data-urlencode "Signature=$signature" "$url"
- 同时我提供下自己的范本供参考:
#!/bin/bash
#/usr/bin/ddns
recordId='525537912'
domain='lolimoe.ltd'
subDomain='ddns'
sId='AKxxxxxxxxxxxxxxxxxxx'
sKey='Qwxxxxxxxxxxxxxxxxxxx'
signatureMethod='HmacSHA1'
timestamp=`date +%s`
nonce=`head -200 /dev/urandom | cksum | cut -f2 -d" "`
region=bj
url="https://cns.api.qcloud.com/v2/index.php"
#获取ip
ip=`curl ip.sb`
action='RecordModify'
recordType='A'
recordLine='默认'
value=$ip
src=`printf "GETcns.api.qcloud.com/v2/index.php?Action=%s&Nonce=%s&Region=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s&recordId=%s&recordLine=%s&recordType=%s&subDomain=%s&value=%s" $action $nonce $region $sId $signatureMethod $timestamp $domain $recordId $recordLine $recordType $subDomain $value`
#echo 'src: ' $src
signature=`echo -n $src|openssl dgst -sha1 -hmac $sKey -binary |base64`
#echo 'signature: ' $signature
params=`printf "Action=%s&Nonce=%s&Region=%s&SecretId=%s&SignatureMethod=%s&Timestamp=%s&domain=%s&recordId=%s&recordLine=%s&recordType=%s&subDomain=%s&value=%s" $action $nonce $region $sId $signatureMethod $timestamp $domain $recordId $recordLine $recordType $subDomain $value`
#echo 'params: ' $params
curl -G -d "$params" --data-urlencode "Signature=$signature" "$url"
获取vps当前公网ip的网站:
dynamic_ip=`curl ip.sb`
参考自南晴浪大佬的Github脚本
同时还有其他网站可以使用,比如:
dynamic_ip=$(curl -s http://myip.dnsomatic.com/)
touch DDNS_TX.sh
,vi
编辑,i
编辑状态,粘贴,:wq
保存脚本以你喜欢的脚本命名,bash
执行一遍回到你的域名列表,查看是否修改正确,确定没问题了,再赋予脚本执行权限:
chmod +x DDNS_TX.sh
然后crontab -e
新建自动执行任务:
* * * * * /bin/bash /home/backup/DDNS_TX.sh >/dev/null 2>&1
每分钟执行一次检测,算是顺利完成了。
- 另外提供个python脚本,方便windows环境下ddns请求:
因为shell脚本在windows环境下编码问题,导致web请求加密中文字符不符合腾讯云的编码规则,会导致请求出错,没错,就是请求里,解析线路那条“默认”两个中文字符的编码问题,不太懂这块,暂不知道处理方案,所以拿来一份大佬的python脚本,可以顺利解决这个问题,windows环境下安装python环境还是挺简单的,实在不会的话可以参看我这篇文章目录2.b章节有提到:
好了,python脚本如下:
import hmac
import base64
from hashlib import sha1
from urllib.parse import urlencode, quote
from urllib import request
from datetime import datetime
import socket
import json
from urllib import request
IP = request.urlopen("https://api.ipify.org").read().decode('utf8')
def update_dns(IP):
ts = str(int(datetime.timestamp(datetime.now())))
# 填写你自己的 Id、Key 和域名
secretId = 'AKIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secretKey = 'Qwcwwwwwwwwwwwwwwwwww'
domain = 'unitymoe.com'
subDomain = 'ddns'
baseUrl = 'cns.api.qcloud.com/v2/index.php?'
# 请求的参数
para = {
'Action': 'RecordModify',
'Nonce': '22222',
'Region':'bj',
'SecretId': secretId,
'SignatureMethod':'HmacSHA1',
'Timestamp': ts,
'domain': domain,
'recordId': '648828796',
'recordLine': '默认',
'recordType': 'A', # "A","CNAME","MX","TXT","NS","AAAA","SRV"
'subDomain': subDomain,
'value': IP
}
sigSrcList = []
# urlencode 会对中文进行编码,导致验证失败,因此这里手动组装签名原始字符串
for k in para.keys():
sigSrcList.append(k + '=' + para[k])
sigSrcStr = 'GET' + baseUrl + '&'.join(sigSrcList)
para['Signature'] = base64.b64encode(hmac.new(secretKey.encode('utf-8'), sigSrcStr.encode('utf-8'), digestmod=sha1).digest())
url = 'https://' + baseUrl + urlencode(para, safe='')
response = request.urlopen(url)
print(response.read())
update_dns(IP)
三、以CloudFlare的DNS服务为例,通过API接口创建DDNS服务
如果是灰产大佬,只用国外域名和国外的DNS解析服务商的话,cloudflare算是一个典型代表,我们也做一个演示,让大家看看怎么利用cloudflare的API调用,做相应的DDNS检测脚本:
登录cloudflare,
如果没有账号的,可以参考这篇文章第六步注册一个免费的:
以下是CloudFlare API创建过程,感兴趣的可以点开查看:
- B_1.点击create tokens创建新的API
- B_2.取个喜欢的命名,选择必要的授权
ZONE范围,DNS解析项目,Edit编辑权限即可:
- B_3.最后确认下信息
- B_4.获得我们的API Key,保存好你的API key
Mnxxxxxxxxxxxxxxxxxxx
同时注意页面会给你一个形如下列的测试指令:
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer MnLX1qFiX-DMZD2nj1To2zW4Xupd4d9bgPNp5Dgn" \
-H "Content-Type:application/json"
粘贴到ssh执行看看返回数据,如果类似以下语句
{"code":10000,"message":"This API Token is valid and active"
则ok
创建完毕,点击COPY保存好
我们通过分析官方api说明,做如下测试:
curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=omo.moe" -H "X-Auth-Email:your@email.com" -H "X-Auth-Key:0302b8ed13acf6bdfdfddfdf980e9c4c" -H "Content-Type: application/json"
获取得到omo.moe域名zone区域的id
{"result":[{"id":"b0ddfbddfaaereaewafewfeafeddad","name":"omo.moe"
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/b0ddfbddfaaereaewafewfeafeddad/dns_records?type=A&name=hkt.omo.moe" -H "X-Auth-Email:your@email.com" -H "X-Auth-Key:0302b8ed13acf6bdfdfddfdf980e9c4c" -H "Content-Type: application/json"
将omo.moe 区域id带入请求,获取得到name:hkt.omo.moe对应的id
{"result":[{"id":"ede77fc01e85cddfdafdadddafdaf8542ba99","zone_id":"b0ddfbddfaaereaewafewfeafeddad","zone_name":"omo.moe","name":"hkt.omo.moe","type":"A","content":"114.114.114.114"
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/b0ddfbddfaaereaewafewfeafeddad/dns_records/ede77fc01e85cddfdafdadddafdaf8542ba99" -H "X-Auth-Email: your@email.com " -H "X-Auth-Key:0302b8ed13acf6bdfdfddfdf980e9c4c" -H "Content-Type: application/json" --data '{"type":"A","name":"hkt.omo.moe","content":"114.114.114.114","proxied":false}'
将zone id b0ddxxxx和name hkt.omo.moe对应的id:ede77xxxx拼接成新的请求,填入content栏为新的ip,示例114.114.114.114,获得回应如下:
{"result":{"id":"ede77fc01e85cddfdafdadddafdaf8542ba99","zone_id":"b0ddfbddfaaereaewafewfeafeddad","zone_name":"omo.moe","name":"hkt.omo.moe","type":"A","content":"114.114.114.114","proxiable":true,"proxied":false,"ttl":1,"locked":false,"meta":{"auto_added":false,"managed_by_apps":false,"managed_by_argo_tunnel":false,"source":"primary"},"created_on":"2021-05-07T09:37:11.998571Z","modified_on":"2021-05-07T10:13:28.733348Z"},"success":true,"errors":[],"messages":[]}
注意最后
"success":true,"errors":[],"messages":[]
表示成功,无错误
根据以上测试,可以构建ddns脚本如下:
#!/bin/bash
############### 授权信息(需修改成你自己的) ################
# CloudFlare 注册邮箱
auth_email="your@email.com"
# CloudFlare Global API Key
auth_key="0302b8ed13acf6bdfdfddfdf980e9c4c"
# 做 DDNS 的根域名
zone_name="omo.moe"
# 做 DDNS 的域名
record_name="hkt.omo.moe"
###################### 修改配置信息 #######################
# 域名类型,IPv4 为 A,IPv6 则是 AAAA
record_type="A"
# IPv6 检测服务
#ip=$(curl -s https://ipv6.vircloud.net)
# IPv4 检测服务
ip=$(curl -s ifconfig.me)
#如果无效,可以尝试替换为以下ip返回服务:
#curl ifconfig.me
#curl icanhazip.com
#curl ident.me
#curl ipecho.net/plain
#curl whatismyip.akamai.com
#curl tnx.nl/ip
#curl myip.dnsomatic.com
#curl ip.appspot.com
#curl -s checkip.dyndns.org | sed 's/.*IP Address: \([0-9\.]*\).*/\1/g'
# 变动前的公网 IP 保存位置
ip_file="ip.txt"
echo "your current vps IP is: "$ip
# 域名识别信息保存位置
id_file="cloudflare.ids"
# 监测日志保存位置
log_file="cloudflare.log"
###################### 监测日志格式 ########################
log() {
if [ "$1" ]; then
echo -e "[$(date)] - $1" $log_file
fi
}
log "Check Initiated"
###################### 判断 IP 是否变化 ####################
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ "$ip" == "$old_ip" ]; then
echo "IP has not changed."
exit 0
fi
fi
###################### 获取域名及授权 ######################
if [ -f $id_file ] && [ $(wc -l $id_file | cut -d " " -f 1) == 2 ]; then
zone_identifier=$(head -1 $id_file)
echo "Zone ID is" $zone_identifier
record_identifier=$(tail -1 $id_file)
echo "Record ID is" $record_identifier
else
zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )
record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1)
echo "$zone_identifier" > $id_file
echo "$record_identifier" >> $id_file
fi
###################### 更新 DNS 记录 ######################
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$record_identifier\",\"type\":\"$record_type\",\"name\":\"$record_name\",\"content\":\"$ip\",\"proxied\":"false"}")
######################### 更新反馈 #########################
if [[ $update == *"\"success\":false"* ]]; then
message="API UPDATE FAILED. DUMPING RESULTS:\n$update"
log "$message"
echo -e "$message"
exit 1
else
message="IP changed to: $ip"
echo "$ip" > $ip_file
log "$message"
echo "$message"
fi
其中更新DNS记录请求也可以规范写成:
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" \
-H "X-Auth-Email: $auth_email" \
-H "X-Auth-Key: $auth_key" \
-H "Content-Type: application/json" \
--data '{"id":"$record_identifier","type":"$record_type","name":"$record_name","content":"$ip","proxied":"false"}')
注意,选一种即可,不要都复制进去了。
编辑保存为ddns.sh,主要修改auth_email为你的CF邮箱、auth_key为API KEY、zone_name为你的主域名,record_name为你要作为DDNS的二级域名(之前做好解析,解析记录随便填127.0.0.1就可以)
bash ddns.sh
测试看看cloudflare是否更改成功,没问题后赋予执行权限同时加入自动执行任务清单:
chmod +x ddns.shcrontab -e
配置Crontab任务
添加如下代码
*/5 * * * * bash /root/ddns.sh
每个5分钟检测一次(每间隔4分钟),IP是否改变。
crontab基本用法参考我这篇文章第二部分:
对这些脚本感兴趣的可以去原作者南晴浪大佬探讨:
https://blog.sometimesnaive.org/article/5
或者参考:
https://op.ci/618.html
希望能对囊中羞涩或者爱折腾购买NAT机器的朋友一点指引,获得稳定的域名访问/家庭内网群晖随身娱乐/某些科学上网配置域名访问配置隐藏源ip/NAT私人机场等用途,注意合规使用,莫做违法之事。
最后附送NAT机器建站nginx配置参考:
server {
listen 20788 ssl http2;
root /home/wwwroot/lolimoe.ltd;
index index.html index.htm index.php;
#charset koi8-r;
access_log /var/log/nginx/lolimoe.ltd.access.log main;
server_name zhenjiang.lolimoe.ltd;
ssl_certificate "/usr/local/cert/lolimoe_ltd.crt";
ssl_certificate_key "/usr/local/cert/lolimoe_ltd.key";
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
client_max_body_size 8888m;
location / {
index index.html index.php;
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.php) {
rewrite (.*) $1/index.php;
}
if (!-f $request_filename) {
rewrite (.*) /index.php;
}
}
location ~ \.php {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ ^.+\.php {
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
}
location ~ /.ht {
deny all;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 15d;
}
location ~ .*\.(js|css)?$ {
expires 1d;
}
}