起因是因为要在家里搭建一个服务器,但因为家用宽带的ip是动态更新的,如果在学校使用当IP更新后我相当于丢失了这个服务器。
至于市面上的众多DDNS软件之所以不用,一是因为个人不喜欢到处注册,其实是因为纯粹的无聊。疫情期间过于无聊,就打算造个轮子来玩玩。

前言

使用方法

  • python 版本:3.7
    1
    python <FileName> <accessKeyId> <accessSecret> <DomainName>

DDNS是指动态域名解析服务,将用户的IP地址解析到对应的域名

为什么要用DDNS

  • 家用宽带没有固定的公网IP
  • 不想并且也无法每天手动更新IP
  • 需要远程使用

准备工作

  1. 拥有一个公网IP,不然解析了也没啥用
  2. 拥有一个域名,并且是在阿里云购买的
  3. 查看阿里云的SDK

阿里云SDK

一个是阿里云的核心SDK库,一个是阿里云的域名库,阿里云DNS SDK库,阿里云SDK平台

  • 阿里云核心SDK库:pip install aliyun-python-sdk-core
  • 阿里云域名SDK库:pip aliyun-python-sdk-domain
  • 阿里云DNS SDK库:pip install aliyun-python-sdk-alidns

实现思路

  1. 获取本地公网IP(local+ip)
  2. 获取云端解析IP(cloud_ip)
  3. 判断两个IP是否一致
  4. 公网IP发生变化(不一致)时,更新域名解析记录

设计缺陷

  • accessKeyId需要运行是作为参数输入,如果服务器宕机重启,有可能导致无法运行,或者泄露accessKeyId
  • 当刷新时间过短,会消耗网络资源(每次获取解析的IP都是通过阿里云接口获取,并非本地保存)
  • 程序效率低下,很多多余操作。

详细步骤

获取本地公网IP(local_ip)

这里我通过网络上面的外网IP的API获取,我目前只使用了一个,计划以后添加。
另一个实现思路是类似百度搜索IP,然后提取网页内容。

1
2
3
4
5
6
7
8
9
10
from urllib import request
import time

# 获取本机公网IP
def get_internet_ip():
myip_html = request.urlopen('http://www.3322.org/dyndns/getip')
myip = myip_html.read()
ip = str(myip, encoding='utf-8').replace("\n", "")
# print(ip)
return ip

解析记录IP查询(cloud_ip)

  • 获取accessKeyId和accessSecret
    通过阿里云控制台获取,但一般建议使用RAM角色来进行权限的控制,这样accessKeyId和accessSecret就无法进行其他操作。由于是个人使用,所以我选择直接使用。
    获取到accessKeyId和accessSecret之后,填入对应的函数中,初始化即可:
  • 通过 get_cloud_ip()函数,填入你的accessKeyId和accessSecret以及你要解析的域名(DomainName)
    这个函数会返回很多东西,其中就包括我们需要的IP记录。
    这里我们可以选择提取我们需要的IP记录,或者直接修改,但我感觉这样不太优雅,所以我这里选择在程序中进行修改。
  • 修改域名解析记录所需和函数 get_cloud_ip() 查询的内容和格式都是一样的
    所以我们可以对里面需要的项来进行修改,避免重新构建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest

# 获取解析记录,返回字符串
def get_cloud_ip(accessKeyId, accessSecret, DomainName):
client = AcsClient(accessKeyId, accessSecret, 'cn-hangzhou')
request = DescribeDomainRecordsRequest()
request.set_accept_format('json')
request.set_DomainName(DomainName)
response = client.do_action_with_exception(request)
# python2: print(response)
# print(type(response))
# print(str(response, encoding='utf-8'))
return response

判断本地IP(locol_ip)和解析记录的IP(cloud_ip)是否相同

  • 当两者相同时,等待下次刷新。
    这里使用sleep函数实现简单的暂停,感觉这是个笨方法。
  • 当两者不相同时,运行更新函数 update_ip() 更新解析记录。
    由于返回的解析记录中,含有多个记录,而我目前只需要修改主机记录为 WWW 的IP记录即可,所以我们提取该主机记录出来进行修改。
    修改完后,更新标记 check_update_ip 为真值。
  • 当更新标记 check_update_ip为真时,运行更新函数 update_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
my_cloud_data = get_cloud_ip.get_cloud_ip(accessKeyId, accessSecret, DomainName)
# 数据提取
my_cloud_data = json.loads(my_cloud_data)
my_cloud_date = my_cloud_data['DomainRecords']['Record']
# 判断ip是否变化,变化更换云上解析的IP
for Record in my_cloud_date:
if Record['RR'] == 'www':
# 读取cloud_ip
cloud_ip = Record['Value']
# 如果IP不相等,修改 Record
if cloud_ip != My_ip:
Record['Value'] = My_ip
new_cloud_date = Record
# print(Record['RR'])
print('[INFO] 更新IP中....')
check_update_ip = True
print(new_cloud_date)
elif cloud_ip == My_ip:
print('[INFO] 等待更新....')
check_update_ip = False
# 如果需要更新IP 则更新
if check_update_ip == True:
## update
update_ip.update_ip(accessKeyId, accessSecret, new_cloud_date)
write_log(new_cloud_date['Value'])
print("[INFO] 更新成功... ")
# check_update_ip = False
else:
time.sleep(3600) #单位秒
print("INFO checking...")

更新解析记录

这里使用的是阿里云官方的示例代码,阿里云云解析DNS接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.acs_exception.exceptions import ClientException
from aliyunsdkcore.acs_exception.exceptions import ServerException
from aliyunsdkalidns.request.v20150109.UpdateDomainRecordRequest import UpdateDomainRecordRequest

# 需要->RecordId,RR类型,Type,Value
# 返回 RecordId,RequestId
def update_ip(accessKeyId, accessSecret, new_cloud_date):
client = AcsClient(accessKeyId, accessSecret, 'cn-hangzhou')

request = UpdateDomainRecordRequest()
request.set_accept_format('json')

request.set_RecordId(new_cloud_date["RecordId"])
request.set_RR(new_cloud_date["RR"])
request.set_Type(new_cloud_date["Type"])
request.set_Value(new_cloud_date["Value"])

response = client.do_action_with_exception(request)
# python2: print(response)
print(str(response, encoding='utf-8'))
return response

总结

  • 每次更新IP后可以把IP保存为解析记录的IP,下次直接判断两者即可,不用每次都调用查询API。
  • 多看官方API文档。