Implement definitive (?) reading from BIRD socket

master
LEdoian 1 year ago
parent 5cc5685ae3
commit e9ff79c57f

@ -1,6 +1,6 @@
"""Common TopologyProviders""" """Common TopologyProviders"""
from birdvisu import ospffile from birdvisu import ospffile, ospfsock
from birdvisu.maps_new import TopologyProvider, Router, Network, Link, Topology from birdvisu.maps_new import TopologyProvider, Router, Network, Link, Topology
import subprocess import subprocess
import re import re
@ -125,3 +125,15 @@ class RunningBirdTopologyProvider(TopologyProvider):
def get_topology(self): def get_topology(self):
return self.topology return self.topology
class BirdSocketTopologyProvider(TopologyProvider):
"""This one is dynamic, provides new topology every time."""
def __init__(self, socket_filename='/run/bird/bird.ctl'):
self.socket_filename = socket_filename
def get_topology(self):
birdsock = ospfsock.BirdSocketConnection()
resp = birdsock.request('show ospf state')
# TODO: Naming things this is not exactly parser…
parser = OspfDataTopologyProvider(resp.text)
return parser.get_topology()

@ -1,18 +1,43 @@
"""Simple utility to interface with BIRD using the control socket.""" """Simple utility to interface with BIRD using the control socket."""
from dataclasses import dataclass from dataclasses import dataclass,field
import socket as sk import socket as sk
from string import digits
class BirdError(Exception):
def __init__(self, code, where, text):
self.code = code
self.start = where[0]
self.end = where[1]
self.text = text
@dataclass @dataclass
class BirdResponse: class BirdResponse:
text: str = '' text: str = ''
codes: list[tuple[tuple[int, int], int]] = [] # List of line ranges (inclusive) and the respective code. codes: list[tuple[tuple[int, int], int]] = [] # List of line ranges (inclusive) and the respective code.
def raise_exceptions(self) -> None:
# If the response contains any errors, raise them as BirdErrors
# TODO Py3.11: use ExceptionGroups?
# Until then, let's just report the first one?
assert len(self.codes) >= 1, "Not a valid response."
for where, code in self.codes:
if code.startswith('9'):
# Extract text
start = where[0] - 1
end = where[1]
print(start,end)
errorlines = self.text.splitlines(keepends=True)[start:end]
exc = BirdError(code, where, ''.join(errorlines))
raise exc
class BirdSocketConnection: class BirdSocketConnection:
def __init__(self, sockpath='/run/bird/bird.ctl'): def __init__(self, sockpath='/run/bird/bird.ctl'):
self.socket = sk.socket(sk.AF_UNIX, sk.SOCK_STREAM) self.socket = sk.socket(sk.AF_UNIX, sk.SOCK_STREAM)
self.socket.setblocking(False) self.socket.setblocking(False)
self.socket.connect(sockpath) self.socket.connect(sockpath)
# Bird announces itself by printing version. We save the response just in case.
self.bird_prologue = self._parse_response()
def request(self, req: str) -> BirdResponse: def request(self, req: str) -> BirdResponse:
if not req.endswith('\n'): if not req.endswith('\n'):
@ -26,14 +51,32 @@ class BirdSocketConnection:
# described at one place. Therefore other parts of the code do not need # described at one place. Therefore other parts of the code do not need
# to know anything about the protocol (maybe apart from the meaning of # to know anything about the protocol (maybe apart from the meaning of
# status codes, if they are interested…) # status codes, if they are interested…)
MAXLINELENGTH = 1000 # Pretty please… result = BirdResponse()
READSIZE = MAXLINELENGTH * 2 # Ensures that after the read at least one complete line has been read. code : str | None = None # string because of leading zeroes, and it has no numeric interpretation anyway
lines = [] start = None
data = b'' cont = True
# There is no way to read line-wise, so we just read everything and split afterwards. f = self.socket.makefile('r')
# This is probably very horrible. for i, line in enumerate(f, start=1):
try: assert cont, "WTF bad format (should not continue)."
while True: if line.startswith(tuple(digits)):
data += self.socket. if start is not None:
end = i-1
result.codes.append(((start, end), code))
code = line[:4]
start = i
cont = line[4] == '-'
text = line[5:]
else:
assert cont == True
text = line[1:]
result.text += text # Invariant: LF as line terminator
if not cont:
# Finalization. We do not have i after the end of the cycle,
# but this is the last iteration.
end = i
result.codes.append(((start, end), code))
break
result.raise_exceptions()
return result

Loading…
Cancel
Save