Merge pull request #9491 from opensourcerouting/totally-nssa

ospf6d: add support for NSSA totally stub areas
This commit is contained in:
Russ White 2021-09-07 19:44:52 -04:00 committed by GitHub
commit 5e6004e53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 35 deletions

View File

@ -176,9 +176,9 @@ OSPF6 area
The `not-advertise` option, when present, prevents the summary route from The `not-advertise` option, when present, prevents the summary route from
being advertised, effectively filtering the summarized routes. 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). 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 4. Support for NSSA Translator functionality when there are multiple NSSA
ABR in an area. 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:
OSPF6 interface OSPF6 interface

View File

@ -748,7 +748,15 @@ void ospf6_abr_defaults_to_stub(struct ospf6 *o)
def->path.cost = metric_value(o, type, 0); def->path.cost = metric_value(o, type, 0);
for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) { 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 /* withdraw defaults when an area switches from stub to
* non-stub */ * non-stub */
route = ospf6_route_lookup(&def->prefix, 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); SET_FLAG(def->flag, OSPF6_ROUTE_REMOVE);
ospf6_abr_originate_summary_to_area(def, oa); 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); ospf6_route_delete(def);

View File

@ -46,6 +46,9 @@
#include "ospf6d.h" #include "ospf6d.h"
#include "lib/json.h" #include "lib/json.h"
#include "ospf6_nssa.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_AREA, "OSPF6 area");
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PLISTNAME, "Prefix list name"); 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 else
vty_out(vty, " area %s stub\n", oa->name); vty_out(vty, " area %s stub\n", oa->name);
} }
if (IS_AREA_NSSA(oa)) if (IS_AREA_NSSA(oa)) {
vty_out(vty, " area %s nssa\n", oa->name); 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)) if (PREFIX_NAME_IN(oa))
vty_out(vty, " area %s filter-list prefix %s in\n", vty_out(vty, " area %s filter-list prefix %s in\n",
oa->name, PREFIX_NAME_IN(oa)); oa->name, PREFIX_NAME_IN(oa));
@ -1250,18 +1257,18 @@ DEFUN (no_ospf6_area_stub_no_summary,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd, DEFPY(ospf6_area_nssa, ospf6_area_nssa_cmd,
"area <A.B.C.D|(0-4294967295)> nssa", "area <A.B.C.D|(0-4294967295)>$area_str nssa [no-summary$no_summary]",
"OSPF6 area parameters\n" "OSPF6 area parameters\n"
"OSPF6 area ID in IP address format\n" "OSPF6 area ID in IP address format\n"
"OSPF6 area ID as a decimal value\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; struct ospf6_area *area;
VTY_DECLVAR_CONTEXT(ospf6, ospf6); 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)) { if (!ospf6_area_nssa_set(ospf6, area)) {
vty_out(vty, vty_out(vty,
@ -1269,26 +1276,32 @@ DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd,
return CMD_WARNING_CONFIG_FAILED; 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; return CMD_SUCCESS;
} }
DEFUN(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd, DEFPY(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd,
"no area <A.B.C.D|(0-4294967295)> nssa", "no area <A.B.C.D|(0-4294967295)>$area_str nssa [no-summary$no_summary]",
NO_STR NO_STR
"OSPF6 area parameters\n" "OSPF6 area parameters\n"
"OSPF6 area ID in IP address format\n" "OSPF6 area ID in IP address format\n"
"OSPF6 area ID as a decimal value\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; struct ospf6_area *area;
VTY_DECLVAR_CONTEXT(ospf6, ospf6); 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_nssa_unset(ospf6, area);
ospf6_area_no_summary_unset(ospf6, area);
return CMD_SUCCESS; return CMD_SUCCESS;
} }

View File

@ -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 */ /* Check the forwarding address */
if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) {
offset = sizeof(*external) offset = sizeof(*external)

View File

@ -92,6 +92,7 @@ ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la
clippy_scan += \ clippy_scan += \
ospf6d/ospf6_top.c \ ospf6d/ospf6_top.c \
ospf6d/ospf6_area.c \
ospf6d/ospf6_asbr.c \ ospf6d/ospf6_asbr.c \
ospf6d/ospf6_lsa.c \ ospf6d/ospf6_lsa.c \
ospf6d/ospf6_gr_helper.c \ ospf6d/ospf6_gr_helper.c \

View File

@ -72,14 +72,20 @@ def expect_lsas(router, area, lsas, wait=5, extra_params=""):
assert result is None, assertmsg 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." "Run command `ipv6 ospf6 route` and expect route with type."
tgen = get_topogen() tgen = get_topogen()
if detail == False: 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: 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)) logger.info("waiting OSPFv3 router '{}' route".format(router))
test_func = partial( test_func = partial(
@ -91,6 +97,21 @@ def expect_ospfv3_routes(router, routes, wait=5, detail=False):
assert result is None, assertmsg 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): def build_topo(tgen):
"Build function" "Build function"
@ -338,13 +359,6 @@ def test_nssa_lsa_type7():
return lsa return lsa
return None 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") logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away")
# Test that LSA doesn't exist. # Test that LSA doesn't exist.
@ -354,12 +368,69 @@ def test_nssa_lsa_type7():
assert result is None, assertmsg assert result is None, assertmsg
# Test that route doesn't exist. # 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) _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
assertmsg = '"{}" route still exists'.format("r4") assertmsg = '"{}" route still exists'.format("r4")
assert result is None, assertmsg 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): def teardown_module(_mod):
"Teardown the pytest environment" "Teardown the pytest environment"
tgen = get_topogen() tgen = get_topogen()