diff --git a/.gitignore b/.gitignore index 97c70a0..65cd4d8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage.xml dist/ site/ +env/ diff --git a/pybird/__init__.py b/pybird/__init__.py index 69d2c93..3171a5c 100644 --- a/pybird/__init__.py +++ b/pybird/__init__.py @@ -3,6 +3,7 @@ import socket from datetime import datetime, timedelta from subprocess import PIPE, Popen +from more_itertools import peekable class PyBird: @@ -262,30 +263,37 @@ def _parse_route_data(self, data): [....] 0000 """ - lines = data.splitlines() + splitted = data.splitlines() + lines = peekable(splitted) routes = [] route_summary = None - self.log.debug("PyBird: parse route data: lines=%d", len(lines)) - line_counter = -1 - while line_counter < len(lines) - 1: - line_counter += 1 - line = lines[line_counter].strip() + self.log.debug("PyBird: parse route data: lines=%d", len(splitted)) + for line in lines: self.log.debug("PyBird: parse route data: %s", line) + + # A line starts vwith either a space or a number. If it is a number, + # it is a new block, if it is a space, it belongs to the previous number. + # At this point it should not be a space, but if it is, log and skip it. (field_number, line) = self._extract_field_number(line) + if None == field_number: + self.log.debug("PyBird: WARNING: Line star with unexpected character, skipping") + continue + + # Collect all data lines + datablock = [ line.strip() ] + while lines.peek("default").startswith(" "): + self.log.debug("PyBird: Collecting field %s data line: %s", (field_number, lines.peek())) + datablock.append(next(lines).strip()) if field_number in self.ignored_field_numbers: continue if field_number == 1007: - try: - route_summary = self._parse_route_summary(line) - except ValueError: - # bird2 sends route summary on a new line - line_counter += 1 - line = lines[line_counter].strip() - route_summary = self._parse_route_summary(line) + # As far as foreseen, the info is always on the last line in the datablock. + # Allso, if desired, here is the place to to extract the tablename. + route_summary = self._parse_route_summary(datablock[-1]) route_detail = None @@ -294,17 +302,7 @@ def _parse_route_data(self, data): # This is not detail of a BGP route continue - # A route detail spans multiple lines, read them all - route_detail_raw = [] - while "BGP." in line: - route_detail_raw.append(line) - line_counter += 1 - line = lines[line_counter] - self.log.debug("PyBird: parse route data: %s", line) - # this loop will have walked a bit too far, correct it - line_counter -= 1 - - route_detail = self._parse_route_detail(route_detail_raw) + route_detail = self._parse_route_detail(datablock) # Save the summary+detail info in our result route_detail.update(route_summary) diff --git a/pyproject.toml b/pyproject.toml index 645e9ab..0a3cf98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ packages = [{ include = "pybird" }] [tool.poetry.dependencies] python = "^3.7" +mote_itertools = "*" [tool.poetry.dev-dependencies] # testing diff --git a/tests/data/parse/route/data/ipv6_table.expected b/tests/data/parse/route/data/ipv6_table.expected new file mode 100644 index 0000000..2e7249b --- /dev/null +++ b/tests/data/parse/route/data/ipv6_table.expected @@ -0,0 +1 @@ +[{"origin": "IGP", "as_path": "2914 3491 55644 45271", "next_hop": "89.188.4.16", "med": "1", "local_pref": "210", "atomic_aggr": true, "aggregator": "10.100.230.241 AS65010", "community": "31477:204 31477:2600", "prefix": "49.14.107.0/24", "peer": "89.188.4.16", "interface": null, "source": "eqam7_4", "time": "2023-12-29"}, {"origin": "IGP", "as_path": "1299 6762 28008 23201", "next_hop": "89.188.4.16", "med": "1", "local_pref": "210", "community": "31477:203 31477:2700", "originator_id": "89.188.4.5", "cluster_list": "89.188.4.16", "prefix": "181.123.200.0/22", "peer": "89.188.4.16", "interface": null, "source": "eqam7_4", "time": "2023-12-29"}, {"origin": "IGP", "as_path": "6939 15412 142505 141047 24550 45157 4613 4613", "next_hop": "2a01:1b0:1::16", "med": "0", "local_pref": "500", "community": "31477:202 31477:2300 31477:2320", "prefix": "2400:a400::/32", "peer": "2a01:1b0:1::16", "interface": null, "source": "eqam7_6", "time": "2023-12-29"}, {"origin": "IGP", "as_path": "6939 23911 38255 146244", "next_hop": "2a01:1b0:1::16", "med": "0", "local_pref": "500", "community": "31477:202 31477:2300 31477:2320", "prefix": "240a:ae0a::/32", "peer": "2a01:1b0:1::16", "interface": null, "source": "eqam7_6", "time": "2023-12-29"}] diff --git a/tests/data/parse/route/data/ipv6_table.input b/tests/data/parse/route/data/ipv6_table.input new file mode 100644 index 0000000..f947e8d --- /dev/null +++ b/tests/data/parse/route/data/ipv6_table.input @@ -0,0 +1,40 @@ +0001 BIRD 2.0.7 ready. +1007-Table master4: + 49.14.107.0/24 unreachable [eqam7_4 2023-12-29 from 89.188.4.16] * (100) [AS45271i] +1008- Type: BGP univ +1012- BGP.origin: IGP + BGP.as_path: 2914 3491 55644 45271 + BGP.next_hop: 89.188.4.16 + BGP.med: 1 + BGP.local_pref: 210 + BGP.atomic_aggr: + BGP.aggregator: 10.100.230.241 AS65010 + BGP.community: (31477,204) (31477,2600) +1007-181.123.200.0/22 unreachable [eqam7_4 2023-12-29 from 89.188.4.16] * (100) [AS23201i] +1008- Type: BGP univ +1012- BGP.origin: IGP + BGP.as_path: 1299 6762 28008 23201 + BGP.next_hop: 89.188.4.16 + BGP.med: 1 + BGP.local_pref: 210 + BGP.community: (31477,203) (31477,2700) + BGP.originator_id: 89.188.4.5 + BGP.cluster_list: 89.188.4.16 +1007- + Table master6: + 2400:a400::/32 unreachable [eqam7_6 2023-12-29 from 2a01:1b0:1::16] * (100) [AS4613i] +1008- Type: BGP univ +1012- BGP.origin: IGP + BGP.as_path: 6939 15412 142505 141047 24550 45157 4613 4613 + BGP.next_hop: 2a01:1b0:1::16 + BGP.med: 0 + BGP.local_pref: 500 + BGP.community: (31477,202) (31477,2300) (31477,2320) +1007-240a:ae0a::/32 unreachable [eqam7_6 2023-12-29 from 2a01:1b0:1::16] * (100) [AS146244i] +1008- Type: BGP univ +1012- BGP.origin: IGP + BGP.as_path: 6939 23911 38255 146244 + BGP.next_hop: 2a01:1b0:1::16 + BGP.med: 0 + BGP.local_pref: 500 + BGP.community: (31477,202) (31477,2300) (31477,2320)