And buffing
This commit is contained in:
parent
7fcbf6f3fa
commit
0016eaa63d
2 changed files with 24 additions and 120 deletions
|
@ -9,15 +9,16 @@ when packet delivery latencies radically degrade and maintain a report file.
|
||||||
import argparse
|
import argparse
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from itertools import cycle
|
||||||
import logging
|
import logging
|
||||||
from multiprocessing import Process, Queue
|
from multiprocessing import Process, Queue
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .lib import *
|
|
||||||
|
|
||||||
import graphviz
|
import graphviz
|
||||||
|
from icmplib import Hop, ping, traceroute
|
||||||
|
from icmplib.utils import *
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,24 +28,12 @@ parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("hosts", nargs="+")
|
parser.add_argument("hosts", nargs="+")
|
||||||
|
|
||||||
|
|
||||||
def distinct(iter):
|
|
||||||
s = set()
|
|
||||||
l = []
|
|
||||||
for e in iter:
|
|
||||||
if e in s:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
l.append(e)
|
|
||||||
s.add(e)
|
|
||||||
return l
|
|
||||||
|
|
||||||
|
|
||||||
class Topology(object):
|
class Topology(object):
|
||||||
LOCALHOST = Hop("127.0.0.1", 1, [0.0], 0)
|
LOCALHOST = Hop("127.0.0.1", 1, [0.0], 0)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._graph = defaultdict(set) # Dict[address, List[address]]
|
self._graph = defaultdict(set)
|
||||||
self._nodes = {self.LOCALHOST.address: self.LOCALHOST} # Dict[address, Host]
|
self._nodes = {self.LOCALHOST.address: self.LOCALHOST}
|
||||||
|
|
||||||
def next_hops(self, address: str) -> List[str]:
|
def next_hops(self, address: str) -> List[str]:
|
||||||
return list(self._graph.get(address))
|
return list(self._graph.get(address))
|
||||||
|
@ -60,9 +49,11 @@ class Topology(object):
|
||||||
e0 = self._nodes[e.address]
|
e0 = self._nodes[e.address]
|
||||||
e0._packets_sent += e._packets_sent
|
e0._packets_sent += e._packets_sent
|
||||||
e0._rtts.extend(e.rtts)
|
e0._rtts.extend(e.rtts)
|
||||||
e0._distance = max(e.distance, e0.distance)
|
e0._distance = min(e.distance, e0.distance)
|
||||||
|
|
||||||
hosts = [(self.LOCALHOST.address, self.LOCALHOST.distance)] + [(e.address, e.distance) for e in trace]
|
hosts = [(self.LOCALHOST.address, self.LOCALHOST.distance)] + [
|
||||||
|
(e.address, e.distance) for e in trace
|
||||||
|
]
|
||||||
i1 = iter(hosts)
|
i1 = iter(hosts)
|
||||||
i2 = iter(hosts)
|
i2 = iter(hosts)
|
||||||
next(i2)
|
next(i2)
|
||||||
|
@ -78,8 +69,10 @@ class Topology(object):
|
||||||
g.edge(n.address, next)
|
g.edge(n.address, next)
|
||||||
|
|
||||||
# Lol. Lmao.
|
# Lol. Lmao.
|
||||||
return requests.post("https://dot-to-ascii.ggerganov.com/dot-to-ascii.php", params={"boxart":1, "src": g.source}).text
|
return requests.post(
|
||||||
# return g.source
|
"https://dot-to-ascii.ggerganov.com/dot-to-ascii.php",
|
||||||
|
params={"boxart": 1, "src": g.source},
|
||||||
|
).text
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(sorted(self._nodes.values(), key=lambda n: n.distance))
|
return iter(sorted(self._nodes.values(), key=lambda n: n.distance))
|
||||||
|
@ -88,6 +81,7 @@ class Topology(object):
|
||||||
del self._graph[key]
|
del self._graph[key]
|
||||||
del self._nodes[key]
|
del self._nodes[key]
|
||||||
|
|
||||||
|
|
||||||
def compute_topology(hostlist, topology=None):
|
def compute_topology(hostlist, topology=None):
|
||||||
"""Walk a series of traceroute tuples, computing a 'worst expected latency' topology from them."""
|
"""Walk a series of traceroute tuples, computing a 'worst expected latency' topology from them."""
|
||||||
|
|
||||||
|
@ -101,12 +95,6 @@ def compute_topology(hostlist, topology=None):
|
||||||
return topology
|
return topology
|
||||||
|
|
||||||
|
|
||||||
def cycle(iter):
|
|
||||||
while True:
|
|
||||||
for e in iter:
|
|
||||||
yield e
|
|
||||||
|
|
||||||
|
|
||||||
def pinger(host, id, queue):
|
def pinger(host, id, queue):
|
||||||
# Mokney patch the RTT tracking
|
# Mokney patch the RTT tracking
|
||||||
host._rtts = deque(host._rtts, maxlen=100)
|
host._rtts = deque(host._rtts, maxlen=100)
|
||||||
|
@ -143,6 +131,7 @@ if __name__ == "__main__":
|
||||||
spinner = cycle("|/-\\")
|
spinner = cycle("|/-\\")
|
||||||
|
|
||||||
with open("incidents.txt", "a") as fp:
|
with open("incidents.txt", "a") as fp:
|
||||||
|
fp.write("RESTART\n")
|
||||||
while True:
|
while True:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
|
@ -157,7 +146,7 @@ if __name__ == "__main__":
|
||||||
log.info("Graph -\n" + topology.render())
|
log.info("Graph -\n" + topology.render())
|
||||||
|
|
||||||
for h in topology:
|
for h in topology:
|
||||||
if h.distance < 1 or h.distance > 6:
|
if h.distance == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if h.address in workers:
|
if h.address in workers:
|
||||||
|
@ -176,7 +165,11 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
elif res.is_alive:
|
elif res.is_alive:
|
||||||
if last and (delta := timestamp - last) > recovered_duration:
|
if last and (delta := timestamp - last) > recovered_duration:
|
||||||
fp.write(f"RECOVERED\t{res.address}\t{timestamp.isoformat()}\t{delta.total_seconds()}\n")
|
fp.write(
|
||||||
|
f"RECOVERED\t{res.address}\t{timestamp.isoformat()}\t{delta.total_seconds()}\n"
|
||||||
|
)
|
||||||
|
elif not last:
|
||||||
|
fp.write(f"UP\t{res.address}\t{timestamp.isoformat()}\n")
|
||||||
last_seen[res.address] = timestamp
|
last_seen[res.address] = timestamp
|
||||||
|
|
||||||
elif not res.is_alive:
|
elif not res.is_alive:
|
||||||
|
@ -184,7 +177,9 @@ if __name__ == "__main__":
|
||||||
workers[h.address].terminate()
|
workers[h.address].terminate()
|
||||||
del workers[h.address]
|
del workers[h.address]
|
||||||
del topology[h.address]
|
del topology[h.address]
|
||||||
fp.write(f"DEAD\t{res.address}\t{timestamp.isoformat()}\t{delta.total_seconds()}\n")
|
fp.write(
|
||||||
|
f"DEAD\t{res.address}\t{timestamp.isoformat()}\t{delta.total_seconds()}\n"
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
fp.write(f"DOWN\t{res.address}\t{timestamp.isoformat()}\n")
|
fp.write(f"DOWN\t{res.address}\t{timestamp.isoformat()}\n")
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
from icmplib import Hop
|
|
||||||
from icmplib.exceptions import (
|
|
||||||
ICMPLibError,
|
|
||||||
TimeExceeded,
|
|
||||||
)
|
|
||||||
from icmplib.models import Hop, ICMPRequest
|
|
||||||
from icmplib.sockets import (
|
|
||||||
ICMPv4Socket,
|
|
||||||
ICMPv6Socket,
|
|
||||||
)
|
|
||||||
from icmplib.utils import *
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def urping(address: str, hops: int, via: str, family=None, count=3, fudge=4, interval=0.05, timeout=2, source=None, **kwargs) -> Hop:
|
|
||||||
"""Ur-ping by (ab)using ICMP TTLs.
|
|
||||||
|
|
||||||
Craft an ICMP message which would go one (or more) hops FARTHER than the `address` host, routed towards `via`.
|
|
||||||
Send `count * fudge` packets, looking for responses from `address`.
|
|
||||||
Responses from `address` are considered; and a `Hop` is built from those results.
|
|
||||||
Other responses from other hosts are discarded.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if is_hostname(via):
|
|
||||||
via = resolve(via, family)[0]
|
|
||||||
|
|
||||||
if is_hostname(address):
|
|
||||||
address = resolve(address, falmiy)[0]
|
|
||||||
|
|
||||||
if is_ipv6_address(via):
|
|
||||||
_Socket = ICMPv6Socket
|
|
||||||
else:
|
|
||||||
_Socket = ICMPv4Socket
|
|
||||||
|
|
||||||
ttl = hops
|
|
||||||
hop = Hop(address, 0, [])
|
|
||||||
packets_sent = 0
|
|
||||||
|
|
||||||
with _Socket(source) as sock:
|
|
||||||
for _ in range(count * fudge):
|
|
||||||
request = ICMPRequest(
|
|
||||||
destination=via,
|
|
||||||
# Note that we act like this is a new stream with a new ID and sequence each time to try and fool multipathing.
|
|
||||||
id=unique_identifier(),
|
|
||||||
sequence=0,
|
|
||||||
ttl=ttl,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sock.send(request)
|
|
||||||
packets_sent += 1
|
|
||||||
|
|
||||||
reply = None
|
|
||||||
reply = sock.receive(request, timeout)
|
|
||||||
rtt = (reply.time - request.time) * 1000
|
|
||||||
|
|
||||||
reply.raise_for_status()
|
|
||||||
|
|
||||||
except TimeExceeded:
|
|
||||||
sleep(interval)
|
|
||||||
|
|
||||||
except ICMPLibError as e:
|
|
||||||
log.exception(e)
|
|
||||||
break
|
|
||||||
|
|
||||||
if not reply:
|
|
||||||
log.debug(f"got no reply from {address}")
|
|
||||||
else:
|
|
||||||
# Because the default repr() is crap
|
|
||||||
reply_str = "ICMPReply({})".format(", ".join(slot + "=" + str(getattr(reply, slot)) for slot in reply.__slots__))
|
|
||||||
log.debug(f"Pinging {address} (distance {hops}) via {via} got reply {reply_str}")
|
|
||||||
|
|
||||||
if reply and reply.source != address:
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif reply:
|
|
||||||
hop._packets_sent += 1
|
|
||||||
hop._rtts.append(rtt)
|
|
||||||
|
|
||||||
if hop and hop._packets_sent >= count:
|
|
||||||
break
|
|
||||||
|
|
||||||
return hop
|
|
Loading…
Reference in a new issue