diff --git a/doc/user/ospf6d.rst b/doc/user/ospf6d.rst index 499788ae87..4fd86ffb13 100644 --- a/doc/user/ospf6d.rst +++ b/doc/user/ospf6d.rst @@ -176,9 +176,9 @@ OSPF6 area The `not-advertise` option, when present, prevents the summary route from being advertised, effectively filtering the summarized routes. -.. clicmd:: area A.B.C.D nssa +.. clicmd:: area A.B.C.D nssa [no-summary] -.. clicmd:: area (0-4294967295) nssa +.. clicmd:: area (0-4294967295) nssa [no-summary] Configure the area to be a NSSA (Not-So-Stubby Area). @@ -194,6 +194,10 @@ OSPF6 area 4. Support for NSSA Translator functionality when there are multiple NSSA ABR in an area. + An NSSA ABR can be configured with the `no-summary` option to prevent the + advertisement of summaries into the area. In that case, a single Type-3 LSA + containing a default route is originated into the NSSA. + .. _ospf6-interface: OSPF6 interface diff --git a/ospf6d/ospf6_abr.c b/ospf6d/ospf6_abr.c index 650262f1ae..aa85475678 100644 --- a/ospf6d/ospf6_abr.c +++ b/ospf6d/ospf6_abr.c @@ -748,7 +748,15 @@ void ospf6_abr_defaults_to_stub(struct ospf6 *o) def->path.cost = metric_value(o, type, 0); for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) { - if (!IS_AREA_STUB(oa)) { + if (IS_AREA_STUB(oa) || (IS_AREA_NSSA(oa) && oa->no_summary)) { + /* announce defaults to stubby areas */ + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "Announcing default route into stubby area %s", + oa->name); + UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); + ospf6_abr_originate_summary_to_area(def, oa); + } else { /* withdraw defaults when an area switches from stub to * non-stub */ route = ospf6_route_lookup(&def->prefix, @@ -762,14 +770,6 @@ void ospf6_abr_defaults_to_stub(struct ospf6 *o) SET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); ospf6_abr_originate_summary_to_area(def, oa); } - } else { - /* announce defaults to stubby areas */ - if (IS_OSPF6_DEBUG_ABR) - zlog_debug( - "Announcing default route into stubby area %s", - oa->name); - UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); - ospf6_abr_originate_summary_to_area(def, oa); } } ospf6_route_delete(def); diff --git a/ospf6d/ospf6_area.c b/ospf6d/ospf6_area.c index 098132b1f6..71d32b409c 100644 --- a/ospf6d/ospf6_area.c +++ b/ospf6d/ospf6_area.c @@ -46,6 +46,9 @@ #include "ospf6d.h" #include "lib/json.h" #include "ospf6_nssa.h" +#ifndef VTYSH_EXTRACT_PL +#include "ospf6d/ospf6_area_clippy.c" +#endif DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_AREA, "OSPF6 area"); DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PLISTNAME, "Prefix list name"); @@ -643,8 +646,12 @@ void ospf6_area_config_write(struct vty *vty, struct ospf6 *ospf6) else vty_out(vty, " area %s stub\n", oa->name); } - if (IS_AREA_NSSA(oa)) - vty_out(vty, " area %s nssa\n", oa->name); + if (IS_AREA_NSSA(oa)) { + vty_out(vty, " area %s nssa", oa->name); + if (oa->no_summary) + vty_out(vty, " no-summary"); + vty_out(vty, "\n"); + } if (PREFIX_NAME_IN(oa)) vty_out(vty, " area %s filter-list prefix %s in\n", oa->name, PREFIX_NAME_IN(oa)); @@ -1250,18 +1257,18 @@ DEFUN (no_ospf6_area_stub_no_summary, return CMD_SUCCESS; } -DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd, - "area nssa", +DEFPY(ospf6_area_nssa, ospf6_area_nssa_cmd, + "area $area_str nssa [no-summary$no_summary]", "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" - "Configure OSPF6 area as nssa\n") + "Configure OSPF6 area as nssa\n" + "Do not inject inter-area routes into area\n") { - int idx_ipv4_number = 1; struct ospf6_area *area; VTY_DECLVAR_CONTEXT(ospf6, ospf6); - OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + OSPF6_CMD_AREA_GET(area_str, area, ospf6); if (!ospf6_area_nssa_set(ospf6, area)) { vty_out(vty, @@ -1269,26 +1276,32 @@ DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd, return CMD_WARNING_CONFIG_FAILED; } - ospf6_area_no_summary_unset(ospf6, area); + if (no_summary) + ospf6_area_no_summary_set(ospf6, area); + else + ospf6_area_no_summary_unset(ospf6, area); + if (ospf6_check_and_set_router_abr(ospf6)) + ospf6_abr_defaults_to_stub(ospf6); return CMD_SUCCESS; } -DEFUN(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd, - "no area nssa", +DEFPY(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd, + "no area $area_str nssa [no-summary$no_summary]", NO_STR "OSPF6 area parameters\n" "OSPF6 area ID in IP address format\n" "OSPF6 area ID as a decimal value\n" - "Configure OSPF6 area as nssa\n") + "Configure OSPF6 area as nssa\n" + "Do not inject inter-area routes into area\n") { - int idx_ipv4_number = 2; struct ospf6_area *area; VTY_DECLVAR_CONTEXT(ospf6, ospf6); - OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + OSPF6_CMD_AREA_GET(area_str, area, ospf6); ospf6_area_nssa_unset(ospf6, area); + ospf6_area_no_summary_unset(ospf6, area); return CMD_SUCCESS; } diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c index f16a1975a8..b5bf81c213 100644 --- a/ospf6d/ospf6_asbr.c +++ b/ospf6d/ospf6_asbr.c @@ -588,6 +588,32 @@ void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa) } } + /* + * RFC 3101 - Section 2.5: + * "If the destination is a Type-7 default route (destination ID = + * DefaultDestination) and one of the following is true, then do + * nothing with this LSA and consider the next in the list: + * + * o The calculating router is a border router and the LSA has + * its P-bit clear. Appendix E describes a technique + * whereby an NSSA border router installs a Type-7 default + * LSA without propagating it. + * + * o The calculating router is a border router and is + * suppressing the import of summary routes as Type-3 + * summary-LSAs". + */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && external->prefix.prefix_length == 0 + && CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR) + && (CHECK_FLAG(external->prefix.prefix_options, + OSPF6_PREFIX_OPTION_P) + || oa->no_summary)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Skipping Type-7 default route"); + return; + } + /* Check the forwarding address */ if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { offset = sizeof(*external) diff --git a/ospf6d/subdir.am b/ospf6d/subdir.am index ac99e90b26..5a4e4db69b 100644 --- a/ospf6d/subdir.am +++ b/ospf6d/subdir.am @@ -92,6 +92,7 @@ ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la clippy_scan += \ ospf6d/ospf6_top.c \ + ospf6d/ospf6_area.c \ ospf6d/ospf6_asbr.c \ ospf6d/ospf6_lsa.c \ ospf6d/ospf6_gr_helper.c \ diff --git a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py index 3738a0c33e..bea3aeaaf6 100644 --- a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py +++ b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py @@ -72,14 +72,20 @@ def expect_lsas(router, area, lsas, wait=5, extra_params=""): assert result is None, assertmsg -def expect_ospfv3_routes(router, routes, wait=5, detail=False): +def expect_ospfv3_routes(router, routes, wait=5, type=None, detail=False): "Run command `ipv6 ospf6 route` and expect route with type." tgen = get_topogen() if detail == False: - cmd = "show ipv6 ospf6 route json" + if type == None: + cmd = "show ipv6 ospf6 route json" + else: + cmd = "show ipv6 ospf6 route {} json".format(type) else: - cmd = "show ipv6 ospf6 route detail json" + if type == None: + cmd = "show ipv6 ospf6 route detail json" + else: + cmd = "show ipv6 ospf6 route {} detail json".format(type) logger.info("waiting OSPFv3 router '{}' route".format(router)) test_func = partial( @@ -91,6 +97,21 @@ def expect_ospfv3_routes(router, routes, wait=5, detail=False): assert result is None, assertmsg +def dont_expect_route(router, unexpected_route, type=None): + "Specialized test function to expect route go missing" + tgen = get_topogen() + + if type == None: + cmd = "show ipv6 ospf6 route json" + else: + cmd = "show ipv6 ospf6 route {} json".format(type) + + output = tgen.gears[router].vtysh_cmd(cmd, isjson=True) + if unexpected_route in output["routes"]: + return output["routes"][unexpected_route] + return None + + def build_topo(tgen): "Build function" @@ -338,13 +359,6 @@ def test_nssa_lsa_type7(): return lsa return None - def dont_expect_route(unexpected_route): - "Specialized test function to expect route go missing" - output = tgen.gears["r4"].vtysh_cmd("show ipv6 ospf6 route json", isjson=True) - if unexpected_route in output["routes"]: - return output["routes"][unexpected_route] - return None - logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away") # Test that LSA doesn't exist. @@ -354,12 +368,69 @@ def test_nssa_lsa_type7(): assert result is None, assertmsg # Test that route doesn't exist. - test_func = partial(dont_expect_route, "2001:db8:100::/64") + test_func = partial(dont_expect_route, "r4", "2001:db8:100::/64") _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) assertmsg = '"{}" route still exists'.format("r4") assert result is None, assertmsg +def test_nssa_no_summary(): + """ + Test the following: + * Type-3 inter-area routes should be removed when the NSSA no-summary option + is configured; + * A type-3 inter-area default route should be originated into the NSSA area + when the no-summary option is configured; + * Once the no-summary option is unconfigured, all previously existing + Type-3 inter-area routes should be re-added, and the inter-area default + route removed. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # + # Configure area 1 as a NSSA totally stub area. + # + config = """ + configure terminal + router ospf6 + area 2 nssa no-summary + """ + tgen.gears["r2"].vtysh_cmd(config) + + logger.info("Expecting inter-area routes to be removed") + for route in ["2001:db8:1::/64", "2001:db8:2::/64"]: + test_func = partial(dont_expect_route, "r4", route, type="inter-area") + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = "{}'s {} inter-area route still exists".format("r4", route) + assert result is None, assertmsg + + logger.info("Expecting inter-area default-route to be added") + routes = {"::/0": {}} + expect_ospfv3_routes("r4", routes, wait=30, type="inter-area") + + # + # Configure area 1 as a regular NSSA area. + # + config = """ + configure terminal + router ospf6 + area 2 nssa + """ + tgen.gears["r2"].vtysh_cmd(config) + + logger.info("Expecting inter-area routes to be re-added") + routes = {"2001:db8:1::/64": {}, "2001:db8:2::/64": {}} + expect_ospfv3_routes("r4", routes, wait=30, type="inter-area") + + logger.info("Expecting inter-area default route to be removed") + test_func = partial(dont_expect_route, "r4", "::/0", type="inter-area") + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = "{}'s inter-area default route still exists".format("r4") + assert result is None, assertmsg + + def teardown_module(_mod): "Teardown the pytest environment" tgen = get_topogen()