283 lines
9.0 KiB
Python
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") |