diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index effad0fd00..3430d8a282 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -648,7 +648,7 @@ Interfaces it's recommended to set the hello delay and hello interval with the same values. The default value is 10 seconds. -.. clicmd:: ip ospf network (broadcast|non-broadcast|point-to-multipoint|point-to-point [dmvpn]) +.. clicmd:: ip ospf network (broadcast|non-broadcast|point-to-multipoint [delay-reflood]|point-to-point [dmvpn]) When configuring a point-to-point network on an interface and the interface has a /32 address associated with then OSPF will treat the interface @@ -660,6 +660,13 @@ Interfaces point-to-point, but the HUB will be a point-to-multipoint. To make this topology work, specify the optional 'dmvpn' parameter at the spoke. + When the network is configured as point-to-multipoint and `delay-reflood` + is specified, LSAs received on the interface from neighbors on the + interface will not be flooded back out on the interface immediately. + Rather, they will be added to the neighbor's link state retransmission + list and only sent to the neighbor if the neighbor doesn't acknowledge + the LSA prior to the link state retransmission timer expiring. + Set explicitly network type for specified interface. .. clicmd:: ip ospf priority (0-255) diff --git a/lib/libospf.h b/lib/libospf.h index 676b563fff..9eaca9a1a8 100644 --- a/lib/libospf.h +++ b/lib/libospf.h @@ -68,6 +68,7 @@ extern "C" { #define OSPF_MTU_IGNORE_DEFAULT 0 #define OSPF_FAST_HELLO_DEFAULT 0 +#define OSPF_P2MP_DELAY_REFLOOD_DEFAULT false #define OSPF_AREA_BACKBONE 0x00000000 /* 0.0.0.0 */ #define OSPF_AREA_RANGE_COST_UNSPEC -1U diff --git a/ospfd/ospf_flood.c b/ospfd/ospf_flood.c index f3fe504a07..a4d0f77faf 100644 --- a/ospfd/ospf_flood.c +++ b/ospfd/ospf_flood.c @@ -770,15 +770,26 @@ int ospf_flood_through_interface(struct ospf_interface *oi, OSPF_SEND_PACKET_DIRECT); } } else - /* Optimization: for P2MP interfaces, - don't send back out the incoming interface immediately, - allow time to rx multicast ack to the rx'ed (multicast) - update */ - if (retx_flag != 1 || - oi->type != OSPF_IFTYPE_POINTOMULTIPOINT || inbr == NULL || - oi != inbr->oi) - ospf_ls_upd_send_lsa(oi->nbr_self, lsa, - OSPF_SEND_PACKET_INDIRECT); + /* If P2MP delayed reflooding is configured and the LSA was + received from a neighbor on the P2MP interface, do not flood + if back out on the interface. The LSA will be retransmitted + upon expiration of each neighbor's retransmission timer. This + will allow time to receive a multicast multicast link state + acknoweldgement and remove the LSA from each neighbor's link + state retransmission list. */ + if (oi->p2mp_delay_reflood && + (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) && + (inbr != NULL) && (oi == inbr->oi)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "Delay reflooding for LSA[%s] from NBR %pI4 on interface %s", + dump_lsa_key(lsa), + inbr ? &(inbr->router_id) + : &(oi->ospf->router_id), + IF_NAME(oi)); + } else + ospf_ls_upd_send_lsa(oi->nbr_self, lsa, + OSPF_SEND_PACKET_INDIRECT); return 0; } diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c index 8da982aed8..2c66cb3cfc 100644 --- a/ospfd/ospf_interface.c +++ b/ospfd/ospf_interface.c @@ -545,6 +545,7 @@ static struct ospf_if_params *ospf_new_if_params(void) oip->is_v_wait_set = false; oip->ptp_dmvpn = 0; + oip->p2mp_delay_reflood = OSPF_P2MP_DELAY_REFLOOD_DEFAULT; return oip; } diff --git a/ospfd/ospf_interface.h b/ospfd/ospf_interface.h index 24768b9ab4..ec1afa1b8b 100644 --- a/ospfd/ospf_interface.h +++ b/ospfd/ospf_interface.h @@ -109,6 +109,9 @@ struct ospf_if_params { /* point-to-point DMVPN configuration */ uint8_t ptp_dmvpn; + + /* point-to-multipoint delayed reflooding configuration */ + bool p2mp_delay_reflood; }; enum { MEMBER_ALLROUTERS = 0, @@ -177,6 +180,9 @@ struct ospf_interface { /* point-to-point DMVPN configuration */ uint8_t ptp_dmvpn; + /* point-to-multipoint delayed reflooding */ + bool p2mp_delay_reflood; + /* State of Interface State Machine. */ uint8_t state; diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index f92be3fca5..0ee42e0e70 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -3958,6 +3958,16 @@ static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf, /* OSPF Authentication information */ ospf_interface_auth_show(vty, oi, json_interface_sub, use_json); + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + if (use_json) + json_object_boolean_add(json_interface_sub, + "p2mpDelayReflood", + oi->p2mp_delay_reflood); + else + vty_out(vty, + " %sDelay reflooding LSAs received on P2MP interface\n", + oi->p2mp_delay_reflood ? "" : "Don't "); + } } } @@ -8308,13 +8318,17 @@ DEFUN_HIDDEN (no_ospf_hello_interval, } DEFUN(ip_ospf_network, ip_ospf_network_cmd, - "ip ospf network ", + "ip ospf network ", "IP Information\n" "OSPF interface commands\n" "Network type\n" "Specify OSPF broadcast multi-access network\n" "Specify OSPF NBMA network\n" "Specify OSPF point-to-multipoint network\n" + "Specify OSPF delayed reflooding of LSAs received on P2MP interface\n" "Specify OSPF point-to-point network\n" "Specify OSPF point-to-point DMVPN network\n") { @@ -8322,6 +8336,7 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd, int idx = 0; int old_type = IF_DEF_PARAMS(ifp)->type; uint8_t old_ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + uint8_t old_p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; struct route_node *rn; if (old_type == OSPF_IFTYPE_LOOPBACK) { @@ -8331,21 +8346,26 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd, } IF_DEF_PARAMS(ifp)->ptp_dmvpn = 0; + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = + OSPF_P2MP_DELAY_REFLOOD_DEFAULT; if (argv_find(argv, argc, "broadcast", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_BROADCAST; else if (argv_find(argv, argc, "non-broadcast", &idx)) IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_NBMA; - else if (argv_find(argv, argc, "point-to-multipoint", &idx)) + else if (argv_find(argv, argc, "point-to-multipoint", &idx)) { IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOMULTIPOINT; - else if (argv_find(argv, argc, "point-to-point", &idx)) { + if (argv_find(argv, argc, "delay-reflood", &idx)) + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = true; + } else if (argv_find(argv, argc, "point-to-point", &idx)) { IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOPOINT; if (argv_find(argv, argc, "dmvpn", &idx)) IF_DEF_PARAMS(ifp)->ptp_dmvpn = 1; } - if (IF_DEF_PARAMS(ifp)->type == old_type - && IF_DEF_PARAMS(ifp)->ptp_dmvpn == old_ptp_dmvpn) + if (IF_DEF_PARAMS(ifp)->type == old_type && + IF_DEF_PARAMS(ifp)->ptp_dmvpn == old_ptp_dmvpn && + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood == old_p2mp_delay_reflood) return CMD_SUCCESS; SET_IF_PARAM(IF_DEF_PARAMS(ifp), type); @@ -8357,10 +8377,19 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd, continue; oi->type = IF_DEF_PARAMS(ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; - if (oi->state > ISM_Down) { - OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); - OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + /* + * The OSPF interface only needs to be flapped if the network + * type or DMVPN parameter changes. + */ + if (IF_DEF_PARAMS(ifp)->type != old_type || + IF_DEF_PARAMS(ifp)->ptp_dmvpn != old_ptp_dmvpn) { + if (oi->state > ISM_Down) { + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } } } @@ -8398,6 +8427,8 @@ DEFUN (no_ip_ospf_network, IF_DEF_PARAMS(ifp)->type = ospf_default_iftype(ifp); IF_DEF_PARAMS(ifp)->ptp_dmvpn = 0; + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = + OSPF_P2MP_DELAY_REFLOOD_DEFAULT; if (IF_DEF_PARAMS(ifp)->type == old_type) return CMD_SUCCESS; @@ -8409,6 +8440,8 @@ DEFUN (no_ip_ospf_network, continue; oi->type = IF_DEF_PARAMS(ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; if (oi->state > ISM_Down) { OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); @@ -11817,6 +11850,10 @@ static int config_write_interface_one(struct vty *vty, struct vrf *vrf) == OSPF_IFTYPE_POINTOPOINT && params->ptp_dmvpn) vty_out(vty, " dmvpn"); + if (params->type == + OSPF_IFTYPE_POINTOMULTIPOINT && + params->p2mp_delay_reflood) + vty_out(vty, " delay-reflood"); if (params != IF_DEF_PARAMS(ifp) && rn) vty_out(vty, " %pI4", &rn->p.u.prefix4); diff --git a/ospfd/ospfd.c b/ospfd/ospfd.c index eae1f301a2..51e937f42c 100644 --- a/ospfd/ospfd.c +++ b/ospfd/ospfd.c @@ -1113,6 +1113,7 @@ struct ospf_interface *add_ospf_interface(struct connected *co, skip network type setting. */ oi->type = IF_DEF_PARAMS(co->ifp)->type; oi->ptp_dmvpn = IF_DEF_PARAMS(co->ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(co->ifp)->p2mp_delay_reflood; /* Add pseudo neighbor. */ ospf_nbr_self_reset(oi, oi->ospf->router_id); diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py b/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py index 4f797743e7..a90d7dbdc0 100644 --- a/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py +++ b/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py @@ -363,6 +363,185 @@ def test_ospf_p2mp_tc1_p0(request): write_test_footer(tc_name) +def test_ospf_p2mp_tc_delay_reflood(request): + """OSPF IFSM -Verify "delay-reflood" parameter in p2mp network.""" + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + r0 = tgen.gears["r0"] + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + global topo + + step("Verify for interface with network type P2MP that delay-reflood is configured") + r0.vtysh_multicmd( + "conf t\ninterface r0-r1-eth0\nip ospf network point-to-multipoint delay-reflood" + ) + + dut = "r0" + input_dict = { + "r0": { + "links": { + "r1": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": True, + } + }, + "r2": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + "r3": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + } + } + } + result = verify_ospf_interface(tgen, topo, dut=dut, input_dict=input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + delay_reflood_cfg = ( + tgen.net["r0"] + .cmd( + 'vtysh -c "show running" | grep "^ ip ospf network point-to-multipoint delay-reflood"' + ) + .rstrip() + ) + + assertmsg = "delay-reflood' configuration applied, but not present in configuration" + assert ( + delay_reflood_cfg == " ip ospf network point-to-multipoint delay-reflood" + ), assertmsg + + step("Verify for interface with network type P2MP that delay-reflood is removed") + r0.vtysh_multicmd( + "conf t\ninterface r0-r1-eth0\nip ospf network point-to-multipoint" + ) + + input_dict = { + "r0": { + "links": { + "r1": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + "r2": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + "r3": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + } + } + } + result = verify_ospf_interface(tgen, topo, dut=dut, input_dict=input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + delay_reflood_cfg = ( + tgen.net["r0"] + .cmd( + 'vtysh -c "show running" | grep "^ ip ospf network point-to-multipoint delay-reflood"' + ) + .rstrip() + ) + assertmsg = ( + "delay-reflood' configuration removed, but still present in configuration" + ) + assert ( + delay_reflood_cfg != " ip ospf network point-to-multipoint delay-reflood" + ), assertmsg + + step( + "Verify for interface with network type P2MP that delay-reflood is removed with removal of network type" + ) + r0.vtysh_multicmd( + "conf t\ninterface r0-r1-eth0\nip ospf network point-to-multipoint delay-reflood" + ) + r0.vtysh_multicmd( + "conf t\ninterface r0-r1-eth0\nno ip ospf network point-to-multipoint" + ) + r0.vtysh_multicmd( + "conf t\ninterface r0-r1-eth0\nip ospf network point-to-multipoint" + ) + + input_dict = { + "r0": { + "links": { + "r1": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + "r2": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + "r3": { + "ospf": { + "mcastMemberOspfAllRouters": True, + "ospfEnabled": True, + "networkType": "POINTOMULTIPOINT", + "p2mpDelayReflood": False, + } + }, + } + } + } + result = verify_ospf_interface(tgen, topo, dut=dut, input_dict=input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + delay_reflood_cfg = ( + tgen.net["r0"] + .cmd( + 'vtysh -c "show running" | grep "^ ip ospf network point-to-multipoint delay-reflood"' + ) + .rstrip() + ) + assertmsg = ( + "delay-reflood' configuration removed, but still present in configuration" + ) + assert ( + delay_reflood_cfg != " ip ospf network point-to-multipoint delay-reflood" + ), assertmsg + + write_test_footer(tc_name) + + @retry(retry_timeout=30) def verify_ospf_json(tgen, dut, input_dict, cmd="show ip ospf database json"): del tgen