This repository has been archived on 2023-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
NTP_Daemon/ntp_daemon.py
2022-06-04 19:25:08 +08:00

283 lines
9.0 KiB
Python

import socket, struct, threading, time, json, logging
# flask, requests
import flask, requests
RECORD_UPDATE_SWITCH = True
API_LISTEN = ("0.0.0.0", 5000)
INTERVAL = 60 * 60
DOMAIN = ""
A_ID = ""
A_KEY = ""
app = flask.Flask("NTP")
log = logging.getLogger('werkzeug')
log.disabled = True
if RECORD_UPDATE_SWITCH:
# alibabacloud_alidns20150109==2.0.2
from alibabacloud_alidns20150109.client import Client as Alidns20150109Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_alidns20150109 import models as alidns_20150109_models
from alibabacloud_tea_util import models as util_models
class Domain:
def __init__(self):
pass
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> Alidns20150109Client:
config = open_api_models.Config(
access_key_id=access_key_id,
access_key_secret=access_key_secret
)
config.endpoint = f'alidns.cn-hangzhou.aliyuncs.com'
return Alidns20150109Client(config)
@staticmethod
def get_records():
client = Domain.create_client(A_ID, A_KEY)
describe_domain_records_request = alidns_20150109_models.DescribeDomainRecordsRequest(
domain_name=DOMAIN
)
runtime = util_models.RuntimeOptions()
try:
return True, eval(str(client.describe_domain_records_with_options(describe_domain_records_request, runtime)))["body"]["DomainRecords"]["Record"]
except Exception as error:
return False, error
@staticmethod
def delete_record(rr) -> None:
client = Domain.create_client(A_ID, A_KEY)
delete_sub_domain_records_request = alidns_20150109_models.DeleteSubDomainRecordsRequest(
domain_name=DOMAIN,
rr=rr
)
runtime = util_models.RuntimeOptions()
try:
return True, eval(str(client.describe_domain_records_with_options(client.delete_sub_domain_records_with_options(delete_sub_domain_records_request, runtime))))
except Exception as error:
return False, error
@staticmethod
def add_record(rr, type, value, line) -> None:
client = Domain.create_client(A_ID, A_KEY)
add_domain_record_request = alidns_20150109_models.AddDomainRecordRequest(
domain_name=DOMAIN,
rr=rr,
type=type,
value=value,
line=line
)
runtime = util_models.RuntimeOptions()
try:
return True, eval(str(client.add_domain_record_with_options(add_domain_record_request, runtime)))
except Exception as error:
return False, error
def is_ipv4(ip):
try:
socket.inet_pton(socket.AF_INET, ip)
except AttributeError:
try:
socket.inet_aton(ip)
except socket.error:
return False
return ip.count('.') == 3
except socket.error:
return False
return True
def is_ipv6(ip):
try:
socket.inet_pton(socket.AF_INET6, ip)
except socket.error:
return False
return True
def ip_type(ip):
if is_ipv4(ip) and not is_ipv6(ip):
return 4
elif is_ipv6(ip) and not is_ipv4(ip):
return 6
else:
return 0
def ip_area(ip):
type = ip_type(ip)
if type == 4 or type == 6:
info = requests.get(f"https://api.ghink.net/ip/zh_cn/v{type}/{ip}").json()
if info["code"] != 20000:
return "default"
info = info["content"]
if type == 4:
if info["pro"] == "":
return "oversea"
elif "教育" in info["addr"] or "学院" in info["addr"] or "大学" in info["addr"]:
return "edu"
elif "电信" in info["addr"]:
return "telecom"
elif "移动" in info["addr"] or "移通" in info["addr"]:
return "mobile"
elif "联通" in info["addr"]:
return "unicom"
else:
return "default"
elif type == 6:
if info["pro"] == "" and info["city"] == "":
return "oversea"
elif "教育" in info["addr"] or "学院" in info["addr"] or "大学" in info["addr"]:
return "edu"
elif "电信" in info["addr"]:
return "telecom"
elif "移动" in info["addr"] or "移通" in info["addr"]:
return "mobile"
elif "联通" in info["addr"]:
return "unicom"
else:
return "default"
else:
return "default"
def ntp(server):
type = ip_type(server)
if type == 4:
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
elif type == 6:
client = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = ("\x1b" + 47 * "\0").encode()
try:
client.sendto(data, (server, 123))
except socket.gaierror:
return False
data, address = client.recvfrom(1024)
if data:
t = struct.unpack('!12I', data)[10]
t -= 2208988800
return t
else:
return False
def check(server):
global active
if ntp(server):
if server not in active:
active.append(server)
else:
if server in active:
del active[active.index(server)]
def update_status():
global active, servers
# Read Server Pool
servers = []
with open("pool.txt", "r") as fb:
servers = [s.strip() for s in fb.readlines()]
# Duplicate Removal
temp = []
for s in servers:
if s not in temp:
temp.append(s)
servers = temp
with open("pool.txt", "w+") as fb:
for s in servers:
fb.write(f"{s}\n")
# Check
for s in active:
thread = threading.Thread(target=check, args=(s, ), name=s)
thread.start()
thread.join(1)
with open("pool.txt", "r") as fb:
for line in fb.readlines():
thread = threading.Thread(target=check, args=(line.strip(), ), name=line.strip())
thread.start()
thread.join(1)
def update_status_daemon():
while True:
update_status()
time.sleep(INTERVAL)
def update_records():
ok, records = Domain.get_records()
if ok:
for r in records:
if r["Value"] not in servers:
ok, msg = Domain.delete_record(r["Value"])
if not ok:
print(msg)
for s in active:
flag = True
for r in records:
if r["Value"] == s:
flag = False
if flag:
ok, msg = Domain.add_record("@", "A" if ip_type(s) == 4 else "AAAA", s, ip_area(s))
if not ok and "The maximum number of 10 records is exceeded" not in str(msg) and "The DNS record already exists." not in str(msg):
print(msg)
else:
print(records)
def update_records_daemon():
while True:
update_records()
time.sleep(10)
@app.route("/update")
def update_api():
update_thread = threading.Thread(target=update_status, name="Update From API For Status")
update_thread.daemon = True
update_thread.start()
if RECORD_UPDATE_SWITCH:
update_thread = threading.Thread(target=update_records, name="Update From API For Records")
update_thread.daemon = True
update_thread.start()
return json.dumps(
{
"message": "Success",
"content": None
}
), 200, {"content-type": "application/json"}
@app.route("/list")
def list_api():
return json.dumps(
{
"message": "Success",
"content": active
}
), 200, {"content-type": "application/json"}
if __name__ == '__main__':
active = []
servers = []
update_status_daemon_thread = threading.Thread(target=update_status_daemon, name="Update Daemon For Status")
update_status_daemon_thread.daemon = True
update_status_daemon_thread.start()
if RECORD_UPDATE_SWITCH:
update_status_daemon_thread = threading.Thread(target=update_records_daemon, name="Update Daemon For Records")
update_status_daemon_thread.daemon = True
update_status_daemon_thread.start()
api_daemon = threading.Thread(target=app.run, args=API_LISTEN, name="API Daemon")
api_daemon.daemon = True
api_daemon.start()
while True:
if input() == "exit":
break
print(f"Report {time.ctime(time.time())}:")
print(f"{len(active)} Servers Active, They are:")
for i in range(len(active)):
if not (i+1) % 5:
print(active[i])
else:
print(active[i], end=" ")
print("", end="" if not len(active) % 5 or not len(active) else "\n")