e10.jpg

一、前言: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访问子账号,如果你账号资产不重要,直接用你的主号来操作就行,此段可以略过

创建专用API子账号流程

  • A_1.首先登录API密钥页面:

API密钥页面
1_create api.png
选择新建子账号

  • A_2.这里我选择自定义创建:

2_create sub user.jpg

  • A_3.选择可访问资源类型:

3_choose sub user type .png

  • A_4.填写子账号用户细节

用户名,手机号,邮箱等信息,注意勾选“编程访问”方式
4_sub user details .png

  • A_5.授权子账号策略为完整的云资源授权

如果需要其他授权,可以搜索对应关键字快速寻找
授权:QCloudResourceFullAccess
或者留空,创建完毕后,再去
控制台用户预览
用户列表选择授权需要的权限也行
5_sub user policies .png

  • A_6.最后确认用户细节和策略清单

如果没问题就提交下一步
6_sub user last confirm .png

  • A_7.保存你的API Secret ID信息

我们在此页面可以拿到对应的API ID和KEY密钥相关信息,请务必妥善保存,如果怕遗忘可以点选发送到可靠邮箱备份。
7_sub user secret details .png

创建完毕后在你的控制台访问管理>概览页面,标记着子账号登录信息备忘:
控制台概览页面
复制保存好你的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
&<接口请求参数>

注意两点:

  1. 参数严格按照字母排列顺序排序;
  2. 大小写敏感

那么我们的脚本第一步功能:获取解析记录可以这样写:

#!/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"

原文参考链接

  • 同时我提供下自己的范本供参考:

修改DNS解析脚本范例

#!/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请求:

基于python语言的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创建过程,感兴趣的可以点开查看:
CloudFlare API创建流程

  • B_1.点击create tokens创建新的API

CF_create_tokens.png

  • B_2.取个喜欢的命名,选择必要的授权

ZONE范围,DNS解析项目,Edit编辑权限即可:
CF_tokens_define.png

  • B_3.最后确认下信息

CF_tokens_confirm.png

  • B_4.获得我们的API Key,保存好你的API key

Mnxxxxxxxxxxxxxxxxxxx
CF_tokens_get&test.png
同时注意页面会给你一个形如下列的测试指令:

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脚本如下:

Cloud Flare 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.sh
crontab -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配置参考:

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;
  }
}

Last modification:October 5, 2021
If you think my article is useful to you, please feel free to appreciate