2020-04-24 11:41:43 -07:00
|
|
|
"""Device-Agnostic Parsed Response Data Model."""
|
|
|
|
|
|
|
|
# Standard Library
|
2020-06-06 01:25:02 -07:00
|
|
|
import re
|
2021-09-13 02:35:52 -07:00
|
|
|
from typing import List, Literal
|
2021-02-25 23:40:19 -07:00
|
|
|
from ipaddress import ip_network
|
2020-04-24 11:41:43 -07:00
|
|
|
|
|
|
|
# Third Party
|
2021-09-13 02:35:52 -07:00
|
|
|
from pydantic import StrictInt, StrictStr, StrictBool, validator
|
2020-04-24 11:41:43 -07:00
|
|
|
|
|
|
|
# Project
|
2021-09-15 18:25:37 -07:00
|
|
|
from hyperglass.state import use_state
|
2020-06-06 01:25:02 -07:00
|
|
|
from hyperglass.external.rpki import rpki_state
|
2020-04-24 11:41:43 -07:00
|
|
|
|
2020-12-13 01:48:20 -07:00
|
|
|
# Local
|
|
|
|
from ..main import HyperglassModel
|
|
|
|
|
2021-09-13 02:35:52 -07:00
|
|
|
WinningWeight = Literal["low", "high"]
|
2020-07-13 02:18:26 -07:00
|
|
|
|
2020-04-24 11:41:43 -07:00
|
|
|
|
2021-09-13 02:35:52 -07:00
|
|
|
class BGPRoute(HyperglassModel):
|
|
|
|
"""Post-parsed BGP route."""
|
2020-04-24 11:41:43 -07:00
|
|
|
|
2020-05-29 17:47:53 -07:00
|
|
|
prefix: StrictStr
|
2020-04-24 11:41:43 -07:00
|
|
|
active: StrictBool
|
|
|
|
age: StrictInt
|
|
|
|
weight: StrictInt
|
|
|
|
med: StrictInt
|
|
|
|
local_preference: StrictInt
|
|
|
|
as_path: List[StrictInt]
|
|
|
|
communities: List[StrictStr]
|
|
|
|
next_hop: StrictStr
|
|
|
|
source_as: StrictInt
|
|
|
|
source_rid: StrictStr
|
|
|
|
peer_rid: StrictStr
|
|
|
|
rpki_state: StrictInt
|
|
|
|
|
2020-06-06 01:25:02 -07:00
|
|
|
@validator("communities")
|
|
|
|
def validate_communities(cls, value):
|
|
|
|
"""Filter returned communities against configured policy.
|
|
|
|
|
|
|
|
Actions:
|
|
|
|
permit: only permit matches
|
|
|
|
deny: only deny matches
|
|
|
|
"""
|
2020-06-06 02:04:23 -07:00
|
|
|
|
2021-09-16 13:46:50 -07:00
|
|
|
(structured := use_state("params").structured)
|
2021-09-15 18:25:37 -07:00
|
|
|
|
2020-06-06 02:04:23 -07:00
|
|
|
def _permit(comm):
|
|
|
|
"""Only allow matching patterns."""
|
|
|
|
valid = False
|
2021-09-15 18:25:37 -07:00
|
|
|
for pattern in structured.communities.items:
|
2020-06-06 02:04:23 -07:00
|
|
|
if re.match(pattern, comm):
|
|
|
|
valid = True
|
|
|
|
break
|
|
|
|
return valid
|
|
|
|
|
|
|
|
def _deny(comm):
|
|
|
|
"""Allow any except matching patterns."""
|
|
|
|
valid = True
|
2021-09-15 18:25:37 -07:00
|
|
|
for pattern in structured.communities.items:
|
2020-06-06 02:04:23 -07:00
|
|
|
if re.match(pattern, comm):
|
|
|
|
valid = False
|
|
|
|
break
|
|
|
|
return valid
|
|
|
|
|
|
|
|
func_map = {"permit": _permit, "deny": _deny}
|
2021-09-15 18:25:37 -07:00
|
|
|
func = func_map[structured.communities.mode]
|
2020-06-06 02:04:23 -07:00
|
|
|
|
|
|
|
return [c for c in value if func(c)]
|
2020-06-06 01:25:02 -07:00
|
|
|
|
|
|
|
@validator("rpki_state")
|
|
|
|
def validate_rpki_state(cls, value, values):
|
|
|
|
"""If external RPKI validation is enabled, get validation state."""
|
|
|
|
|
2021-09-16 13:46:50 -07:00
|
|
|
(structured := use_state("params").structured)
|
2021-09-15 18:25:37 -07:00
|
|
|
|
|
|
|
if structured.rpki.mode == "router":
|
2020-06-06 01:25:02 -07:00
|
|
|
# If router validation is enabled, return the value as-is.
|
|
|
|
return value
|
|
|
|
|
2021-09-15 18:25:37 -07:00
|
|
|
elif structured.rpki.mode == "external":
|
2020-06-06 01:25:02 -07:00
|
|
|
# If external validation is enabled, validate the prefix
|
|
|
|
# & asn with Cloudflare's RPKI API.
|
|
|
|
as_path = values["as_path"]
|
|
|
|
|
|
|
|
if len(as_path) == 0:
|
|
|
|
# If the AS_PATH length is 0, i.e. for an internal route,
|
|
|
|
# return RPKI Unknown state.
|
|
|
|
return 3
|
|
|
|
else:
|
|
|
|
# Get last ASN in path
|
|
|
|
asn = as_path[-1]
|
|
|
|
|
2021-02-25 23:40:19 -07:00
|
|
|
try:
|
|
|
|
net = ip_network(values["prefix"])
|
|
|
|
except ValueError:
|
|
|
|
return 3
|
|
|
|
|
|
|
|
# Only do external RPKI lookups for global prefixes.
|
|
|
|
if net.is_global:
|
2020-06-06 01:25:02 -07:00
|
|
|
return rpki_state(prefix=values["prefix"], asn=asn)
|
|
|
|
else:
|
|
|
|
return value
|
|
|
|
|
2020-04-24 11:41:43 -07:00
|
|
|
|
2021-09-13 02:35:52 -07:00
|
|
|
class BGPRouteTable(HyperglassModel):
|
|
|
|
"""Post-parsed BGP route table."""
|
2020-04-24 11:41:43 -07:00
|
|
|
|
|
|
|
vrf: StrictStr
|
|
|
|
count: StrictInt = 0
|
2021-09-13 02:35:52 -07:00
|
|
|
routes: List[BGPRoute]
|
2020-07-13 02:18:26 -07:00
|
|
|
winning_weight: WinningWeight
|
2021-09-13 02:35:52 -07:00
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""Sort routes by prefix after validation."""
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
self.routes = sorted(self.routes, key=lambda r: r.prefix)
|
|
|
|
|
|
|
|
def __add__(self: "BGPRouteTable", other: "BGPRouteTable") -> "BGPRouteTable":
|
|
|
|
"""Merge another BGP table instance with this instance."""
|
|
|
|
if isinstance(other, BGPRouteTable):
|
|
|
|
self.routes = sorted([*self.routes, *other.routes], key=lambda r: r.prefix)
|
|
|
|
self.count = len(self.routes)
|
|
|
|
return self
|