2022-12-12 19:40:30 +01:00
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import base64
|
|
|
|
import logging
|
|
|
|
import random
|
2022-12-21 21:21:57 +01:00
|
|
|
import threading
|
|
|
|
import requests
|
2022-12-12 19:40:30 +01:00
|
|
|
|
2022-12-21 22:52:56 +01:00
|
|
|
|
2022-12-12 19:40:30 +01:00
|
|
|
class NodeCommunicator:
|
|
|
|
|
2022-12-21 22:52:56 +01:00
|
|
|
def __init__(self, name: str, config):
|
2022-12-12 19:40:30 +01:00
|
|
|
self.name = name
|
|
|
|
self.__config = config
|
|
|
|
self.__api_addr = config["api-con"]
|
2022-12-21 22:52:56 +01:00
|
|
|
|
|
|
|
def update(self, action: str, peering: dict):
|
2023-06-14 08:52:17 +02:00
|
|
|
try:
|
|
|
|
if action == "add":
|
|
|
|
req = requests.post(f"{self.__api_addr}peerings", json=peering, timeout=6)
|
|
|
|
elif action == "update":
|
|
|
|
req = requests.put(f"{self.__api_addr}peerings", json=peering, timeout=6)
|
|
|
|
elif action == "delete":
|
|
|
|
req = requests.delete(f"{self.__api_addr}peerings", json=peering, timeout=6)
|
|
|
|
else:
|
|
|
|
return 400
|
|
|
|
|
|
|
|
# should only get here if a request was "successful" (not necessarily http code 200)
|
|
|
|
return req.status_code
|
|
|
|
except requests.exceptions.ReadTimeout:
|
|
|
|
# if it took more than timeout for the client to respond
|
|
|
|
return 504
|
2022-12-21 22:52:56 +01:00
|
|
|
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
class PeeringManager:
|
|
|
|
|
|
|
|
def __init__(self, config):
|
|
|
|
self.__config = config
|
|
|
|
self.__peering_file = config["peerings"]
|
|
|
|
|
|
|
|
self.__load_peerings()
|
|
|
|
|
|
|
|
self._nodes = {}
|
|
|
|
for node in self.__config["nodes"]:
|
2022-12-21 22:52:56 +01:00
|
|
|
self._nodes[node] = NodeCommunicator(
|
|
|
|
name=node, config=self.__config["nodes"][node])
|
|
|
|
|
2022-12-12 19:40:30 +01:00
|
|
|
self._amounts = None
|
2022-12-21 21:21:57 +01:00
|
|
|
self._threads = []
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
def __load_peerings(self):
|
|
|
|
if not os.path.exists(self.__peering_file):
|
|
|
|
with open(self.__peering_file, "x") as p:
|
2024-09-01 01:02:11 +02:00
|
|
|
json.dump({"mntner": {}, "asn": {}}, p)
|
2022-12-12 19:40:30 +01:00
|
|
|
try:
|
|
|
|
with open(self.__peering_file, "r") as p:
|
|
|
|
self.peerings = json.load(p)
|
2024-09-01 01:02:11 +02:00
|
|
|
# migration due to typo in "mnter" key:
|
|
|
|
if "mnter" in self.peerings:
|
|
|
|
print("WARN: migrating peering data (Typo mnter)")
|
|
|
|
self.peerings = {"mntner": self.peerings["mnter"], "asn": self.peerings["asn"]}
|
|
|
|
except json.decoder.JSONDecodeError as e:
|
|
|
|
print("CRITICAL: potentially corrupted peering database, aborting:")
|
|
|
|
print(e.args)
|
|
|
|
exit(1)
|
|
|
|
# with open(self.__peering_file, "w") as p:
|
|
|
|
# json.dump({"mntner": {}, "asn": {}}, p)
|
|
|
|
# with open(self.__peering_file, "r") as p:
|
|
|
|
# self.peerings = json.load(p)
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
# self.peerings = {}
|
|
|
|
# missing_peerings = False
|
|
|
|
# for peering in self._peerings:
|
|
|
|
# if os.path.exists(f"{self._peering_dir}/{peering}.json"):
|
|
|
|
# with open(f"{self._peering_dir}/{peering}.json") as peer_cfg:
|
|
|
|
# self.peerings[peering] = json.load(peer_cfg)
|
|
|
|
# else:
|
|
|
|
# logging.warning(f"peering with id {peering} doesn't exist. removing reference in `{self._peering_dir}/peerings.json`")
|
|
|
|
# self._peerings.remove(peering)
|
|
|
|
# missing_peerings = True
|
|
|
|
# if missing_peerings:
|
|
|
|
# with open(f"{self._peering_dir}/peerings.json","w") as p:
|
|
|
|
# json.dump(self._peerings, p, indent=4)
|
|
|
|
def _save_peerings(self):
|
|
|
|
with open(self.__peering_file, "w") as p:
|
|
|
|
json.dump(self.peerings, p, indent=4)
|
|
|
|
self._amounts = None
|
|
|
|
|
2022-12-21 22:52:56 +01:00
|
|
|
def _update_nodes(self, action: str, peering, new_peering=None):
|
2022-12-12 19:40:30 +01:00
|
|
|
"""mode: "add","update","delete
|
|
|
|
peering: peering to send to node (included in peering)
|
|
|
|
new_peering: if mode=="update" the new peering to update to
|
|
|
|
"""
|
2022-12-21 21:21:57 +01:00
|
|
|
if peering["node"] in self._nodes:
|
2023-06-14 08:52:17 +02:00
|
|
|
return_code = self._nodes[peering["node"]].update(action=action, peering = peering if not new_peering else new_peering)
|
|
|
|
if return_code in [200, 201]:
|
|
|
|
return True, return_code
|
|
|
|
else:
|
|
|
|
return False, return_code
|
2022-12-21 22:52:56 +01:00
|
|
|
else:
|
2023-06-14 08:52:17 +02:00
|
|
|
return False, 404
|
2022-12-21 21:21:57 +01:00
|
|
|
|
2022-12-12 19:40:30 +01:00
|
|
|
def _update_amounts(self):
|
|
|
|
__new = {}
|
|
|
|
for asn in self.peerings["asn"]:
|
|
|
|
for peering in self.peerings["asn"][asn]:
|
2024-09-01 01:02:11 +02:00
|
|
|
if peering["node"] not in __new:
|
2022-12-12 19:40:30 +01:00
|
|
|
__new[peering["node"]] = 0
|
|
|
|
__new[peering["node"]] += 1
|
|
|
|
self._amounts = __new
|
|
|
|
|
|
|
|
def amount_by_node(self, node_name: str):
|
2024-09-01 01:02:11 +02:00
|
|
|
if self._amounts is None:
|
2022-12-12 19:40:30 +01:00
|
|
|
self._update_amounts()
|
|
|
|
try:
|
|
|
|
return self._amounts[node_name]
|
|
|
|
except KeyError:
|
|
|
|
return 0
|
2022-12-21 22:52:56 +01:00
|
|
|
|
2024-09-01 14:25:08 +02:00
|
|
|
def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=None, bgp_enh=None):
|
2022-12-12 19:40:30 +01:00
|
|
|
"""checks if a peerings with specific data already exists"""
|
|
|
|
# check if mnt is specified, already exists in the database and if that mnt has the specified ASn -> if not: return False
|
2024-09-01 01:02:11 +02:00
|
|
|
if mnt and not (mnt in self.peerings["mntner"] and asn in self.peerings["mntner"][mnt]):
|
2022-12-12 19:40:30 +01:00
|
|
|
return False
|
|
|
|
selected_peerings = self.peerings["asn"][asn]
|
|
|
|
# check if the ASn even has peerings
|
|
|
|
if len(selected_peerings) == 0:
|
|
|
|
return False
|
|
|
|
for p in selected_peerings:
|
|
|
|
if p["node"] == node:
|
|
|
|
if (not wg_key or p["wg_key"] == wg_key) and (not endpoint or p["endpoint"] == endpoint) \
|
|
|
|
and (not ipv6ll or p["ipv6ll"] == ipv6ll) and (not ipv4 or p["ipv4"] == ipv4) and (not ipv6 or p["ipv6"] == ipv6)\
|
|
|
|
and (not bgp_mp or p["bgp_mp"] == bgp_mp) and (not bgp_enh or p["bgp_enh"] == bgp_enh):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_peerings_by_mnt(self, mnt):
|
|
|
|
# print(self.peerings)
|
|
|
|
try:
|
|
|
|
out = []
|
2024-09-01 01:02:11 +02:00
|
|
|
# if admin is logged in: return all
|
|
|
|
if mnt == self.__config["MNT"]:
|
|
|
|
for mntner in self.peerings["mntner"]:
|
|
|
|
for asn in self.peerings["mntner"][mntner]:
|
|
|
|
try:
|
|
|
|
for peering in self.peerings["asn"][asn]:
|
|
|
|
out.append(peering)
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
for asn in self.peerings["mntner"][mnt]:
|
|
|
|
try:
|
|
|
|
for peering in self.peerings["asn"][asn]:
|
|
|
|
out.append(peering)
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2022-12-12 19:40:30 +01:00
|
|
|
return out
|
|
|
|
except KeyError:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def add_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
|
|
|
|
# check if this MNT already has a/this asn
|
|
|
|
try:
|
2024-09-01 01:02:11 +02:00
|
|
|
if asn not in self.peerings["mntner"][mnt]:
|
2022-12-12 19:40:30 +01:00
|
|
|
# ... and add it if it hasn't
|
|
|
|
self.peerings[mnt].append(asn)
|
|
|
|
except KeyError:
|
|
|
|
# ... and cerate it if it doesn't have any yet
|
2024-09-01 01:02:11 +02:00
|
|
|
self.peerings["mntner"][mnt] = [asn]
|
2022-12-12 19:40:30 +01:00
|
|
|
try:
|
2024-09-01 01:02:11 +02:00
|
|
|
if asn not in self.peerings["asn"]:
|
2022-12-12 19:40:30 +01:00
|
|
|
self.peerings["asn"][asn] = []
|
|
|
|
except KeyError:
|
|
|
|
self.peerings["asn"][asn] = []
|
|
|
|
|
|
|
|
# deny more than one peering per ASN to one node
|
|
|
|
for peering in self.peerings["asn"][asn]:
|
|
|
|
if peering["node"] == node:
|
2023-07-21 11:08:27 +02:00
|
|
|
return False, 409
|
2022-12-12 19:40:30 +01:00
|
|
|
|
2022-12-21 22:52:56 +01:00
|
|
|
peering = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key, "endpoint": endpoint,
|
|
|
|
"ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh}
|
2023-06-14 08:52:17 +02:00
|
|
|
success, code = self._update_nodes("add", peering=peering)
|
|
|
|
if not success:
|
|
|
|
# if ssomething failed notify user and don't add it to the list
|
|
|
|
return False, code
|
|
|
|
#
|
2022-12-21 22:52:56 +01:00
|
|
|
self.peerings["asn"][asn].append(peering)
|
2022-12-12 19:40:30 +01:00
|
|
|
self._save_peerings()
|
2023-06-14 08:52:17 +02:00
|
|
|
return True, code
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
def update_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
|
|
|
|
# check if this MNT already has a/this asn
|
|
|
|
try:
|
2024-09-01 01:02:11 +02:00
|
|
|
if asn not in self.peerings["mntner"][mnt]:
|
2022-12-12 19:40:30 +01:00
|
|
|
# ... and add it if it hasn't
|
|
|
|
self.peerings[mnt].append(asn)
|
|
|
|
except KeyError:
|
2023-06-14 08:52:17 +02:00
|
|
|
# ... and create it if it doesn't have any yet
|
2024-09-01 01:02:11 +02:00
|
|
|
self.peerings["mntner"][mnt] = [asn]
|
2022-12-12 19:40:30 +01:00
|
|
|
try:
|
2023-06-14 08:52:17 +02:00
|
|
|
# there are no peerings for this asn -> can't edit nothing...
|
2024-09-01 01:02:11 +02:00
|
|
|
if asn not in self.peerings["asn"]:
|
2023-06-14 08:52:17 +02:00
|
|
|
return False, 404
|
2022-12-12 19:40:30 +01:00
|
|
|
except KeyError:
|
2023-06-14 08:52:17 +02:00
|
|
|
# this should only happen if "asn" not in self.peerings
|
|
|
|
return False, 404
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
success = False
|
|
|
|
for pNr in range(len(self.peerings["asn"][asn])):
|
|
|
|
if self.peerings["asn"][asn][pNr]["node"] == node:
|
2022-12-21 21:21:57 +01:00
|
|
|
old_peering = self.peerings["asn"][asn][pNr]
|
|
|
|
new_peering = self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key,
|
2022-12-21 22:52:56 +01:00
|
|
|
"endpoint": endpoint, "ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh}
|
2022-12-12 19:40:30 +01:00
|
|
|
success = True
|
2023-06-14 08:52:17 +02:00
|
|
|
peering_number = pNr
|
2022-12-12 19:40:30 +01:00
|
|
|
if not success:
|
|
|
|
return False
|
2023-06-14 08:52:17 +02:00
|
|
|
# notify the node
|
|
|
|
|
|
|
|
success, code = self._update_nodes("update", old_peering, new_peering=new_peering)
|
|
|
|
if not success:
|
|
|
|
# revert updating peering
|
|
|
|
self.peerings["asn"][asn][peering_number] = old_peering
|
|
|
|
return False, code
|
|
|
|
else:
|
2022-12-21 22:52:56 +01:00
|
|
|
|
2023-06-14 08:52:17 +02:00
|
|
|
self._save_peerings()
|
|
|
|
return True, code
|
2022-12-12 19:40:30 +01:00
|
|
|
|
|
|
|
def delete_peering(self, asn, node, mnt, wg_key=None):
|
|
|
|
if not self.exists(asn, node, mnt=mnt, wg_key=wg_key):
|
2023-06-14 08:52:17 +02:00
|
|
|
return False, 404
|
2022-12-12 19:40:30 +01:00
|
|
|
for p in self.peerings["asn"][asn]:
|
|
|
|
if p["node"] == node:
|
2023-06-14 08:52:17 +02:00
|
|
|
|
2022-12-12 19:40:30 +01:00
|
|
|
self.peerings["asn"][asn].remove(p)
|
|
|
|
self._save_peerings()
|
2023-06-14 08:52:17 +02:00
|
|
|
success, code = self._update_nodes("delete", peering=p)
|
|
|
|
print(f"DELETE: {asn} on {node}: {success}, {code}")
|
|
|
|
return success, code
|
2022-12-12 19:40:30 +01:00
|
|
|
# if nothing got found (should have been catched by self.exists)
|
2023-06-14 08:52:17 +02:00
|
|
|
return False, 404
|