From 751500acdbdff0b4ba4d025f4436ddb1282f2588 Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Fri, 15 Dec 2023 16:31:44 +0100 Subject: [PATCH] topotests: add bgp_rpki_topo1 Add bgp_rpki_topo1 topotest to validate the RPKI feature. Use a RTR RPKI server from the above link with a black cleaning. Link: https://raw.githubusercontent.com/tmshlvck/pyrtr/90df586375396aae08b07069187308b5b7b8823b/pyrtr/__init__.py Signed-off-by: Louis Scalbert --- tests/topotests/bgp_rpki_topo1/__init__.py | 0 tests/topotests/bgp_rpki_topo1/r1/bgpd.conf | 14 + tests/topotests/bgp_rpki_topo1/r1/rtrd.py | 319 ++++++++++++++++++ .../topotests/bgp_rpki_topo1/r1/staticd.conf | 1 + tests/topotests/bgp_rpki_topo1/r1/vrps.csv | 3 + tests/topotests/bgp_rpki_topo1/r1/zebra.conf | 6 + .../r2/bgp_table_rmap_rpki_any.json | 37 ++ .../r2/bgp_table_rmap_rpki_notfound.json | 7 + .../r2/bgp_table_rmap_rpki_valid.json | 1 + .../bgp_rpki_topo1/r2/bgp_table_rpki_any.json | 52 +++ .../r2/bgp_table_rpki_notfound.json | 22 ++ .../r2/bgp_table_rpki_valid.json | 35 ++ tests/topotests/bgp_rpki_topo1/r2/bgpd.conf | 19 ++ .../bgp_rpki_topo1/r2/rpki_prefix_table.json | 18 + .../topotests/bgp_rpki_topo1/r2/staticd.conf | 1 + tests/topotests/bgp_rpki_topo1/r2/zebra.conf | 9 + .../bgp_rpki_topo1/test_bgp_rpki_topo1.py | 264 +++++++++++++++ 17 files changed, 808 insertions(+) create mode 100644 tests/topotests/bgp_rpki_topo1/__init__.py create mode 100644 tests/topotests/bgp_rpki_topo1/r1/bgpd.conf create mode 100755 tests/topotests/bgp_rpki_topo1/r1/rtrd.py create mode 100644 tests/topotests/bgp_rpki_topo1/r1/staticd.conf create mode 100644 tests/topotests/bgp_rpki_topo1/r1/vrps.csv create mode 100644 tests/topotests/bgp_rpki_topo1/r1/zebra.conf create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json create mode 120000 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/bgpd.conf create mode 100644 tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json create mode 100644 tests/topotests/bgp_rpki_topo1/r2/staticd.conf create mode 100644 tests/topotests/bgp_rpki_topo1/r2/zebra.conf create mode 100644 tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py diff --git a/tests/topotests/bgp_rpki_topo1/__init__.py b/tests/topotests/bgp_rpki_topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf new file mode 100644 index 0000000000..437d393c75 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65530 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 3 + neighbor 192.0.2.2 update-source 192.0.2.1 + address-family ipv4 unicast + network 198.51.100.0/24 + network 203.0.113.0/24 + network 10.0.0.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_rpki_topo1/r1/rtrd.py b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py new file mode 100755 index 0000000000..f8277b4751 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py @@ -0,0 +1,319 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright (C) 2023 Tomas Hlavacek (tmshlvck@gmail.com) + +from typing import List, Tuple, Callable, Type +import socket +import threading +import socketserver +import struct +import ipaddress +import csv +import os +import sys + +LISTEN_HOST, LISTEN_PORT = "0.0.0.0", 15432 +VRPS_FILE = os.path.join(sys.path[0], "vrps.csv") + + +def dbg(m: str): + print(m) + + +class RTRDatabase(object): + def __init__(self, vrps_file: str) -> None: + self.last_serial = 0 + self.ann4 = [] + self.ann6 = [] + self.withdraw4 = [] + self.withdraw6 = [] + + with open(vrps_file, "r") as fh: + for rasn, rnet, rmaxlen, _ in csv.reader(fh): + try: + net = ipaddress.ip_network(rnet) + asn = int(rasn[2:]) + maxlen = int(rmaxlen) + if net.version == 6: + self.ann6.append((asn, str(net), maxlen)) + elif net.version == 4: + self.ann4.append((asn, str(net), maxlen)) + else: + raise ValueError(f"Unknown AFI: {net.version}") + except Exception as e: + dbg( + f"VRPS load: ignoring {str((rasn, rnet,rmaxlen))} because {str(e)}" + ) + + def get_serial(self) -> int: + return self.last_serial + + def set_serial(self, serial: int) -> None: + self.last_serial = serial + + def get_announcements4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann4 + else: + return [] + + def get_withdrawals4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw4 + else: + return [] + + def get_announcements6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann6 + else: + return [] + + def get_withdrawals6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw6 + else: + return [] + + +class RTRConnHandler(socketserver.BaseRequestHandler): + PROTO_VERSION = 0 + + def setup(self) -> None: + self.session_id = 2345 + self.serial = 1024 + + dbg(f"New connection from: {str(self.client_address)} ") + # TODO: register for notifies + + def finish(self) -> None: + pass + # TODO: de-register + + HEADER_LEN = 8 + + def decode_header(self, buf: bytes) -> Tuple[int, int, int, int]: + # common header in all received packets + return struct.unpack("!BBHI", buf) + # reutnrs (proto_ver, pdu_type, sess_id, length) + + SERNOTIFY_TYPE = 0 + SERNOTIFY_LEN = 12 + + def send_sernotify(self, serial: int) -> None: + # serial notify PDU + dbg(f" None: + # cache response PDU + dbg(f"Serial query: {serial}") + if sess_id: + self.server.db.set_serial(serial) + else: + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals4(serial): + self.withdraw_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(serial): + self.announce_ipv6(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals6(serial): + self.withdraw_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + RESET_TYPE = 2 + + def handle_reset(self): + dbg(">Reset") + self.session_id += 1 + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(self.serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(self.serial): + self.announce_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + ERROR_TYPE = 10 + + def handle_error(self, buf: bytes): + dbg(f">Error: {str(buf)}") + + def handle(self): + while True: + b = self.request.recv(self.HEADER_LEN, socket.MSG_WAITALL) + if len(b) == 0: + break + proto_ver, pdu_type, sess_id, length = self.decode_header(b) + dbg( + f">Header proto_ver={proto_ver} pdu_type={pdu_type} sess_id={sess_id} length={length}" + ) + + if sess_id: + self.session_id = sess_id + + if pdu_type == self.SERIAL_QUERY_TYPE: + b = self.request.recv( + self.SERIAL_QUERY_LEN - self.HEADER_LEN, socket.MSG_WAITALL + ) + self.handle_serial_query(b, sess_id) + + elif pdu_type == self.RESET_TYPE: + self.handle_reset() + + elif pdu_type == self.ERROR_TYPE: + b = self.request.recv(length - self.HEADER_LEN, socket.MSG_WAITALL) + self.handle_error(b) + + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + def __init__( + self, bind: Tuple[str, int], handler: Type[RTRConnHandler], db: RTRDatabase + ) -> None: + super().__init__(bind, handler) + self.db = db + + +def main(): + db = RTRDatabase(VRPS_FILE) + server = ThreadedTCPServer((LISTEN_HOST, LISTEN_PORT), RTRConnHandler, db) + dbg(f"Server listening on {LISTEN_HOST} port {LISTEN_PORT}") + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/tests/topotests/bgp_rpki_topo1/r1/staticd.conf b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf new file mode 100644 index 0000000000..7f2f057bfe --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.1.2 diff --git a/tests/topotests/bgp_rpki_topo1/r1/vrps.csv b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv new file mode 100644 index 0000000000..5a6e023bb9 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv @@ -0,0 +1,3 @@ +ASN,IP Prefix,Max Length,Trust Anchor +AS65530,198.51.100.0/24,24,private +AS65530,203.0.113.0/24,24,private diff --git a/tests/topotests/bgp_rpki_topo1/r1/zebra.conf b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf new file mode 100644 index 0000000000..b742b70356 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json new file mode 100644 index 0000000000..a04e9ef6ce --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json @@ -0,0 +1,37 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json new file mode 100644 index 0000000000..01e288c86f --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json @@ -0,0 +1,7 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json new file mode 120000 index 0000000000..2645bfaf0e --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json @@ -0,0 +1 @@ +bgp_table_rpki_valid.json \ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json new file mode 100644 index 0000000000..5546d45c67 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json @@ -0,0 +1,52 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json new file mode 100644 index 0000000000..7b9a5c875b --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json @@ -0,0 +1,22 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json new file mode 100644 index 0000000000..eb3852a789 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json @@ -0,0 +1,35 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf new file mode 100644 index 0000000000..95b1e5bdc1 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.0.2.1 remote-as 65530 + neighbor 192.0.2.1 timers connect 1 + neighbor 192.0.2.1 ebgp-multihop 3 + neighbor 192.0.2.1 update-source 192.0.2.2 +! +router bgp 65002 vrf vrf10 + no bgp ebgp-requires-policy + neighbor 192.0.2.3 remote-as 65530 + neighbor 192.0.2.3 timers 1 3 + neighbor 192.0.2.3 timers connect 1 + neighbor 192.0.2.3 ebgp-multihop 3 + neighbor 192.0.2.3 update-source 192.0.2.2 +! +rpki + rpki retry_interval 5 + rpki cache 192.0.2.1 15432 preference 1 +exit diff --git a/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json new file mode 100644 index 0000000000..fbc5cc9f07 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json @@ -0,0 +1,18 @@ +{ + "prefixes":[ + { + "prefix":"198.51.100.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + }, + { + "prefix":"203.0.113.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + } + ], + "ipv4PrefixCount":2, + "ipv6PrefixCount":0 +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/staticd.conf b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf new file mode 100644 index 0000000000..e3f5d7dba0 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.1/32 192.168.1.1 diff --git a/tests/topotests/bgp_rpki_topo1/r2/zebra.conf b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf new file mode 100644 index 0000000000..96865f0b62 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf @@ -0,0 +1,9 @@ +interface lo + ip address 192.0.2.2/32 +! +interface vrf10 vrf vrf10 + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py new file mode 100644 index 0000000000..5221b6963d --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + " -M bgpd_rpki" if rname == "r2" else "", + ) + + tgen.start_router() + + r1_path = os.path.join(CWD, "r1") + + global rtrd_process + + tgen.gears["r1"].cmd("chmod u+x {}/rtrd.py".format(r1_path)) + rtrd_process = tgen.gears["r1"].popen("python3 {}/rtrd.py".format(r1_path)) + + +def teardown_module(mod): + tgen = get_topogen() + + logger.info("r1: sending SIGTERM to rtrd RPKI server") + rtrd_process.kill() + tgen.stop_topology() + + +def show_rpki_prefixes(rname, expected, vrf=None): + tgen = get_topogen() + + if vrf: + cmd = "show rpki prefix-table vrf {} json".format(vrf) + else: + cmd = "show rpki prefix-table json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + return topotest.json_cmp(output, expected) + + +def show_bgp_ipv4_table_rpki(rname, rpki_state, expected, vrf=None): + tgen = get_topogen() + + cmd = "show bgp" + if vrf: + cmd += " vrf {}".format(vrf) + cmd += " ipv4 unicast" + if rpki_state: + cmd += " rpki {}".format(rpki_state) + cmd += " json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + expected_nb = len(expected.get("routes")) + output_nb = len(output.get("routes", {})) + + if expected_nb != output_nb: + return {"error": "expected {} prefixes. Got {}".format(expected_nb, output_nb)} + + return topotest.json_cmp(output, expected) + + +def test_show_bgp_rpki_prefixes(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + rname = "r2" + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, rname, rpki_state, expected_json + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_no_rpki_cache(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _show_rpki_no_connection(rname): + output = json.loads( + tgen.gears[rname].vtysh_cmd("show rpki cache-connection json") + ) + + return output == {"error": "No connection to RPKI cache server."} + + step("Remove RPKI server from configuration") + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + no rpki cache 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI connection state") + + test_func = functools.partial(_show_rpki_no_connection, rname) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "RPKI is still connected on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_reconnect(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Restore RPKI server configuration") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + rpki cache 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Apply RPKI valid route-map on neighbor") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +route-map RPKI permit 10 + match rpki valid +! +router bgp 65002 + address-family ipv4 unicast + neighbor 192.0.2.1 route-map RPKI in +""" + ) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rmap_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, + rname, + rpki_state, + expected_json, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) -- 2.39.5