From 64fef74134d97144f6a23d0d91bfe61ca1183a68 Mon Sep 17 00:00:00 2001 From: Horned_Axe Date: Sat, 13 Jul 2024 22:48:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=BF=E7=94=A8ifconfi?= =?UTF-8?q?g=E8=8E=B7=E5=8F=96=E5=9C=B0=E5=9D=80=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E5=BA=94=E5=AF=B9=E5=90=8C=E4=B8=80=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=AD=98=E5=9C=A8=E5=A4=9A=E4=B8=AA=E5=85=AC=E7=BD=91?= =?UTF-8?q?ipv6=E5=9C=B0=E5=9D=80=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ README.md | 71 ++++++++++++++++++++++----------------- README_ZH_CN.md | 11 +++--- src/AcsClientSingleton.py | 7 ++-- src/DDNS.py | 41 +++++++++++----------- src/IpGetter.py | 32 ++++++++++++++++-- 6 files changed, 106 insertions(+), 58 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bad829 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +aliyun_venv/ +*.pyc \ No newline at end of file diff --git a/README.md b/README.md index 6cb023e..8aff068 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,57 @@ # DDNS -[中文](https://github.com/mgsky1/DDNS/blob/master/README_ZH_CN.md)|[English](https://github.com/mgsky1/DDNS/blob/master/README.md) + +[中文](https://github.com/mgsky1/DDNS/blob/master/README_ZH_CN.md) | [English](https://github.com/mgsky1/DDNS/blob/master/README.md) + ## Summary -> Python and Aliyun SDK API have been used in this project. You can use this tool to map the local applications like NAS, DB, WEB etc to the internet. + +> Implemented using Python and Alibaba Cloud DNS API. Can be used in a home environment to map NAS, DB, Web, and other applications to the public network. + ## Install + ```bash -pip3 install aliyun-python-sdk-core-v3 +pip3 install aliyun-python-sdk-core ``` +Tested with aliyun-python-sdk-core==2.15.1 on 2024/07/13. + ## Run ```bash -python3 src/DDNS.py # default ipv4 -python3 src/DDNS.py -6 # change to ipv6 +cd src +python3 DDNS.py # Default to ipv4 +python3 DDNS.py -6 # Switch to ipv6 ``` + ## Note -> * Based on Python3、Aliyun API. -> * To begin, you can run the main function in DDNS.py. The main function in other .py files are for the test purpose. -> * You can set this script as a timer task in your opening system. For example, running this script at 4:30am everyday or when connecting to the internet. -> * On the [dev](https://github.com/mgsky1/DDNS/tree/dev) branch, this project supports binding multiple domains to the same ip address. -> * If you use iPv4, please make sure that the record type of your domain which will be used is **A**. If you use iPv6, the type is **AAAA**. -> * This script is my idea for implementing DDNS. +> * Based on: Python 3, Alibaba Cloud Python SDK, Alibaba Cloud DNS API +> * Directly run the main function of the DDNS.py file. The main functions of other .py files are for testing purposes. +> * You can set this script as a system scheduled task, for example, to execute once at 4:30 AM every day or to run automatically each time you connect to the internet. +> * The latest [dev](https://github.com/mgsky1/DDNS/tree/dev) branch adds the feature of binding multiple domain names to the same IP address. Feel free to try it out. +> * If you use an IPv4 address, make sure the record type of the domain is set to **A**. If you use an IPv6 address, set it to **AAAA**. +> * This script is a personal implementation of DDNS. ## Restrict -> This script is suitable for the broadband which has a dynamic IP. If not, you can try NAT-DDNS tools like [frp](https://github.com/fatedier/frp). +> This script is suitable for home broadband with dynamic IP. If not, you can use NAT-DDNS tools like [frp](https://github.com/fatedier/frp) for intranet penetration. ## Configuration -The config.json has some infomation you should provide. The config structure may like this: +This project has been modified to store user configurations using a configuration file. The configuration file is in JSON format and is stored in config.json, as shown below: ``` { - "AccessKeyId": "Your_AccessKeyId",//Your Aliyun AccessKeyId - "AccessKeySecret": "Your_AccessKeySecret",//Your Aliyun AccessKeySecret - "First-level-domain": "Your_First-level-domain",//First level domain, eg example.com - "Second-level-domain": "Your_Second-level-domain"//Second level domain, eg ddns.example.com Just input ddns + "AccessKeyId": "Your_AccessKeyId", // Your Alibaba Cloud AccessKeyId + "AccessKeySecret": "Your_AccessKeySecret", // Your Alibaba Cloud AccessKeySecret + "First-level-domain": "Your_First-level-domain", // First-level domain, e.g., example.com + "Second-level-domain": "Your_Second-level-domain" // Second-level domain, e.g., ddns.example.com, just enter ddns, or use @ to directly resolve the primary domain } ``` + ## Tip -> How to determine wether your broadband service has a dynamic IP. -> * Step 1:Find your WAN IP by google or other tools. -> * Step 2:Run a web service locally. For example, starting IIS in Windows or Apache in Linux and using their default webpage. -> * Step 3: Set the map rules in your home router. The ports which you will use to access the local service over internet had better not to be 80 beacuse the 80 port may be blocked by your internet service provider. -> * Step 4: Use the IP you fond by google and the port to access your local web service. If ok, congratulations! +> How to determine if your broadband has a dynamic IP: +> * Step 1: Search for your IP on Baidu to find your IP address. +> * Step 2: Start a local website, for example, start IIS on Windows or install Apache or Nginx on Linux and start it with their default page. +> * Step 3: Set up port forwarding rules on the router. It is best not to use port 80 for public IP network access as it may be blocked by the ISP. +> * Step 4: Finally, use the public IP + port number found earlier to access and see if you can display the intranet page. If so, congratulations! ## ScreenShots -NOTE: Because I have updated before, the script tells me the DNS record has already exists. Aliyun does not allow users to update the same IP when the IP has not been changed. The second picture shows the local service. The Third one shows accessing local service over internet under the help of DDNS. +Note: Since I have already updated, it prompts that the IP address already exists. Alibaba Cloud does not allow the same IP to be updated repeatedly. The second image is local, and the third image is external network.
![](http://xxx.fishc.org/forum/201805/26/181341tp2frcnnnvnvc5iz.png) ![](http://xxx.fishc.org/forum/201805/26/200124rsubrwwdblr8ffwz.png) @@ -49,12 +59,13 @@ NOTE: Because I have updated before, the script tells me the DNS record has alre ![](http://xxx.fishc.org/forum/201805/26/200228kb1u63hargn0pc1n.png) ## Change Log -> * 2018/5/29 Add detecting internet access. -> * 2018/6/10 Start using configuration file. -> * 2018/9/24 Improve the error output -> * 2018/12/24 Improve the way to get IP, deleteing BS4 dependence. Thanks [@Nielamu](https://github.com/NieLamu). -> * 2018/12/27 Support ipv6. Thanks [@chnlkw](https://github.com/chnlkw). -> * 2020/05/05 Improve the policy of getting ipv4 address. Once failed, it will write into the log and retry with new method after 10 sec. Thanks [@sunsheho](https://github.com/sunsheho). +> * 2018/5/29 Network connectivity check, only operate when there is a network, otherwise wait for network connection. +> * 2018/6/10 Use configuration file to store user data. +> * 2018/9/24 Modify failure prompt output, add Alibaba help URL for users to check corresponding error information. +> * 2018/12/24 Improve IP acquisition method, remove BS4 dependency, thanks to [@Nielamu](https://github.com/NieLamu). +> * 2018/12/27 Add IPv6 support, thanks to [@chnlkw](https://github.com/chnlkw). +> * 2020/05/05 Modify IPv4 address acquisition method. If it fails, it will be logged and retried in 10 seconds using a new method. Thanks to [@sunsheho](https://github.com/sunsheho). +> * 2024/07/13 Add a method to get IPv6 address using ifconfig to handle the situation where the same device has multiple public IPv6 addresses. ## Contribution -If you interest in this project and want to improve it, welcome to fork the project. Have any questions? you can ask in issue~ +Feel free to fork the project if interested, and if you have any questions, feel free to ask in the issue section~ \ No newline at end of file diff --git a/README_ZH_CN.md b/README_ZH_CN.md index ca28ee0..d241a96 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -9,13 +9,15 @@ ## Install ```bash -pip3 install aliyun-python-sdk-core-v3 +pip3 install aliyun-python-sdk-core ``` +aliyun-python-sdk-core==2.15.1 2024/07/13测试通过 ## Run ```bash -python3 src/DDNS.py # 默认ipv4 -python3 src/DDNS.py -6 # 改用ipv6 +cd src +python3 DDNS.py # 默认ipv4 +python3 DDNS.py -6 # 改用ipv6 ``` @@ -36,7 +38,7 @@ python3 src/DDNS.py -6 # 改用ipv6 "AccessKeyId": "Your_AccessKeyId",//你的阿里云AccessKeyId "AccessKeySecret": "Your_AccessKeySecret",//你的阿里云AccessKeySecret "First-level-domain": "Your_First-level-domain",//一级域名,例如 example.com - "Second-level-domain": "Your_Second-level-domain"//二级域名,例如 ddns.example.com 填入ddns即可 + "Second-level-domain": "Your_Second-level-domain"//二级域名,例如 ddns.example.com 填入ddns即可,也可使用@表示直接解析主域名 } ``` ## Tip @@ -61,6 +63,7 @@ python3 src/DDNS.py -6 # 改用ipv6 > * 2018/12/24 改进ip获取方式,删除BS4依赖,感谢[@Nielamu](https://github.com/NieLamu) > * 2018/12/27 增加ipv6支持,感谢[@chnlkw](https://github.com/chnlkw) > * 2020/05/05 修改ipv4地址获取方式。如果失败,会写入日志,并在10秒后采用新的方式重试。感谢[@sunsheho](https://github.com/sunsheho) +> * 2024/07/13 添加使用ifconfig获取ipv6地址的方式,能够应对同一设备拥有多个公网ipv6地址的情况 ## Contribution 如果感兴趣欢迎fork项目,如果有任何问题欢迎在issue区提问~ diff --git a/src/AcsClientSingleton.py b/src/AcsClientSingleton.py index c170150..6d254d3 100644 --- a/src/AcsClientSingleton.py +++ b/src/AcsClientSingleton.py @@ -4,15 +4,18 @@ @time: created on 2018/5/26 18:50 @修改记录: 2018/6/10 =》 AccessKeyId 和 AccessKeySecret从配置文件中读取 +2024/7/13 =》 避免循环导入 ''' from aliyunsdkcore.client import AcsClient -import Utils as tools +import json + class AcsClientSing: __client = None @classmethod def getInstance(self): if self.__client is None: - acsDict = tools.Utils.getConfigJson() + with open('config.json') as file: + acsDict = json.loads(file.read()) self.__client = AcsClient(acsDict.get('AccessKeyId'), acsDict.get('AccessKeySecret'), 'cn-hangzhou') return self.__client diff --git a/src/DDNS.py b/src/DDNS.py index dd79701..1fb6c04 100644 --- a/src/DDNS.py +++ b/src/DDNS.py @@ -7,6 +7,7 @@ 2018/5/29 => 增加网络连通性检测,只有联通时才进行操作,否则等待 2018/6/10 => 使用配置文件存储配置,避免代码内部修改(需要注意Python模块相互引用问题) 2018/9/24 => 修改失败提示信息 +2024/7/13 => 修改DDNS函数逻辑,应对同一设备存在多个公网ipv6地址的情况 ''' from aliyunsdkcore.acs_exception.exceptions import ServerException from aliyunsdkcore.acs_exception.exceptions import ClientException @@ -18,23 +19,32 @@ def DDNS(use_v6): client = Utils.getAcsClient() recordId = Utils.getRecordId(Utils.getConfigJson().get('Second-level-domain')) if use_v6: - ip = Utils.getRealIPv6() + ips = Utils.getRealIPv6() type = 'AAAA' else: - ip = Utils.getRealIP() + ips = [Utils.getRealIP()] type = 'A' - print({'type': type, 'ip':ip}) + print({'type': type, 'ip':ips}) request = Utils.getCommonRequest() request.set_domain('alidns.aliyuncs.com') request.set_version('2015-01-09') request.set_action_name('UpdateDomainRecord') - request.add_query_param('RecordId', recordId) - request.add_query_param('RR', Utils.getConfigJson().get('Second-level-domain')) - request.add_query_param('Type', type) - request.add_query_param('Value', ip) - response = client.do_action_with_exception(request) - return response + + for ip in ips: + try: + request.add_query_param('RecordId', recordId) + request.add_query_param('RR', Utils.getConfigJson().get('Second-level-domain')) + request.add_query_param('Type', type) + request.add_query_param('Value', ip) + response = client.do_action_with_exception(request) + print(f"更新IP地址 {ip} 成功!") + except (ServerException, ClientException) as reason: + print(f"更新IP地址 {ip} 失败!") + print("原因为:", reason.get_error_msg()) + print("错误码:", reason.get_error_code()) + print("可参考: https://help.aliyun.com/document_detail/29774.html") + print("或阿里云帮助文档") if __name__ == "__main__": parser = argparse.ArgumentParser(description='DDNS') @@ -42,14 +52,5 @@ def DDNS(use_v6): args = parser.parse_args() isipv6 = isinstance(args.ipv6, list) - try: - while not Utils.isOnline(): - time.sleep(3) - continue - result = DDNS(isipv6) - print("成功!") - except (ServerException,ClientException) as reason: - print("失败!原因为") - print(reason.get_error_msg()) - print("可参考:https://help.aliyun.com/document_detail/29774.html?spm=a2c4g.11186623.2.20.fDjexq#%E9%94%99%E8%AF%AF%E7%A0%81") - print("或阿里云帮助文档") \ No newline at end of file + DDNS(isipv6) + print("完成!") diff --git a/src/IpGetter.py b/src/IpGetter.py index 02cc09f..50e2fb1 100644 --- a/src/IpGetter.py +++ b/src/IpGetter.py @@ -5,12 +5,16 @@ 2018/12/24 =》改进ip获取方式 取消BS4依赖 感谢@Nielamu的建议 2020/05/05 =》改进ipv4获取方式,如果获取失败,则会写入日志文件,并过10秒后使用新的网址重试,感谢@sunsheho贡献的代码 我在休眠时间上略作修改 +2024/07/13 =》如果能使用ifconfig命令,则使用ifconfig来获取公网ipv6地址,应对同一设备存在多个公网ipv6地址的情况 ''' import urllib.request from urllib import request,error import json import time,datetime from time import sleep +import subprocess +import re +import shutil def senderror(errcont): enow=datetime.datetime.now() @@ -59,6 +63,22 @@ def getRealIp(data): getIpPage() +# 使用ifconfig获取当前IPV6地址 +def get_ifconfig_output(): + try: + result = subprocess.run(['ifconfig'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + return result.stdout + except subprocess.CalledProcessError as e: + print(f"Error executing ifconfig: {e}") + return "" + +# 使用正则表达式匹配公网ipv6地址 +def get_ipv6_addresses(ifconfig_output): + ipv6_pattern = re.compile(r'inet6 (\S+) .*scopeid 0x0') + ipv6_addresses = ipv6_pattern.findall(ifconfig_output) + return ipv6_addresses + + # 利用API获取含有用户ip的JSON数据 def getIpPageV6(): url = "https://v6.ident.me/.json" @@ -69,5 +89,13 @@ def getIpPageV6(): # 解析数据,获得IP def getRealIpV6(data): - jsonData = json.loads(data) - return jsonData['address'] + if shutil.which('ifconfig'): # 如果能使用ifconfig命令,则使用ifconfig来获取ipv6地址,应对同一设备存在多个公网ipv6地址的情况 + ifconfig_output = get_ifconfig_output() + if ifconfig_output: + ipv6_addresses = get_ipv6_addresses(ifconfig_output) + return ipv6_addresses + else: + return [] + else: + jsonData = json.loads(data) + return jsonData['address'] From 33ffd3f7f198cf8e1985abc32ac67a3cb75593a5 Mon Sep 17 00:00:00 2001 From: Horned_Axe Date: Sun, 14 Jul 2024 00:23:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=9B=B4=E6=96=B0RecordID=E4=B8=8EIP?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E7=9A=84=E5=8C=B9=E9=85=8D=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=BA=94=E5=AF=B9=E8=AE=BE=E5=A4=87=E6=8B=A5=E6=9C=89?= =?UTF-8?q?=E5=A4=9A=E4=B8=AAIPv6=E5=9C=B0=E5=9D=80=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DDNS.py | 37 ++++++++++++++++++++++--------------- src/Utils.py | 4 +++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/DDNS.py b/src/DDNS.py index 1fb6c04..3acb2bd 100644 --- a/src/DDNS.py +++ b/src/DDNS.py @@ -17,7 +17,7 @@ def DDNS(use_v6): client = Utils.getAcsClient() - recordId = Utils.getRecordId(Utils.getConfigJson().get('Second-level-domain')) + recordIds = Utils.getRecordId(Utils.getConfigJson().get('Second-level-domain')) if use_v6: ips = Utils.getRealIPv6() type = 'AAAA' @@ -31,20 +31,27 @@ def DDNS(use_v6): request.set_version('2015-01-09') request.set_action_name('UpdateDomainRecord') - for ip in ips: - try: - request.add_query_param('RecordId', recordId) - request.add_query_param('RR', Utils.getConfigJson().get('Second-level-domain')) - request.add_query_param('Type', type) - request.add_query_param('Value', ip) - response = client.do_action_with_exception(request) - print(f"更新IP地址 {ip} 成功!") - except (ServerException, ClientException) as reason: - print(f"更新IP地址 {ip} 失败!") - print("原因为:", reason.get_error_msg()) - print("错误码:", reason.get_error_code()) - print("可参考: https://help.aliyun.com/document_detail/29774.html") - print("或阿里云帮助文档") + used_ips = set() # 用于跟踪已使用过的IP地址 + + for recordId in recordIds: + for ip in ips: + if ip in used_ips: + continue + try: + request.add_query_param('RecordId', recordId) + request.add_query_param('RR', Utils.getConfigJson().get('Second-level-domain')) + request.add_query_param('Type', type) + request.add_query_param('Value', ip) + used_ips.add(ip) + response = client.do_action_with_exception(request) + print(f"更新RecordId {recordId} 的IP地址 {ip} 成功!") + break + except (ServerException, ClientException) as reason: + print(f"更新RecordId {recordId} 的IP地址 {ip} 失败!") + print("原因为:", reason.get_error_msg()) + print("错误码:", reason.get_error_code()) + print("可参考: https://help.aliyun.com/document_detail/29774.html") + print("或阿里云帮助文档") if __name__ == "__main__": parser = argparse.ArgumentParser(description='DDNS') diff --git a/src/Utils.py b/src/Utils.py index a64e186..71ff030 100644 --- a/src/Utils.py +++ b/src/Utils.py @@ -38,9 +38,11 @@ def getRecordId(domain): response = client.do_action_with_exception(request) jsonObj = json.loads(response.decode("UTF-8")) records = jsonObj["DomainRecords"]["Record"] + record_ids = [] for each in records: if each["RR"] == domain: - return each["RecordId"] + record_ids.append(each["RecordId"]) # 跟踪每一个recordID + return record_ids #获取CommonRequest def getCommonRequest():