From 934e8f3146d9740fd487f88df1db5f61a9311f0a Mon Sep 17 00:00:00 2001 From: Tan Siewert Date: Sat, 4 Oct 2025 21:02:53 +0200 Subject: [PATCH 1/3] bgp_route_frr: skip empty route query results In case a route is not present in the RIB, FRR returns an empty JSON object: ``` $ vtysh -c "show bgp ipv4 unicast 1.2.3.4 json" {} ``` Skip these empty objects to prevent the parser from failing. Signed-off-by: Tan Siewert --- hyperglass/plugins/_builtin/bgp_route_frr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hyperglass/plugins/_builtin/bgp_route_frr.py b/hyperglass/plugins/_builtin/bgp_route_frr.py index 7130b20..96d2561 100644 --- a/hyperglass/plugins/_builtin/bgp_route_frr.py +++ b/hyperglass/plugins/_builtin/bgp_route_frr.py @@ -36,6 +36,10 @@ def parse_frr(output: t.Sequence[str]) -> "OutputDataModel": _log.debug("Pre-parsed data", data=parsed) + # If empty (i.e. no route found), skip + if not parsed: + continue + validated = FRRBGPTable(**parsed) bgp_table = validated.bgp_table() From 32873934cf89936c0d25e6a3e9e054cb068e635f Mon Sep 17 00:00:00 2001 From: Tan Siewert Date: Sat, 4 Oct 2025 21:05:13 +0200 Subject: [PATCH 2/3] frr: set default values for optional next-hop fields The metric and used fields might not be returned, causing the parser to fail if they are not present. The absence was observed in FRR 10.4.1 with a multipath IPv6 route that has two next-hops (one GUA, one LL): ``` $ vtysh -c "show bgp ipv6 unicast 2003::/19 json" | jq '.paths[].nexthops' [ { "ip": "2a0d:2146:bdff:120::1", "afi": "ipv6", "scope": "global", "linkLocalOnly": false, "length": 32, "metric": 0, "accessible": true }, { "ip": "fe80::3e61:408:1e3e:cff0", "afi": "ipv6", "scope": "link-local", "length": 32, "accessible": true, "used": true } ] [ { "ip": "2a0d:2146:bdff:120::1", "afi": "ipv6", "scope": "global", "linkLocalOnly": false, "length": 32, "metric": 0, "accessible": true }, { "ip": "fe80::3e61:408:1e3e:cff0", "afi": "ipv6", "scope": "link-local", "length": 32, "accessible": true, "used": true } ] ``` Tested: Query a prefix with multiple next-hops where the metric or used attributes are missing on one of them. Signed-off-by: Tan Siewert --- hyperglass/models/parsing/frr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hyperglass/models/parsing/frr.py b/hyperglass/models/parsing/frr.py index dacd82c..95536e2 100644 --- a/hyperglass/models/parsing/frr.py +++ b/hyperglass/models/parsing/frr.py @@ -31,9 +31,9 @@ class FRRNextHop(_FRRBase): ip: str afi: str - metric: int + metric: int = 0 accessible: bool - used: bool + used: bool = False class FRRPeer(_FRRBase): From 79d235c2ffceea5fc11f3cfd1cb95b4473b3e10f Mon Sep 17 00:00:00 2001 From: Tan Siewert Date: Sat, 4 Oct 2025 21:10:48 +0200 Subject: [PATCH 3/3] frr: return AS0 if AS_PATH length is zero Local prefixes usually do not have an AS_PATH: ``` vtysh -c "show bgp ipv4 unicast 10.10.10.10 json" | jq '.paths[].aspath' { "string": "Local", "segments": [], "length": 0 } ``` Set AS0 as a temporary solution when AS_PATH length is zero. This avoids parser failures for local prefixes, though ideally we should use the device's actual ASN (see TODO in code). Signed-off-by: Tan Siewert --- hyperglass/models/parsing/frr.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hyperglass/models/parsing/frr.py b/hyperglass/models/parsing/frr.py index 95536e2..27a3d89 100644 --- a/hyperglass/models/parsing/frr.py +++ b/hyperglass/models/parsing/frr.py @@ -65,7 +65,15 @@ class FRRPath(_FRRBase): def validate_path(cls, values): """Extract meaningful data from FRR response.""" new = values.copy() - new["aspath"] = values["aspath"]["segments"][0]["list"] + # Local prefixes (i.e. those in the same ASN) usually have + # no AS_PATH. + # Set AS_PATH to AS0 for now as we cannot ensure that the + # ASN for the prefix is the primary ASN. + if values["aspath"]["length"] != 0: + new["aspath"] = values["aspath"]["segments"][0]["list"] + else: + # TODO: Get an ASN that is reasonable (e.g. primary ASN) + new["aspath"] = [0] community = values.get("community", {"list": []}) new["community"] = community["list"] new["lastUpdate"] = values["lastUpdate"]["epoch"]