Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

添加使用ifconfig获取地址的方式(如果ifconfig可用),应对同一设备存在多个公网ipv6地址的情况 #33

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
aliyun_venv/
*.pyc
71 changes: 41 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,71 @@
# 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.<br/>
![](http://xxx.fishc.org/forum/201805/26/181341tp2frcnnnvnvc5iz.png)

![](http://xxx.fishc.org/forum/201805/26/200124rsubrwwdblr8ffwz.png)

![](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~
11 changes: 7 additions & 4 deletions README_ZH_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```


Expand All @@ -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
Expand All @@ -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区提问~
Expand Down
7 changes: 5 additions & 2 deletions src/AcsClientSingleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
50 changes: 29 additions & 21 deletions src/DDNS.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,40 +17,47 @@

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:
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

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')
parser.add_argument('-6', '--ipv6', nargs='*', default=False)
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("或阿里云帮助文档")
DDNS(isipv6)
print("完成!")
32 changes: 30 additions & 2 deletions src/IpGetter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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<global>')
ipv6_addresses = ipv6_pattern.findall(ifconfig_output)
return ipv6_addresses


# 利用API获取含有用户ip的JSON数据
def getIpPageV6():
url = "https://v6.ident.me/.json"
Expand All @@ -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']
4 changes: 3 additions & 1 deletion src/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down