commit c54da263063c086c36a0e519e6375d0dcf149b04
Author: marques <20172333133@m.scnu.edu.cn>
Date: Sat Apr 23 21:18:04 2022 +0800
first commit
diff --git a/README.MD b/README.MD
new file mode 100644
index 0000000..9f88939
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,86 @@
+# 文件结构
+
+ddns_win.py windows106上使用的ddns程序:主要执行的脚本判断从dns解析的结果与当前ip是否一致,不一致则修改DNS记录
+
+ddns_801.py ubuntu186上使用的ddns程序:主要执行的脚本判断从dns解析的结果与当前ip是否一致,不一致则修改DNS记录
+
+getLocalIP 通过socket库获取本机IP,适用于本机PPPOE拨号的情况
+
+getRouterIP 通过爬虫脚本获取路由器WAN口IP,只是用WIS两台路由器
+
+router_info.ini 存放路由器的配置信息
+
+
+
+# DDNS动态域名解析脚本
+
+
+
+## 域名服务商 与 API接口
+
+安利一个域名服务商:porkbun
+
+API接口文档:https://porkbun.com/api/json/v3/documentation
+
+通过查阅API接口了解如何通过接口修改域名记录
+
+porkbun是通过json向接口提交参数完成操作
+
+
+
+## 获取目标IP
+
+### 1. 获取路由器IP
+
+目标:获取路由器WAN口IP,而非公网出口IP
+
+方法:通过爬虫获取路由器系统信息
+
+环境:必联路由(其实就是极路由系统换皮)
+
+具体实现:
+
+通过浏览器F12开发者工具可以获取以下信息:
+
+1. 每次正确输入密码验证后,路由器通过一个密钥stok来验证用户的登录状态,因此需要抓取输入密码后返回的stok保持后续操作的可行性
+
+ 抓取后可以看出,在提交密码时,浏览器用GET方式向http://192.168.199.1/cgi-bin/turbo/api/login/login_admin传递参数username和password
+
+ 其中192.168.199.1时路由器的管理地址,admin时默认的username这个不用修改
+
+ 从路由器返回结果可以看到直接返回了stok,所以可以直接使用(理论上可以直接按开发者工具response看到)但是没看到所以下面用python获取返回的结果。
+
+
+
+
+
+2. 继续观察浏览器network中的数据包,可以发现浏览器通过POST方法向 http://{IP}/cgi-bin/turbo{STOK}/proxy/call 接口传递一个Form data模板来获取路由器信息。
+
+
+
+3. 分析POST结果,可以得出只要按照浏览器的格式POST该模板就可以获取路由器地址,剔除无关信息后返回结果如下
+
+
+
+
+
+### 2. 获取本地IP
+
+直接通过python的socket库就可以获取到
+
+refer:
+
+1. [Python 获取本机公网IPv6地址](https://coco56.blog.csdn.net/article/details/106725406)
+
+2. [python获取本机IP地址](https://www.cnblogs.com/z-x-y/p/9529930.html)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README/img/YbpRaBc.png b/README/img/YbpRaBc.png
new file mode 100644
index 0000000..986c98e
Binary files /dev/null and b/README/img/YbpRaBc.png differ
diff --git a/README/img/fALmA9X.png b/README/img/fALmA9X.png
new file mode 100644
index 0000000..ea87fce
Binary files /dev/null and b/README/img/fALmA9X.png differ
diff --git a/README/img/oX3tOdh.png b/README/img/oX3tOdh.png
new file mode 100644
index 0000000..d7fde23
Binary files /dev/null and b/README/img/oX3tOdh.png differ
diff --git a/README/img/uVaBflU.png b/README/img/uVaBflU.png
new file mode 100644
index 0000000..fdc7a47
Binary files /dev/null and b/README/img/uVaBflU.png differ
diff --git a/ddns_801.py b/ddns_801.py
new file mode 100644
index 0000000..5503d5b
--- /dev/null
+++ b/ddns_801.py
@@ -0,0 +1,31 @@
+from porkbun_ddns import PorkbunAPI
+from getRouterIP import GetRouterWanIP
+import re
+from dns.resolver import resolve
+API_Key = "pk1_我删了"
+Secret_Key = "sk1_我删了"
+
+
+def check_ip(ip_addr):
+ compile_ip = re.compile(
+ '^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$')
+ if compile_ip.match(ip_addr):
+ return True
+ else:
+ return False
+
+
+GetRouterWanIP.check_info_exist()
+getRouterWanIP = GetRouterWanIP()
+getRouterWanIP.get_stok()
+ip = getRouterWanIP.get_wan_ip()
+print(ip)
+if check_ip(ip):
+ if str(resolve("wis01.marques22.com", 'A')[0]) != ip:
+ porkbun_api = PorkbunAPI("marques22.com", API_Key, Secret_Key)
+ porkbun_api.edit_A_with_name("wis01", ip)
+ print("edit end")
+ else:
+ print("ip not change")
+else:
+ print("ipv4 formation is wrong!")
diff --git a/ddns_win.py b/ddns_win.py
new file mode 100644
index 0000000..0f4ebd8
--- /dev/null
+++ b/ddns_win.py
@@ -0,0 +1,30 @@
+from porkbun_ddns import PorkbunAPI
+from getRouterIP import GetRouterWanIP
+import re
+from dns.resolver import resolve
+API_Key = "我删了"
+Secret_Key = "我删了"
+
+
+def check_ip(ip_addr):
+ compile_ip = re.compile(
+ '^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$')
+ if compile_ip.match(ip_addr):
+ return True
+ else:
+ return False
+
+
+GetRouterWanIP.check_info_exist()
+getRouterWanIP = GetRouterWanIP()
+getRouterWanIP.get_stok()
+ip = getRouterWanIP.get_wan_ip()
+
+if check_ip(ip):
+ if str(resolve("wis02.marques22.com", 'A')[0]) != ip:
+ porkbun_api = PorkbunAPI("marques22.com", API_Key, Secret_Key)
+ porkbun_api.edit_A_with_name("wis02", ip)
+ else:
+ print("ip not change")
+else:
+ print("ipv4 formation is wrong!")
diff --git a/getLocal_IP.py b/getLocal_IP.py
new file mode 100644
index 0000000..53fa19e
--- /dev/null
+++ b/getLocal_IP.py
@@ -0,0 +1,32 @@
+import socket
+import os
+import re
+
+def get_local_ip():
+
+
+ try:
+ s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
+ s.connect(('8.8.8.8',80))
+ ip=s.getsockname()[0]
+ finally:
+ s.close()
+
+
+ print(ip)
+ return ip
+
+def get_local_ipv6():
+
+ output = os.popen("ipconfig /all").read()
+ # print(output)
+ result = re.findall(r"(([a-f0-9]{1,4}:){7}[a-f0-9]{1,4})", output, re.I)
+ ip = result[0][0]
+
+ print(ip)
+ return ip
+
+
+if __name__ == '__main__':
+ get_local_ip()
+ get_local_ipv6()
diff --git a/getRouterIP.py b/getRouterIP.py
new file mode 100644
index 0000000..922ab8f
--- /dev/null
+++ b/getRouterIP.py
@@ -0,0 +1,101 @@
+import configparser
+import requests
+import sys
+import os.path as osp
+
+
+class GetRouterWanIP:
+ def __init__(self):
+ self.ip = None
+ self.username = None
+ self.password = None
+ self.stok = None
+ self.remain_time = None
+ self.cookie = None
+ self.error_time = None
+ self.get_router_info()
+
+ @staticmethod
+ def check_info_exist():
+ if not osp.exists("/home/marques/software/ddns/router_info.ini"):
+ print("Error: Router information config not exist!")
+ print("Info: Please fill the configuration file ./router_info.ini")
+ info_config = configparser.ConfigParser()
+ info_config["DEFAULT"] = {"ip": "", "username": "", "password": "", "error_time": 0}
+ with open("/home/marques/software/ddns/router_info.ini", "w") as file:
+ info_config.write(file)
+
+ def get_router_info(self):
+ router_info_config = configparser.ConfigParser()
+ router_info_config.read("/home/marques/software/ddns/router_info.ini", encoding="utf-8")
+
+ self.ip = router_info_config["DEFAULT"]["ip"]
+ self.username = router_info_config["DEFAULT"]["username"]
+ self.password = router_info_config["DEFAULT"]["password"]
+ self.error_time = int(router_info_config["DEFAULT"]["error_time"])
+
+ if self.error_time > 0:
+ print("please check your password and manually reset error_time's value to 0 in the router_info_ini")
+ sys.exit(0)
+
+
+ def get_stok(self):
+ # 获取stok
+ get_stok_url = 'http://{}/cgi-bin/turbo/admin_web/login_admin?username={}&password={}'.format(self.ip,
+ self.username,
+ self.password)
+
+ stok_response = requests.get(get_stok_url)
+ # print(stok_response.json())
+ # self.stok = stok_response.json()["stok"]
+ self.remain_time = stok_response.json()["remaining_num"]
+ # print(self.remain_time)
+ if self.remain_time != 10:
+ router_info_config = configparser.ConfigParser()
+ router_info_config.read("./router_info.ini", encoding="utf-8")
+ router_info_config["DEFAULT"]["error_time"] = str(10-self.remain_time)
+ with open("./router_info.ini", "w") as file:
+ router_info_config.write(file)
+ print("please check your password and manually reset error_time's value to 0 in the router_info_ini")
+ sys.exit(0)
+
+ self.stok = stok_response.json()["stok"]
+ self.cookie = requests.utils.dict_from_cookiejar(stok_response.cookies)
+
+ def get_wan_ip(self):
+ wan_ip_url = "http://{}/cgi-bin/turbo{}/proxy/call".format(self.ip, self.stok)
+
+ params = {"_roundRobinUpdateD": ""}
+
+ headers = {
+ "Host": self.ip,
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
+ "Accept": "application/json, text/javascript, */*; q=0.01",
+ "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Type": "application/json",
+ "X-Requested-With": "XMLHttpRequest",
+ "X-KL-Ajax-Request": "Ajax_Request",
+ "Origin": "http://{}".format(self.ip),
+ "Connection": "keep-alive",
+ "Referer": "http://{}/cgi-bin/turbo{}/admin_web".format(self.ip, self.stok),
+ "Cookie": "sysauth={}".format(self.cookie['sysauth'])
+ }
+
+ post_json = {"muticall": "1", "mutiargs": [{"method": "wan.get_status", "data": {}}], "lang": "zh-CN",
+ "version": "v1"}
+
+ wan_ip_response = requests.post(wan_ip_url, headers=headers, params=params, json=post_json)
+
+ wan_ip = wan_ip_response.json()["data"]["results"][0]["result"]['data']["wan_ip"]
+ #with open("./wan_ip.txt", "w") as file:
+ # file.write(wan_ip)
+
+ print(wan_ip)
+ return wan_ip
+
+if __name__ == '__main__':
+ GetRouterWanIP.check_info_exist()
+ getRouterWanIP = GetRouterWanIP()
+ getRouterWanIP.get_stok()
+ getRouterWanIP.get_wan_ip()
diff --git a/porkbun_ddns.py b/porkbun_ddns.py
new file mode 100644
index 0000000..4d6f638
--- /dev/null
+++ b/porkbun_ddns.py
@@ -0,0 +1,210 @@
+import requests
+
+
+class PorkbunAPI:
+ def __init__(self, domain: "str", api_key: "str", secret_api_key: "str"):
+ self.domain = domain
+ self.__secret_api_key = secret_api_key
+ self.__api_key = api_key
+ self.__records = {}
+
+ @staticmethod
+ def ping_porkbun(self, verbose: "bool" = True):
+ ping_URI = "https://porkbun.com/api/json/v3/ping"
+ ping_json = {
+ "secretapikey": self.__secret_api_key,
+ "apikey": self.__api_key
+ }
+ response = requests.post(ping_URI, json=ping_json)
+
+ if verbose:
+ if response.status_code == 404:
+ print("Internet Failure !")
+ elif response.status_code == 200 and response.json()["status"] == "SUCCESS":
+ print("Internet Success! Your IP is ", response.json()["yourIP"])
+ else:
+ print("response code is ", response.status_code)
+ print(response.json())
+
+ return response.status_code
+
+ def retrieve(self, verbose: "bool" = True):
+ # 0 is DOMAIN
+ retrieve_URI = "https://porkbun.com/api/json/v3/dns/retrieve/{0}".format(self.domain)
+ retrieve_json = {
+ "secretapikey": self.__secret_api_key,
+ "apikey": self.__api_key
+ }
+ response = requests.post(retrieve_URI, json=retrieve_json)
+
+ if response.status_code == 404:
+ if verbose:
+ print("Internet Failure !")
+ elif response.status_code == 200 and response.json()["status"] == "SUCCESS":
+ records = response.json()["records"]
+ for record in records:
+ self.__records[record["id"]] = record
+
+ if verbose:
+ print(("{} " * 6).format("id".center(9), "name".center(48), "type".center(5), "content".center(40),
+ "ttl".center(5), "prio".center(4)))
+ for record in records:
+ print(("{} " * 6).format(str(record["id"]).center(9),
+ str(record["name"][:-len(self.domain) - 1]).center(48),
+ str(record["type"]).center(5),
+ str(record["content"][:40]).center(40) if len(
+ record["content"]) > 40 else str(record["content"]).center(40),
+ str(record["ttl"]).center(5), str(record["prio"]).center(4)))
+ else:
+ if verbose:
+ print("response code is ", response.status_code)
+ print(response.json())
+
+ return response.status_code
+
+ def find_record(self, _type: "str" = None, name: "str" = None, content: "str" = None, verbose: "bool" = True):
+ records = {}
+
+ def filter_fun(x):
+ if _type is not None and self.__records[x]["type"] != _type:
+ return False
+ if name is not None and self.__records[x]["name"][:-len(self.domain) - 1] != name:
+ return False
+ if content is not None and self.__records[x]["content"] != content:
+ return False
+ return True
+
+ for i in filter(filter_fun, self.__records):
+ records[i] = self.__records[i]
+
+ if verbose:
+ print(("{} " * 6).format("id".center(9), "name".center(48),
+ "type".center(5), "content".center(40),
+ "ttl".center(5), "prio".center(4)))
+ for record in records:
+ print(("{} " * 6).format(str(records[record]["id"]).center(9),
+ str(records[record]["name"][:-len(self.domain) - 1]).center(48),
+ str(records[record]["type"]).center(5),
+ str(records[record]["content"][:40]).center(40) if len(
+ records[record]["content"]) > 40 else str(
+ records[record]["content"]).center(40),
+ str(records[record]["ttl"]).center(5),
+ str(records[record]["prio"]).center(4)))
+ return records
+
+ def create(self, name: "str", _type: "str" = "A", content: "str" = "1.1.1.1", ttl: "int >= 600" = 600, prio=None):
+ # 0 is DOMAIN
+ create_URI = "https://porkbun.com/api/json/v3/dns/create/{0}".format(self.domain)
+ create_json = {
+ "secretapikey": self.__secret_api_key,
+ "apikey": self.__api_key,
+ "name": name,
+ "type": _type,
+ "content": content,
+ "ttl": ttl
+ }
+ if prio is not None:
+ create_json["prio"] = prio
+ print({"name": name,
+ "type": _type,
+ "content": content
+ })
+
+ response = requests.post(create_URI, json=create_json)
+
+ if response.status_code == 404:
+ print("Internet Failure !")
+ elif response.status_code == 200 and response.json()["status"] == "SUCCESS":
+ print("Create New Record Success! Your new record is ", response.json()["id"])
+ else:
+ print("response code is ", response.status_code)
+ print(response.json())
+
+ def edit(self, name: "str", _id: "str", _type: "str" = "A", content: "str" = "1.1.1.1", ttl: "int >= 600" = 600,
+ prio=None, verbose: "bool" = True):
+ # 0 is DOMAIN 1 is ID
+ edit_URI = "https://porkbun.com/api/json/v3/dns/edit/{0}/{1}".format(self.domain, _id)
+ edit_json = {
+ "secretapikey": self.__secret_api_key,
+ "apikey": self.__api_key,
+ "name": name,
+ "type": _type,
+ "content": content,
+ "ttl": ttl
+ }
+ if prio is not None:
+ edit_json["prio"] = prio
+
+ if verbose:
+ print({"name": name,
+ "type": _type,
+ "content": content
+ })
+
+ response = requests.post(edit_URI, json=edit_json)
+ if not verbose:
+ return
+
+ if response.status_code == 404:
+ print("Internet Failure !")
+ elif response.status_code == 200 and response.json()["status"] == "SUCCESS":
+ print("Edit Success!")
+ else:
+ print("response code is ", response.status_code)
+ print(response.json())
+
+ def delete(self, _id: "str", verbose: "bool" = True):
+ # 0 is DOMAIN 1 is ID
+ delete_URI = "https://porkbun.com/api/json/v3/dns/delete/{0}/{1}".format(self.domain, _id)
+ delete_json = {
+ "secretapikey": self.__secret_api_key,
+ "apikey": self.__api_key
+ }
+ # print(name + "." + self.domain)
+ response = requests.post(delete_URI, json=delete_json)
+
+ if not verbose:
+ return
+
+ if response.status_code == 404:
+ print("Internet Failure !")
+ elif response.status_code == 200 and response.json()["status"] == "SUCCESS":
+ print("Delete Record Success!")
+ else:
+ print("response code is ", response.status_code)
+ print(response.json())
+
+ def edit_A_with_name(self, name: "str", content: "str" = "1.1.1.1"):
+ self.retrieve(verbose=False)
+ result = self.find_record(_type="A", name=name, verbose=False)
+ if len(result) != 1:
+ print("Multiple records are selected")
+ self.find_record(_type="A", name=name)
+ else:
+ _id = [i for i in result.keys()][0]
+ old_content = result[_id]["content"]
+ if old_content != content:
+ self.edit(name=name, content=content, _type="A", _id=_id)
+
+
+ def edit_AAAA_with_name(self, name: "str", content: "str" = "1.1.1.1"):
+ self.retrieve(verbose=False)
+ result = self.find_record(_type="AAAA", name=name, verbose=False)
+ if len(result) != 1:
+ print("Multiple records are selected")
+ self.find_record(_type="AAAA", name=name)
+ else:
+ _id = [i for i in result.keys()][0]
+ old_content = result[_id]["content"]
+ if old_content != content:
+ self.edit(name=name, content=content, _type="AAAA", _id=_id)
+
+ def delete_A_with_name(self, name: "str"):
+ self.retrieve(verbose=False)
+ result = self.find_record(_type="A", name=name, verbose=False)
+ if len(result) != 1:
+ print("Multiple records are selected")
+ self.find_record(_type="A", name=name)
+ else:
+ _id = [i for i in result.keys()][0]
+ self.delete(_id=_id)
diff --git a/router_info.ini b/router_info.ini
new file mode 100644
index 0000000..aa81304
--- /dev/null
+++ b/router_info.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+ip = 192.168.1.1
+username = admin
+password = DX418418
+error_time = 1
+