]> git.puffer.fish Git - matthieu/frr.git/commitdiff
ospfd: Add prefix-list filtering of OSPF neighbors on OSPF interface
authorAcee Lindem <acee@lindem.com>
Wed, 17 Apr 2024 20:14:56 +0000 (20:14 +0000)
committerAcee Lindem <acee@lindem.com>
Thu, 18 Apr 2024 15:32:58 +0000 (15:32 +0000)
This commit adds the capabiity to filter OSPF neighbors using a
prefix-list with rules matching the neighbor's IP source address.
Configuration, filtering, immediate neighbor pruning, topo-tests,
and documentation are included. The command is:

     ip ospf neighbor-filter <prefix-list> [A.B.C.D]

Signed-off-by: Acee Lindem <acee@lindem.com>
doc/user/ospfd.rst
ospfd/ospf_interface.c
ospfd/ospf_interface.h
ospfd/ospf_packet.c
ospfd/ospf_vty.c
ospfd/ospf_zebra.c
tests/topotests/ospf_p2mp/test_ospf_p2mp_broadcast.py

index 47f8fad17b620ea52ae67cdf9cb2606c23f3f0e7..5652e34a439d1af2f73456ddd54f870941478f40 100644 (file)
@@ -757,6 +757,32 @@ Interfaces
    optional IPv4 address is specified, the prefix suppression will apply
    to the OSPF interface associated with the specified interface address.
 
+.. clicmd:: ip ospf neighbor-filter NAME [A.B.C.D]
+
+   Configure an IP prefix-list to use to filter packets received from
+   OSPF neighbors on the OSPF interface. The prefix-list should include rules
+   to permit or deny OSPF neighbors by IP source address. This is useful for
+   multi-access interfaces where adjacencies with only a subset of the
+   reachable neighbors are desired. Applications include testing partially
+   meshed topologies, OSPF Denial of Sevice (DoS) mitigation, and avoidance
+   of adjacencies with OSPF neighbors not meeting traffic engineering criteria.
+
+      Example:
+
+.. code-block:: frr
+
+   !
+   ! Prefix-list to block neighbor with source address 10.1.0.2
+   !
+   ip prefix-list nbr-filter seq 10 deny 10.1.0.2/32
+   ip prefix-list nbr-filter seq 200 permit any
+   !
+   ! Configure the neighbor filter prefix-list on interface eth0
+   !
+   interface eth0
+    ip ospf neighbor-filter nbr-filter
+   !
+
 .. clicmd:: ip ospf area (A.B.C.D|(0-4294967295))
 
 
index 319db1efe23f02f9358fc36bf16cb4f2c01f22a4..0b2750101927dec709695e6862e13a09fed96519 100644 (file)
@@ -19,6 +19,7 @@
 #include "zclient.h"
 #include "bfd.h"
 #include "ldp_sync.h"
+#include "plist.h"
 
 #include "ospfd/ospfd.h"
 #include "ospfd/ospf_bfd.h"
@@ -67,6 +68,34 @@ int ospf_interface_neighbor_count(struct ospf_interface *oi)
        return count;
 }
 
+
+void ospf_intf_neighbor_filter_apply(struct ospf_interface *oi)
+{
+       struct route_node *rn;
+       struct ospf_neighbor *nbr = NULL;
+       struct prefix nbr_src_prefix = { AF_INET, IPV4_MAX_BITLEN, { 0 } };
+
+       if (!oi->nbr_filter)
+               return;
+
+       /*
+        * Kill neighbors that don't match the neighbor filter prefix-list
+        * excluding the neighbor for the router itself and any neighbors
+        * that are already down.
+        */
+       for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) {
+               nbr = rn->info;
+               if (nbr && nbr != oi->nbr_self && nbr->state != NSM_Down) {
+                       nbr_src_prefix.u.prefix4 = nbr->src;
+                       if (prefix_list_apply(oi->nbr_filter,
+                                             (struct prefix *)&(
+                                                     nbr_src_prefix)) !=
+                           PREFIX_PERMIT)
+                               OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr);
+               }
+       }
+}
+
 int ospf_if_get_output_cost(struct ospf_interface *oi)
 {
        /* If all else fails, use default OSPF cost */
@@ -526,6 +555,7 @@ static struct ospf_if_params *ospf_new_if_params(void)
        UNSET_IF_PARAM(oip, if_area);
        UNSET_IF_PARAM(oip, opaque_capable);
        UNSET_IF_PARAM(oip, keychain_name);
+       UNSET_IF_PARAM(oip, nbr_filter_name);
 
        oip->auth_crypt = list_new();
 
@@ -544,6 +574,7 @@ static void ospf_del_if_params(struct interface *ifp,
 {
        list_delete(&oip->auth_crypt);
        XFREE(MTYPE_OSPF_IF_PARAMS, oip->keychain_name);
+       XFREE(MTYPE_OSPF_IF_PARAMS, oip->nbr_filter_name);
        ospf_interface_disable_bfd(ifp, oip);
        ldp_sync_info_free(&(oip->ldp_sync_info));
        XFREE(MTYPE_OSPF_IF_PARAMS, oip);
@@ -579,7 +610,8 @@ void ospf_free_if_params(struct interface *ifp, struct in_addr addr)
            !OSPF_IF_PARAM_CONFIGURED(oip, if_area) &&
            !OSPF_IF_PARAM_CONFIGURED(oip, opaque_capable) &&
            !OSPF_IF_PARAM_CONFIGURED(oip, prefix_suppression) &&
-               !OSPF_IF_PARAM_CONFIGURED(oip, keychain_name) &&
+           !OSPF_IF_PARAM_CONFIGURED(oip, keychain_name) &&
+           !OSPF_IF_PARAM_CONFIGURED(oip, nbr_filter_name) &&
            listcount(oip->auth_crypt) == 0) {
                ospf_del_if_params(ifp, oip);
                rn->info = NULL;
index 721ab1a9d7ea945815fa65905b2e1a47502145c3..2e53fbfbfada3db36d4e8e6abb434e5be41a4f21 100644 (file)
@@ -124,6 +124,9 @@ struct ospf_if_params {
 
        /* Opaque LSA capability at interface level (see RFC5250) */
        DECLARE_IF_PARAM(bool, opaque_capable);
+
+       /* Name of prefix-list name for packet source address filtering. */
+       DECLARE_IF_PARAM(char *, nbr_filter_name);
 };
 
 enum { MEMBER_ALLROUTERS = 0,
@@ -242,6 +245,9 @@ struct ospf_interface {
        /* List of configured NBMA neighbor. */
        struct list *nbr_nbma;
 
+       /* Configured prefix-list for filtering neighbors. */
+       struct prefix_list *nbr_filter;
+
        /* Graceful-Restart data. */
        struct {
                struct {
@@ -367,6 +373,7 @@ extern void ospf_crypt_key_add(struct list *list, struct crypt_key *key);
 extern int ospf_crypt_key_delete(struct list *list, uint8_t key_id);
 extern uint8_t ospf_default_iftype(struct interface *ifp);
 extern int ospf_interface_neighbor_count(struct ospf_interface *oi);
+extern void ospf_intf_neighbor_filter_apply(struct ospf_interface *oi);
 
 /* Set all multicast memberships appropriately based on the type and
    state of the interface. */
index 60479ddcd17e534ca7106ca760d12dd99e400eed..87aaccad920ba033b98c8be0bd16c9b90eea75e3 100644 (file)
@@ -23,6 +23,7 @@
 #endif
 #include "vrf.h"
 #include "lib_errors.h"
+#include "plist.h"
 
 #include "ospfd/ospfd.h"
 #include "ospfd/ospf_network.h"
@@ -2746,6 +2747,20 @@ static enum ospf_read_return_enum ospf_read_helper(struct ospf *ospf)
        /* associate packet with ospf interface */
        oi = ospf_if_lookup_recv_if(ospf, iph->ip_src, ifp);
 
+       /*
+        * If a neighbor filter prefix-list is configured, apply it to the IP
+        * source address and ignore the packet if it doesn't match.
+        */
+       if (oi && oi->nbr_filter) {
+               struct prefix ip_src_prefix = { AF_INET, IPV4_MAX_BITLEN, { 0 } };
+
+               ip_src_prefix.u.prefix4 = iph->ip_src;
+               if (prefix_list_apply(oi->nbr_filter,
+                                     (struct prefix *)&(ip_src_prefix)) !=
+                   PREFIX_PERMIT)
+                       return OSPF_READ_CONTINUE;
+       }
+
        /*
         * ospf_verify_header() relies on a valid "oi" and thus can be called
         * only after the passive/backbone/other checks below are passed.
index b366b3ca3084eb845f2a3162e77ca853cd3b660b..ea483f72f5acbbc3de34a0c1ac5e450e715883a3 100644 (file)
@@ -4084,6 +4084,31 @@ static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf,
                if (use_json)
                        json_object_object_addf(json_ois, json_oi, "%pI4",
                                                &oi->address->u.prefix4);
+
+               if (oi->nbr_filter) {
+                       if (use_json) {
+                               json_object_string_add(json_interface_sub,
+                                                      "nbrFilterPrefixList",
+                                                      prefix_list_name(
+                                                              oi->nbr_filter));
+                               json_object_string_add(json_oi,
+                                                      "nbrFilterPrefixList",
+                                                      prefix_list_name(
+                                                              oi->nbr_filter));
+                       } else
+                               vty_out(vty,
+                                       "  Neighbor filter prefix-list: %s\n",
+                                       prefix_list_name(oi->nbr_filter));
+               } else {
+                       if (use_json) {
+                               json_object_string_add(json_interface_sub,
+                                                      "nbrFilterPrefixList",
+                                                      "N/A");
+                               json_object_string_add(json_oi,
+                                                      "nbrFilterPrefixList",
+                                                      "N/A");
+                       }
+               }
        }
 }
 
@@ -9936,6 +9961,58 @@ DEFPY(ip_ospf_prefix_suppression, ip_ospf_prefix_suppression_addr_cmd,
        return CMD_SUCCESS;
 }
 
+DEFPY(ip_ospf_neighbor_filter, ip_ospf_neighbor_filter_addr_cmd,
+      "[no] ip ospf neighbor-filter ![PREFIXLIST4_NAME]$prefix_list [A.B.C.D]$ip_addr", NO_STR
+      "IP Information\n"
+      "OSPF interface commands\n"
+      "Filter OSPF neighbor packets\n"
+      "Prefix-List used for filtering\n"
+      "Address of interface\n")
+{
+       VTY_DECLVAR_CONTEXT(interface, ifp);
+       struct ospf_if_params *params;
+       struct prefix_list *nbr_filter = NULL;
+       struct route_node *rn;
+
+       params = IF_DEF_PARAMS(ifp);
+
+       if (ip_addr.s_addr != INADDR_ANY) {
+               params = ospf_get_if_params(ifp, ip_addr);
+               ospf_if_update_params(ifp, ip_addr);
+       }
+
+       if (params->nbr_filter_name)
+               XFREE(MTYPE_OSPF_IF_PARAMS, params->nbr_filter_name);
+
+       if (no) {
+               UNSET_IF_PARAM(params, nbr_filter_name);
+               params->nbr_filter_name = NULL;
+       } else {
+               SET_IF_PARAM(params, nbr_filter_name);
+               params->nbr_filter_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS,
+                                                 prefix_list);
+               nbr_filter = prefix_list_lookup(AFI_IP, params->nbr_filter_name);
+       }
+
+       /*
+        * Determine if there is a change in neighbor filter prefix-list for the
+        * interface.
+        */
+       for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) {
+               struct ospf_interface *oi = rn->info;
+
+               if (oi &&
+                   (ip_addr.s_addr == INADDR_ANY ||
+                    IPV4_ADDR_SAME(&oi->address->u.prefix4, &ip_addr)) &&
+                   oi->nbr_filter != nbr_filter) {
+                       oi->nbr_filter = nbr_filter;
+                       if (oi->nbr_filter)
+                               ospf_intf_neighbor_filter_apply(oi);
+               }
+       }
+       return CMD_SUCCESS;
+}
+
 DEFUN (ospf_max_metric_router_lsa_admin,
        ospf_max_metric_router_lsa_admin_cmd,
        "max-metric router-lsa administrative",
@@ -12359,6 +12436,15 @@ static int config_write_interface_one(struct vty *vty, struct vrf *vrf)
                                vty_out(vty, "\n");
                        }
 
+                       /* neighbor-filter print. */
+                       if (OSPF_IF_PARAM_CONFIGURED(params, nbr_filter_name)) {
+                               vty_out(vty, " ip ospf neighbor-filter %s",
+                                       params->nbr_filter_name);
+                               if (params != IF_DEF_PARAMS(ifp) && rn)
+                                       vty_out(vty, " %pI4", &rn->p.u.prefix4);
+                               vty_out(vty, "\n");
+                       }
+
                        while (1) {
                                if (rn == NULL)
                                        rn = route_top(IF_OIFS_PARAMS(ifp));
@@ -13175,6 +13261,9 @@ static void ospf_vty_if_init(void)
        /* "ip ospf prefix-suppression" commands. */
        install_element(INTERFACE_NODE, &ip_ospf_prefix_suppression_addr_cmd);
 
+       /* "ip ospf neighbor-filter" commands. */
+       install_element(INTERFACE_NODE, &ip_ospf_neighbor_filter_addr_cmd);
+
        /* These commands are compatibitliy for previous version. */
        install_element(INTERFACE_NODE, &ospf_authentication_key_cmd);
        install_element(INTERFACE_NODE, &ospf_message_digest_key_cmd);
index bb6cc3a89c0559717e603787461cf8f5118e80e4..2c518f2c9eee74542935d0f40c9caf56d2058749 100644 (file)
@@ -1769,6 +1769,7 @@ static void ospf_prefix_list_update(struct prefix_list *plist)
        int type;
        int abr_inv = 0;
        struct ospf_area *area;
+       struct ospf_interface *oi;
        struct listnode *node, *n1;
 
        /* If OSPF instatnce does not exist, return right now. */
@@ -1824,6 +1825,19 @@ static void ospf_prefix_list_update(struct prefix_list *plist)
                        }
                }
 
+               /* Update interface neighbor-filter lists. */
+               for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) {
+                       if (OSPF_IF_PARAM(oi, nbr_filter_name) &&
+                           strcmp(OSPF_IF_PARAM(oi, nbr_filter_name),
+                                  prefix_list_name(plist)) == 0) {
+                               oi->nbr_filter = prefix_list_lookup(
+                                       AFI_IP,
+                                       OSPF_IF_PARAM(oi, nbr_filter_name));
+                               if (oi->nbr_filter)
+                                       ospf_intf_neighbor_filter_apply(oi);
+                       }
+               }
+
                /* Schedule ABR task. */
                if (IS_OSPF_ABR(ospf) && abr_inv)
                        ospf_schedule_abr_task(ospf);
index 352180b7ce0059dc740401b4b276a78326f3101e..1f0f87959af190bb31b43f7fa94fa7accea65503 100644 (file)
@@ -118,17 +118,21 @@ def teardown_module(mod):
     tgen.stop_topology()
 
 
-def verify_p2mp_interface(tgen):
+def verify_p2mp_interface(tgen, router, nbr_cnt, nbr_adj_cnt, nbr_filter):
     "Verify the P2MP Configuration and interface settings"
 
-    r1 = tgen.gears["r1"]
+    topo_router = tgen.gears[router]
 
     step("Test running configuration for P2MP configuration")
     rc = 0
-    rc, _, _ = tgen.net["r1"].cmd_status(
+    rc, _, _ = tgen.net[router].cmd_status(
         "show running ospfd | grep 'ip ospf network point-to-multipoint'", warn=False
     )
-    assertmsg = "'ip ospf network point-to-multipoint' applied, but not present in r1 configuration"
+    assertmsg = (
+        "'ip ospf network point-to-multipoint' applied, but not present in "
+        + router
+        + "configuration"
+    )
     assert rc, assertmsg
 
     step("Test OSPF interface for P2MP settings")
@@ -145,11 +149,11 @@ def verify_p2mp_interface(tgen):
                         "networkType": "POINTOMULTIPOINT",
                         "cost": 10,
                         "state": "Point-To-Point",
-                        "nbrCount": 3,
-                        "nbrAdjacentCount": 3,
+                        "nbrCount": nbr_cnt,
+                        "nbrAdjacentCount": nbr_adj_cnt,
                         "prefixSuppression": False,
                         "p2mpDelayReflood": False,
-                        "p2mpNonBroadcast": False,
+                        "nbrFilterPrefixList": nbr_filter,
                     }
                 },
                 "ipAddress": "10.1.0.1",
@@ -161,16 +165,19 @@ def verify_p2mp_interface(tgen):
                 "cost": 10,
                 "state": "Point-To-Point",
                 "opaqueCapable": True,
-                "nbrCount": 3,
-                "nbrAdjacentCount": 3,
+                "nbrCount": nbr_cnt,
+                "nbrAdjacentCount": nbr_adj_cnt,
                 "prefixSuppression": False,
                 "p2mpDelayReflood": False,
-                "p2mpNonBroadcast": False,
+                "nbrFilterPrefixList": nbr_filter,
             }
         }
     }
     test_func = partial(
-        topotest.router_json_cmp, r1, "show ip ospf interface r1-eth0 json", input_dict
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip ospf interface r1-eth0 json",
+        input_dict,
     )
     _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
     assertmsg = "P2MP Interface Mismatch on router r1"
@@ -251,6 +258,23 @@ def verify_p2mp_neighbor(tgen, router, neighbor, state, intf_addr, interface):
     assert result is None, assertmsg
 
 
+def verify_p2mp_neighbor_missing(tgen, router, neighbor):
+    topo_router = tgen.gears[router]
+
+    step("Verify neighbor " + neighbor + " missing")
+    input_dict = {"default": {}}
+    test_func = partial(
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip ospf neighbor " + neighbor + " json",
+        input_dict,
+        True,  # Require exact match for missing neighbor
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = "P2MP Neighbor " + neighbor + " not missing"
+    assert result is None, assertmsg
+
+
 def verify_p2mp_route(tgen, router, prefix, prefix_len, nexthop, interface):
     topo_router = tgen.gears[router]
 
@@ -288,7 +312,7 @@ def test_p2mp_broadcast_interface():
         pytest.skip("Skipped because of router(s) failure")
 
     step("Verify router r1 interface r1-eth0 p2mp configuration")
-    verify_p2mp_interface(tgen)
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
 
     step("Verify router r1 p2mp interface r1-eth0 neighbors")
     verify_p2mp_neighbor(
@@ -313,7 +337,7 @@ def test_p2mp_broadcast_interface():
 
     step("Verify router r1 interface r1-eth0 p2mp configuration application")
     r1.vtysh_cmd("conf t\ninterface r1-eth0\nip ospf network point-to-multipoint")
-    verify_p2mp_interface(tgen)
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
 
     step("Verify restablishment of r1-eth0 p2mp neighbors")
     verify_p2mp_neighbor(
@@ -332,6 +356,108 @@ def test_p2mp_broadcast_interface():
     verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.4", "r1-eth0")
 
 
+def test_p2mp_broadcast_neighbor_filter():
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip("Skipped because of router(s) failure")
+
+    step("Verify router r1 interface r1-eth0 p2mp configuration")
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
+
+    step("Verify router r1 p2mp interface r1-eth0 neighbors")
+    verify_p2mp_neighbor(
+        tgen, "r1", "2.2.2.2", "Full/DROther", "10.1.0.2", "r1-eth0:10.1.0.1"
+    )
+    verify_p2mp_neighbor(
+        tgen, "r1", "3.3.3.3", "Full/DROther", "10.1.0.3", "r1-eth0:10.1.0.1"
+    )
+    verify_p2mp_neighbor(
+        tgen, "r1", "4.4.4.4", "Full/DROther", "10.1.0.4", "r1-eth0:10.1.0.1"
+    )
+
+    step("Add OSPF interface neighbor-filter to r1")
+    r1 = tgen.gears["r1"]
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nip ospf neighbor-filter nbr-filter")
+
+    step("Verify the R1 configuration of 'ip ospf neighbor-filter nbr-filter'")
+    neighbor_filter_cfg = (
+        tgen.net["r1"]
+        .cmd(
+            'vtysh -c "show running ospfd" | grep "^ ip ospf neighbor-filter nbr-filter"'
+        )
+        .rstrip()
+    )
+    assertmsg = (
+        "'ip ospf neighbor-filter nbr-filter' applied, but not present in configuration"
+    )
+    assert neighbor_filter_cfg == " ip ospf neighbor-filter nbr-filter", assertmsg
+
+    step("Verify non-existent neighbor-filter is not applied to r1 interfaces")
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
+
+    step("Add nbr-filter prefix-list configuration to r1")
+    r1.vtysh_cmd("conf t\nip prefix-list nbr-filter seq 200 permit any")
+
+    step(
+        "Verify neighbor-filter is now applied to r1 interface and neighbors still adjacent"
+    )
+    verify_p2mp_interface(tgen, "r1", 3, 3, "nbr-filter")
+
+    step("Add nbr-filter prefix-list configuration to block r4")
+    r1.vtysh_cmd("conf t\nip prefix-list nbr-filter seq 10 deny 10.1.0.4/32")
+
+    step(
+        "Verify neighbor-filter is now applied to r1 interface and r4 is no longer adjacent"
+    )
+    verify_p2mp_interface(tgen, "r1", 2, 2, "nbr-filter")
+    verify_p2mp_neighbor_missing(tgen, "r1", "4.4.4.4")
+
+    step("Verify route to r4 subnet is now through r2")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.2", "r1-eth0")
+
+    step("Add nbr-filter prefix-list configuration to block r2")
+    r1.vtysh_cmd("conf t\nip prefix-list nbr-filter seq 20 deny 10.1.0.2/32")
+
+    step(
+        "Verify neighbor-filter is now applied to r1 interface and r2 is no longer adjacent"
+    )
+    verify_p2mp_interface(tgen, "r1", 1, 1, "nbr-filter")
+    verify_p2mp_neighbor_missing(tgen, "r1", "2.2.2.2")
+
+    step("Verify route to r4 and r2 subnet are now through r3")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.3", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.3", "r1-eth0")
+
+    step("Remove neighbor filter configuration and verify")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nno ip ospf neighbor-filter")
+    rc, _, _ = tgen.net["r1"].cmd_status(
+        "show running ospfd | grep -q 'ip ospf neighbor-filter'", warn=False
+    )
+    assertmsg = "'ip ospf neighbor' not applied, but present in R1 configuration"
+    assert rc, assertmsg
+
+    step("Verify interface neighbor-filter is removed and neighbors present")
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
+
+    step("Add neighbor filter configuration and verify neighbors are filtered")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nip ospf neighbor-filter nbr-filter")
+    verify_p2mp_interface(tgen, "r1", 1, 1, "nbr-filter")
+    verify_p2mp_neighbor_missing(tgen, "r1", "2.2.2.2")
+    verify_p2mp_neighbor_missing(tgen, "r1", "4.4.4.4")
+
+    step("Remove nbr-filter prefix-list configuration to block r2 and verify neighbor")
+    r1.vtysh_cmd("conf t\nno ip prefix-list nbr-filter seq 20")
+    verify_p2mp_interface(tgen, "r1", 2, 2, "nbr-filter")
+    verify_p2mp_neighbor(
+        tgen, "r1", "2.2.2.2", "Full/DROther", "10.1.0.2", "r1-eth0:10.1.0.1"
+    )
+
+    step("Delete nbr-filter prefix-list and verify neighbors are present")
+    r1.vtysh_cmd("conf t\nno ip prefix-list nbr-filter")
+    verify_p2mp_interface(tgen, "r1", 3, 3, "N/A")
+
+
 def test_memory_leak():
     "Run the memory leak test and report results."
     tgen = get_topogen()