2025-05-18 15:24:41 +08:00
|
|
|
"""
|
|
|
|
Peer
|
|
|
|
"""
|
2025-09-16 07:57:30 +08:00
|
|
|
import base64
|
2025-08-29 16:55:33 +08:00
|
|
|
import datetime
|
2025-09-16 07:57:30 +08:00
|
|
|
import json
|
2025-05-18 15:24:41 +08:00
|
|
|
import os, subprocess, uuid, random, re
|
2025-08-29 16:55:33 +08:00
|
|
|
from datetime import timedelta
|
2025-08-17 18:58:28 +08:00
|
|
|
|
|
|
|
import jinja2
|
2025-08-29 16:55:33 +08:00
|
|
|
import sqlalchemy as db
|
2025-05-18 15:24:41 +08:00
|
|
|
from .PeerJob import PeerJob
|
|
|
|
from .PeerShareLink import PeerShareLink
|
|
|
|
from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress
|
|
|
|
|
|
|
|
|
|
|
|
class Peer:
|
|
|
|
def __init__(self, tableData, configuration):
|
|
|
|
self.configuration = configuration
|
|
|
|
self.id = tableData["id"]
|
|
|
|
self.private_key = tableData["private_key"]
|
|
|
|
self.DNS = tableData["DNS"]
|
|
|
|
self.endpoint_allowed_ip = tableData["endpoint_allowed_ip"]
|
|
|
|
self.name = tableData["name"]
|
|
|
|
self.total_receive = tableData["total_receive"]
|
|
|
|
self.total_sent = tableData["total_sent"]
|
|
|
|
self.total_data = tableData["total_data"]
|
|
|
|
self.endpoint = tableData["endpoint"]
|
|
|
|
self.status = tableData["status"]
|
|
|
|
self.latest_handshake = tableData["latest_handshake"]
|
|
|
|
self.allowed_ip = tableData["allowed_ip"]
|
|
|
|
self.cumu_receive = tableData["cumu_receive"]
|
|
|
|
self.cumu_sent = tableData["cumu_sent"]
|
|
|
|
self.cumu_data = tableData["cumu_data"]
|
|
|
|
self.mtu = tableData["mtu"]
|
|
|
|
self.keepalive = tableData["keepalive"]
|
|
|
|
self.remote_endpoint = tableData["remote_endpoint"]
|
|
|
|
self.preshared_key = tableData["preshared_key"]
|
|
|
|
self.jobs: list[PeerJob] = []
|
|
|
|
self.ShareLink: list[PeerShareLink] = []
|
|
|
|
self.getJobs()
|
|
|
|
self.getShareLink()
|
|
|
|
|
|
|
|
def toJson(self):
|
2025-08-06 17:27:14 +08:00
|
|
|
# self.getJobs()
|
|
|
|
# self.getShareLink()
|
2025-05-18 15:24:41 +08:00
|
|
|
return self.__dict__
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return str(self.toJson())
|
|
|
|
|
|
|
|
def updatePeer(self, name: str, private_key: str,
|
|
|
|
preshared_key: str,
|
|
|
|
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
|
|
|
|
keepalive: int) -> tuple[bool, str] or tuple[bool, None]:
|
|
|
|
if not self.configuration.getStatus():
|
|
|
|
self.configuration.toggleConfiguration()
|
|
|
|
|
|
|
|
existingAllowedIps = [item for row in list(
|
|
|
|
map(lambda x: [q.strip() for q in x.split(',')],
|
|
|
|
map(lambda y: y.allowed_ip,
|
|
|
|
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
|
|
|
|
|
|
|
|
if allowed_ip in existingAllowedIps:
|
|
|
|
return False, "Allowed IP already taken by another peer"
|
2025-08-06 17:27:14 +08:00
|
|
|
|
2025-05-18 15:24:41 +08:00
|
|
|
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
|
|
|
|
return False, f"Endpoint Allowed IPs format is incorrect"
|
2025-08-06 17:27:14 +08:00
|
|
|
|
2025-05-18 15:24:41 +08:00
|
|
|
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
|
|
|
|
return False, f"DNS format is incorrect"
|
2025-07-20 20:52:37 +08:00
|
|
|
|
2025-09-14 15:38:19 +08:00
|
|
|
if type(mtu) is str or mtu is None:
|
2025-07-20 20:52:37 +08:00
|
|
|
mtu = 0
|
|
|
|
|
2025-05-18 15:24:41 +08:00
|
|
|
if mtu < 0 or mtu > 1460:
|
|
|
|
return False, "MTU format is not correct"
|
2025-07-20 20:52:37 +08:00
|
|
|
|
2025-09-14 15:38:19 +08:00
|
|
|
if type(keepalive) is str or keepalive is None:
|
2025-07-20 20:52:37 +08:00
|
|
|
keepalive = 0
|
|
|
|
|
2025-05-18 15:24:41 +08:00
|
|
|
if keepalive < 0:
|
|
|
|
return False, "Persistent Keepalive format is not correct"
|
|
|
|
if len(private_key) > 0:
|
|
|
|
pubKey = GenerateWireguardPublicKey(private_key)
|
|
|
|
if not pubKey[0] or pubKey[1] != self.id:
|
|
|
|
return False, "Private key does not match with the public key"
|
|
|
|
try:
|
|
|
|
rd = random.Random()
|
|
|
|
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
|
|
|
|
pskExist = len(preshared_key) > 0
|
|
|
|
|
|
|
|
if pskExist:
|
|
|
|
with open(uid, "w+") as f:
|
|
|
|
f.write(preshared_key)
|
|
|
|
newAllowedIPs = allowed_ip.replace(" ", "")
|
|
|
|
updateAllowedIp = subprocess.check_output(
|
|
|
|
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
|
|
|
|
shell=True, stderr=subprocess.STDOUT)
|
|
|
|
|
|
|
|
if pskExist: os.remove(uid)
|
|
|
|
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
|
|
|
return False, "Update peer failed when updating Allowed IPs"
|
|
|
|
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
|
|
|
|
shell=True, stderr=subprocess.STDOUT)
|
|
|
|
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
|
|
|
return False, "Update peer failed when saving the configuration"
|
|
|
|
with self.configuration.engine.begin() as conn:
|
|
|
|
conn.execute(
|
|
|
|
self.configuration.peersTable.update().values({
|
|
|
|
"name": name,
|
|
|
|
"private_key": private_key,
|
|
|
|
"DNS": dns_addresses,
|
|
|
|
"endpoint_allowed_ip": endpoint_allowed_ip,
|
|
|
|
"mtu": mtu,
|
|
|
|
"keepalive": keepalive,
|
|
|
|
"preshared_key": preshared_key
|
|
|
|
}).where(
|
|
|
|
self.configuration.peersTable.c.id == self.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return True, None
|
|
|
|
except subprocess.CalledProcessError as exc:
|
|
|
|
return False, exc.output.decode("UTF-8").strip()
|
|
|
|
|
|
|
|
def downloadPeer(self) -> dict[str, str]:
|
2025-09-16 07:57:30 +08:00
|
|
|
final = {
|
|
|
|
"fileName": "",
|
|
|
|
"file": ""
|
|
|
|
}
|
2025-05-18 15:24:41 +08:00
|
|
|
filename = self.name
|
|
|
|
if len(filename) == 0:
|
|
|
|
filename = "UntitledPeer"
|
|
|
|
filename = "".join(filename.split(' '))
|
|
|
|
filename = f"{filename}"
|
|
|
|
illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3",
|
|
|
|
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
|
|
|
|
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
|
|
|
|
for i in illegal_filename:
|
|
|
|
filename = filename.replace(i, "")
|
|
|
|
|
|
|
|
for i in filename:
|
|
|
|
if re.match("^[a-zA-Z0-9_=+.-]$", i):
|
2025-09-16 07:57:30 +08:00
|
|
|
final["fileName"] += i
|
2025-07-20 20:52:37 +08:00
|
|
|
|
|
|
|
interfaceSection = {
|
|
|
|
"PrivateKey": self.private_key,
|
|
|
|
"Address": self.allowed_ip,
|
2025-08-17 18:58:28 +08:00
|
|
|
"MTU": (
|
|
|
|
self.configuration.configurationInfo.OverridePeerSettings.MTU
|
|
|
|
if self.configuration.configurationInfo.OverridePeerSettings.MTU else self.mtu
|
|
|
|
),
|
2025-08-16 16:54:31 +08:00
|
|
|
"DNS": (
|
|
|
|
self.configuration.configurationInfo.OverridePeerSettings.DNS
|
|
|
|
if self.configuration.configurationInfo.OverridePeerSettings.DNS else self.DNS
|
|
|
|
)
|
2025-07-20 20:52:37 +08:00
|
|
|
}
|
2025-09-16 07:46:25 +08:00
|
|
|
|
|
|
|
if self.configuration.Protocol == "awg":
|
|
|
|
interfaceSection.update({
|
|
|
|
"Jc": self.configuration.Jc,
|
|
|
|
"Jmin": self.configuration.Jmin,
|
|
|
|
"Jmax": self.configuration.Jmax,
|
|
|
|
"S1": self.configuration.S1,
|
|
|
|
"S2": self.configuration.S2,
|
|
|
|
"H1": self.configuration.H1,
|
|
|
|
"H2": self.configuration.H2,
|
|
|
|
"H3": self.configuration.H3,
|
|
|
|
"H4": self.configuration.H4
|
|
|
|
})
|
|
|
|
|
2025-07-20 20:52:37 +08:00
|
|
|
peerSection = {
|
|
|
|
"PublicKey": self.configuration.PublicKey,
|
2025-08-17 18:58:28 +08:00
|
|
|
"AllowedIPs": (
|
|
|
|
self.configuration.configurationInfo.OverridePeerSettings.EndpointAllowedIPs
|
|
|
|
if self.configuration.configurationInfo.OverridePeerSettings.EndpointAllowedIPs else self.endpoint_allowed_ip
|
|
|
|
),
|
|
|
|
"Endpoint": f'{(self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint if self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint else self.configuration.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1])}:{(self.configuration.configurationInfo.OverridePeerSettings.ListenPort if self.configuration.configurationInfo.OverridePeerSettings.ListenPort else self.configuration.ListenPort)}',
|
|
|
|
"PersistentKeepalive": (
|
|
|
|
self.configuration.configurationInfo.OverridePeerSettings.PersistentKeepalive
|
|
|
|
if self.configuration.configurationInfo.OverridePeerSettings.PersistentKeepalive
|
|
|
|
else self.keepalive
|
|
|
|
),
|
2025-07-20 20:52:37 +08:00
|
|
|
"PresharedKey": self.preshared_key
|
|
|
|
}
|
|
|
|
combine = [interfaceSection.items(), peerSection.items()]
|
|
|
|
for s in range(len(combine)):
|
|
|
|
if s == 0:
|
2025-09-16 07:57:30 +08:00
|
|
|
final["file"] += "[Interface]\n"
|
2025-07-20 20:52:37 +08:00
|
|
|
else:
|
2025-09-16 07:57:30 +08:00
|
|
|
final["file"] += "\n[Peer]\n"
|
2025-07-20 20:52:37 +08:00
|
|
|
for (key, val) in combine[s]:
|
|
|
|
if val is not None and ((type(val) is str and len(val) > 0) or (type(val) is int and val > 0)):
|
2025-09-16 07:57:30 +08:00
|
|
|
final["file"] += f"{key} = {val}\n"
|
2025-09-17 16:35:57 +08:00
|
|
|
|
|
|
|
final["file"] = jinja2.Template(final["file"]).render(configuration=self.configuration)
|
|
|
|
|
|
|
|
|
2025-09-16 07:57:30 +08:00
|
|
|
if self.configuration.Protocol == "awg":
|
|
|
|
final["amneziaVPN"] = json.dumps({
|
|
|
|
"containers": [{
|
|
|
|
"awg": {
|
|
|
|
"isThirdPartyConfig": True,
|
|
|
|
"last_config": final['file'],
|
|
|
|
"port": self.configuration.ListenPort,
|
|
|
|
"transport_proto": "udp"
|
|
|
|
},
|
|
|
|
"container": "amnezia-awg"
|
|
|
|
}],
|
|
|
|
"defaultContainer": "amnezia-awg",
|
|
|
|
"description": self.name,
|
|
|
|
"hostName": (
|
|
|
|
self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint
|
|
|
|
if self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint
|
|
|
|
else self.configuration.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1])
|
|
|
|
})
|
|
|
|
return final
|
2025-05-18 15:24:41 +08:00
|
|
|
|
|
|
|
def getJobs(self):
|
|
|
|
self.jobs = self.configuration.AllPeerJobs.searchJob(self.configuration.Name, self.id)
|
|
|
|
|
|
|
|
def getShareLink(self):
|
|
|
|
self.ShareLink = self.configuration.AllPeerShareLinks.getLink(self.configuration.Name, self.id)
|
|
|
|
|
2025-09-07 21:43:49 +08:00
|
|
|
def resetDataUsage(self, mode: str):
|
2025-05-18 15:24:41 +08:00
|
|
|
try:
|
|
|
|
with self.configuration.engine.begin() as conn:
|
2025-09-07 21:43:49 +08:00
|
|
|
if mode == "total":
|
2025-05-18 15:24:41 +08:00
|
|
|
conn.execute(
|
|
|
|
self.configuration.peersTable.update().values({
|
|
|
|
"total_data": 0,
|
|
|
|
"cumu_data": 0,
|
|
|
|
"total_receive": 0,
|
|
|
|
"cumu_receive": 0,
|
|
|
|
"total_sent": 0,
|
|
|
|
"cumu_sent": 0
|
|
|
|
}).where(
|
|
|
|
self.configuration.peersTable.c.id == self.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.total_data = 0
|
|
|
|
self.total_receive = 0
|
|
|
|
self.total_sent = 0
|
|
|
|
self.cumu_data = 0
|
|
|
|
self.cumu_sent = 0
|
|
|
|
self.cumu_receive = 0
|
2025-09-07 21:43:49 +08:00
|
|
|
elif mode == "receive":
|
2025-05-18 15:24:41 +08:00
|
|
|
conn.execute(
|
|
|
|
self.configuration.peersTable.update().values({
|
|
|
|
"total_receive": 0,
|
|
|
|
"cumu_receive": 0,
|
|
|
|
}).where(
|
|
|
|
self.configuration.peersTable.c.id == self.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.cumu_receive = 0
|
|
|
|
self.total_receive = 0
|
2025-09-07 21:43:49 +08:00
|
|
|
elif mode == "sent":
|
2025-05-18 15:24:41 +08:00
|
|
|
conn.execute(
|
|
|
|
self.configuration.peersTable.update().values({
|
|
|
|
"total_sent": 0,
|
|
|
|
"cumu_sent": 0
|
|
|
|
}).where(
|
|
|
|
self.configuration.peersTable.c.id == self.id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.cumu_sent = 0
|
|
|
|
self.total_sent = 0
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
return False
|
2025-08-29 16:55:33 +08:00
|
|
|
return True
|
|
|
|
|
2025-09-07 17:04:22 +08:00
|
|
|
def getEndpoints(self):
|
|
|
|
result = []
|
|
|
|
with self.configuration.engine.connect() as conn:
|
|
|
|
result = conn.execute(
|
|
|
|
db.select(
|
|
|
|
self.configuration.peersHistoryEndpointTable.c.endpoint
|
|
|
|
).group_by(
|
|
|
|
self.configuration.peersHistoryEndpointTable.c.endpoint
|
|
|
|
).where(
|
|
|
|
self.configuration.peersHistoryEndpointTable.c.id == self.id
|
|
|
|
)
|
|
|
|
).mappings().fetchall()
|
|
|
|
return list(result)
|
|
|
|
|
2025-09-01 17:16:03 +08:00
|
|
|
def getTraffics(self, interval: int = 30, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
|
|
|
|
if startDate is None and endDate is None:
|
|
|
|
endDate = datetime.datetime.now()
|
|
|
|
startDate = endDate - timedelta(minutes=interval)
|
|
|
|
else:
|
|
|
|
endDate = endDate.replace(hour=23, minute=59, second=59, microsecond=999999)
|
|
|
|
startDate = startDate.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
|
|
with self.configuration.engine.connect() as conn:
|
|
|
|
result = conn.execute(
|
|
|
|
db.select(
|
|
|
|
self.configuration.peersTransferTable.c.cumu_data,
|
|
|
|
self.configuration.peersTransferTable.c.total_data,
|
|
|
|
self.configuration.peersTransferTable.c.cumu_receive,
|
|
|
|
self.configuration.peersTransferTable.c.total_receive,
|
|
|
|
self.configuration.peersTransferTable.c.cumu_sent,
|
|
|
|
self.configuration.peersTransferTable.c.total_sent,
|
|
|
|
self.configuration.peersTransferTable.c.time
|
|
|
|
).where(
|
|
|
|
db.and_(
|
|
|
|
self.configuration.peersTransferTable.c.id == self.id,
|
|
|
|
self.configuration.peersTransferTable.c.time <= endDate,
|
|
|
|
self.configuration.peersTransferTable.c.time >= startDate,
|
|
|
|
)
|
|
|
|
).order_by(
|
|
|
|
self.configuration.peersTransferTable.c.time
|
|
|
|
)
|
|
|
|
).mappings().fetchall()
|
|
|
|
return list(result)
|
|
|
|
|
|
|
|
|
2025-08-29 16:55:33 +08:00
|
|
|
def getSessions(self, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
|
|
|
|
if endDate is None:
|
|
|
|
endDate = datetime.datetime.now()
|
|
|
|
|
|
|
|
if startDate is None:
|
2025-09-01 17:16:03 +08:00
|
|
|
startDate = endDate
|
2025-08-29 16:55:33 +08:00
|
|
|
|
|
|
|
endDate = endDate.replace(hour=23, minute=59, second=59, microsecond=999999)
|
|
|
|
startDate = startDate.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
2025-09-01 17:16:03 +08:00
|
|
|
|
2025-08-29 16:55:33 +08:00
|
|
|
with self.configuration.engine.connect() as conn:
|
|
|
|
result = conn.execute(
|
|
|
|
db.select(
|
|
|
|
self.configuration.peersTransferTable.c.time
|
|
|
|
).where(
|
|
|
|
db.and_(
|
|
|
|
self.configuration.peersTransferTable.c.id == self.id,
|
|
|
|
self.configuration.peersTransferTable.c.time <= endDate,
|
|
|
|
self.configuration.peersTransferTable.c.time >= startDate,
|
|
|
|
)
|
|
|
|
).order_by(
|
|
|
|
self.configuration.peersTransferTable.c.time
|
|
|
|
)
|
|
|
|
).fetchall()
|
2025-09-01 17:16:03 +08:00
|
|
|
time = list(map(lambda x : x[0], result))
|
|
|
|
return time
|
2025-08-29 16:55:33 +08:00
|
|
|
|
|
|
|
def __duration(self, t1: datetime.datetime, t2: datetime.datetime):
|
|
|
|
delta = t1 - t2
|
|
|
|
|
|
|
|
hours, remainder = divmod(delta.total_seconds(), 3600)
|
|
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
|