Commit 60508338 authored by Alberts S's avatar Alberts S
Browse files

Use pinger to also gather internal latency (dummy pings)

parent 25fd0fac
import asyncio
import ipaddress
import itertools
from typing import Any
from CapybaraNetty import CapybaraNetty
from ControllerStatic import ControllerStatic
from Dns import Dns
from Inventory import Inventory
from Pinger import Pinger
from Pinger import Pinger, PingerSourceOption
from Router import Router
from Visualizer import Visualizer
......@@ -34,7 +35,7 @@ class Controller(CapybaraNetty):
interfaces = [ipaddress.ip_interface(f"{address}/{subnet.prefixlen}") for address in addresses]
return interfaces
def get_dummy_ip(self):
def get_dummy_ip(self) -> ipaddress.IPv4Interface:
subnet = next(self.dummy_network_base)
addresses = list(subnet.hosts())
interfaces = [ipaddress.ip_interface(f"{address}/{subnet.prefixlen}") for address in addresses]
......@@ -54,14 +55,18 @@ class Controller(CapybaraNetty):
router_coroutines = []
for router in self.routers:
router_dummy_interface_ip = self.get_dummy_ip()
self.pingers.append(
Pinger(
router_ip=router.ip,
router_ssh_user=self.config["default_router_ssh_user"],
external_interface_name=router.get_default_interface_name(),
source_options=[
PingerSourceOption(interface_name=router.get_default_interface_name(), source_ip=None),
PingerSourceOption(interface_name=None, source_ip=str(router_dummy_interface_ip.ip)),
],
)
)
router_coroutines.append(router.add_default_dummy_interface_ip(ip=self.get_dummy_ip()))
router_coroutines.append(router.add_default_dummy_interface_ip(ip=router_dummy_interface_ip))
await asyncio.gather(*router_coroutines)
await asyncio.gather(self.set_router_interface_tunnels())
await self.setup_router_dns()
......@@ -122,7 +127,7 @@ class Controller(CapybaraNetty):
running_pingers.append(pinger.run_daemon(interval_seconds=self.config["pinger_interval"]))
return running_pingers
def find_router_by_ip(self, router_ip):
def find_router_by_ip(self, router_ip) -> Router | None:
for router in self.routers:
if router.ip == router_ip:
return router
......@@ -142,6 +147,15 @@ class Controller(CapybaraNetty):
else:
to_type = "target"
if result["interface_name"] == self.find_router_by_ip(pinger.router_ip).get_default_interface_name():
result_kind = "external"
elif result["source_ip"] == str(
self.find_router_by_ip(pinger.router_ip).get_default_dummy_interface_ip().ip
):
result_kind = "dummy"
else:
result_kind = "unknown"
pinger_data.append(
{
"from": pinger.router_ip,
......@@ -154,6 +168,9 @@ class Controller(CapybaraNetty):
"to_type": to_type,
"to": result["host"],
"time": result["average"],
"interface_name": result["interface_name"],
"source_ip": result["source_ip"],
"result_kind": result_kind,
}
)
return pinger_data
......
......@@ -132,14 +132,15 @@ class ControllerStatic(CapybaraNetty):
self.optimization_threshold_ms = 0
self.daemon_interval = self.config["controller_interval"]
self.route_stickiness_time = self.config["controller_static_route_stickiness_time"]
self.G = nx.Graph()
self.G_all = nx.Graph()
async def run(self):
if len(self.pinger_data) > 0:
self.G = self.get_latency_graph(self.G)
self.find_viable_paths(self.G)
self.set_viable_improvement_routes(self.G)
await self.install_viable_routes(self.G)
self.G_all = self.get_latency_graph(self.G_all)
self.find_viable_paths(self.G_all)
self.set_viable_improvement_routes(self.G_all)
await self.install_viable_routes(self.G_all)
print("a")
def get_latency_graph(self, G: nx.Graph):
......@@ -147,7 +148,14 @@ class ControllerStatic(CapybaraNetty):
host = result["from_name"]
dest = result["to_name"]
time = int(result["time"])
G.add_edge(host, dest, weight=time, color="black")
result_kind = result["result_kind"]
if result_kind == "external":
attrs = {
"weight": time,
}
else:
attrs = {f"weight_{result_kind}": time}
G.add_edge(host, dest, **attrs, color="black")
G.nodes[host]["type"] = result["from_type"]
G.nodes[host]["obj"] = result["from_obj"]
G.nodes[dest]["type"] = result["to_type"]
......
import asyncio
from typing import List, TypedDict
import asyncssh
from CapybaraNetty import CapybaraNetty
class PingerSourceOption(TypedDict):
interface_name: str | None
source_ip: str | None
class Pinger(CapybaraNetty):
def __init__(self, router_ip, router_ssh_user, external_interface_name, ping_addresses=None):
def __init__(self, router_ip, router_ssh_user, source_options: list[PingerSourceOption], ping_addresses=None):
super().__init__()
if ping_addresses is None:
ping_addresses = list()
self.router_ip = router_ip
self.router_ssh_user = router_ssh_user
self.ping_addresses = ping_addresses
self.external_interface_name = external_interface_name
self.source_options = source_options
self.latest_results = list()
async def ssh_exec(self, command, check, **kwargs):
......@@ -56,23 +62,39 @@ class Pinger(CapybaraNetty):
async def get_ping(self):
if len(self.ping_addresses) > 0:
self.__logger.info(f"{self.log_format_ip(self.router_ip)} Running")
fping_cmd_base = f"fping -c 10 -N -p 500 -I {self.external_interface_name} "
fping_cmd_destinations = " ".join(self.ping_addresses)
fping_cmd = fping_cmd_base + " " + fping_cmd_destinations
stdout, stderr = await self.ssh_exec(fping_cmd, check=False)
ping_results = []
for line in stderr: # First line is empty
parsed_line = self.parse_fping_line(line)
if parsed_line["loss"] < 100:
ping_results.append(parsed_line)
self.__logger.debug(
f"PING: {self.log_format_ip(self.router_ip)} => {parsed_line['host']:<16s} {int(parsed_line['average']):<5d}"
)
else:
self.__logger.debug(
f"PING: DOWN {self.log_format_ip(self.router_ip)} => {parsed_line['host']:<16s} {int(parsed_line['loss']):<5d}% packet loss"
)
option: PingerSourceOption
for option in self.source_options:
fping_cmd_option_interface = ""
fping_cmd_option_source_ip = ""
interface_name = option["interface_name"]
source_ip = option["source_ip"]
if interface_name:
fping_cmd_option_interface = f"-I {interface_name}"
if source_ip:
fping_cmd_option_source_ip = f"-S {source_ip}"
fping_cmd_base = f"fping -c 5 -N -p 500 {fping_cmd_option_interface} {fping_cmd_option_source_ip}"
fping_cmd_destinations = " ".join(self.ping_addresses)
fping_cmd = fping_cmd_base + " " + fping_cmd_destinations
# TODO nonimportant optimization - execute in conjunction each call to fping
stdout, stderr = await self.ssh_exec(fping_cmd, check=False)
for line in stderr: # First line is empty
ping_result = self.parse_fping_line(line)
ping_result["interface_name"] = interface_name
ping_result["source_ip"] = source_ip
if ping_result["loss"] < 100:
ping_results.append(ping_result)
self.__logger.info(
f"PING [{interface_name or source_ip}]: {self.log_format_ip(self.router_ip)} => {ping_result['host']:<16s} {int(ping_result['average']):<5d}"
)
else:
self.__logger.debug(
f"PING [{interface_name or source_ip}]: DOWN {self.log_format_ip(self.router_ip)} => {ping_result['host']:<16s} {int(ping_result['loss']):<5d}% packet loss"
)
self.latest_results = ping_results
return ping_results
......
......@@ -184,9 +184,12 @@ class Router(CapybaraNetty):
async def add_default_dummy_interface_ip(self, ip: ipaddress.ip_interface):
await self.add_interface_ip(self.dummy_interface_name, ip)
def get_default_dummy_interface_ip(self):
def get_default_dummy_interface_ip(self) -> ipaddress.IPv4Interface:
return self.managed_interfaces[self.get_interface_name(self.dummy_interface_name)]["ip"]
def get_default_dummy_interface_name(self):
return self.get_interface_name(self.dummy_interface_name)
def get_interface_name(self, name):
return self.interface_prefix + name
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment