From 29e148da33f7b920efb47a12a159ed947e312835 Mon Sep 17 00:00:00 2001 From: zhou-run Date: Thu, 20 Jun 2024 11:24:24 +0800 Subject: [PATCH] isisd: When the ISIS types of the routers do not match on a P2P link, the neighbor status remains UP Test Scenario: RouterA and RouterB are in the same routing domain and have configured a P2P link. RouterA is configured with "is-type level-1" while RouterB is configured with "is-type level-1-2". They establish a level-1 UP neighborship. In this scenario, we expect that when RouterB's configuration is switched to "is-type level-2-only", the neighborship status on both RouterA and RouterB would be non-UP. However, RouterB still shows the neighbor as UP. Upon receiving a P2P Hello packet, the function "process_p2p_hello" is invoked. According to the ISO/IEC 10589 protocol specification, section 8.2.5.2 a) and tables 5 and 7, if the "iih->circ_type" of the neighbor's hello packet does not match one's own "circuit->is_type," we may choose to take no action. When establishing a neighborship for the first time, the neighbor's status can remain in the "Initializing" state. However, if the neighborship has already been established and one's own "circuit->is_type" changes, the neighbor's UP status cannot be reset. Therefore, when processing P2P Hello packets, we should be cognizant of changes in our own link adjacency type. Signed-off-by: zhou-run --- isisd/isis_pdu.c | 3 +- tests/topotests/isis_topo1/test_isis_topo1.py | 134 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c index 5be317018e4e..bf498ffd11d5 100644 --- a/isisd/isis_pdu.c +++ b/isisd/isis_pdu.c @@ -231,7 +231,8 @@ static int process_p2p_hello(struct iih_info *iih) return ISIS_OK; } } - if (!adj || adj->level != iih->calculated_type) { + if (!adj || adj->level != iih->calculated_type || + !(iih->circuit->is_type & iih->circ_type)) { if (!adj) { adj = isis_new_adj(iih->sys_id, NULL, iih->calculated_type, iih->circuit); diff --git a/tests/topotests/isis_topo1/test_isis_topo1.py b/tests/topotests/isis_topo1/test_isis_topo1.py index cea284963d1e..2362ce71c0b5 100644 --- a/tests/topotests/isis_topo1/test_isis_topo1.py +++ b/tests/topotests/isis_topo1/test_isis_topo1.py @@ -628,6 +628,140 @@ def test_isis_hello_padding_during_adjacency_formation(): result = check_last_iih_packet_for_padding(r1, expect_padding=False) assert result is True, result +def test_isis_neighbor_state(): + "Check that the neighbor states remain normal when the ISIS type is switched." + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking 'show isis neighbor json'") + + # Establish a P2P link + # When the IS-IS type of r3 is set to level-1-2 and the IS-IS type of r5 is set to level-1, + # it is expected that all neighbors exist and are in the Up state + r3 = tgen.gears["r3"] + r3.vtysh_cmd( + """ + configure + interface r3-eth1 + isis network point-to-point + end + """ + ) + r5 = tgen.gears["r5"] + r5.vtysh_cmd( + """ + configure + interface r5-eth0 + isis network point-to-point + end + """ + ) + result = _check_isis_neighbor_json("r3", "r5", True, "Up") + assert result is True, result + result = _check_isis_neighbor_json("r5", "r3", True, "Up") + assert result is True, result + + # Remove the configuration that affects the switch of IS-IS type. + # Configure the IS-IS type of r3 to transition from level-1-2 to level-2-only, + # while maintaining the IS-IS type of r5 as level-1. + # In this scenario, + # the expectation is that some neighbors do not exist or are in the Initializing state + r3.vtysh_cmd( + """ + configure + router isis 1 + no redistribute ipv4 connected level-1 + no redistribute ipv6 connected level-1 + is-type level-2-only + interface r3-eth1 + isis circuit-type level-2-only + end + """ + ) + result = _check_isis_neighbor_json("r3", "r5", False, "Initializing") + assert result is True, result + result = _check_isis_neighbor_json("r5", "r3", False, "Initializing") + assert result is True, result + + # Restore to initial configuration + r3.vtysh_cmd( + """ + configure + router isis 1 + no is-type + redistribute ipv4 connected level-1 + redistribute ipv6 connected level-1 + interface r3-eth1 + isis circuit-type level-1 + no isis network point-to-point + end + """ + ) + r5.vtysh_cmd( + """ + configure + interface r5-eth0 + no isis network point-to-point + end + """ + ) + result = _check_isis_neighbor_json("r3", "r5", True, "Up") + assert result is True, result + result = _check_isis_neighbor_json("r5", "r3", True, "Up") + assert result is True, result + + +def _check_isis_neighbor_json(self, neighbor, neighbor_expected, neighbor_state_expected): + tgen = get_topogen() + router = tgen.gears[self] + logger.info(f"check_isis_neighbor_json {router}") + + result = _check_isis_neighbor_exist(self, neighbor) + if result == True: + return _check_isis_neighbor_state(self, neighbor, neighbor_state_expected) + elif neighbor_expected == True: + return "{} with expected neighbor {} got none ".format( + router.name, neighbor + ) + else: + return True + +@retry(retry_timeout=5) +def _check_isis_neighbor_exist(self, neighbor): + tgen = get_topogen() + router = tgen.gears[self] + logger.info(f"check_isis_neighbor_exist {router}") + isis_neightor_output = router.vtysh_cmd( + "show isis neighbor {} json".format(neighbor) + ) + + neighbor_json = json.loads(isis_neightor_output) + if "state" not in neighbor_json["areas"][0]["circuits"][1]["interface"]: + return "The neighbor {} of router {} has not been learned yet ".format( + neighbor, router.name + ) + + return True + +@retry(retry_timeout=5) +def _check_isis_neighbor_state(self, neighbor, neighbor_state_expected): + tgen = get_topogen() + router = tgen.gears[self] + logger.info(f"check_isis_neighbor_state {router}") + isis_neightor_output = router.vtysh_cmd( + "show isis neighbor {} json".format(neighbor) + ) + + neighbor_json = json.loads(isis_neightor_output) + neighbor_state = neighbor_json["areas"][0]["circuits"][1]["interface"]["state"] + if neighbor_state == neighbor_state_expected: + return True + return "{} peer with expected neighbor_state {} got {} ".format( + router.name, neighbor_state_expected, neighbor_state + ) @retry(retry_timeout=5) def check_last_iih_packet_for_padding(router, expect_padding):