]> git.puffer.fish Git - mirror/frr.git/commitdiff
ospfd: Implement non-broadcast support for point-to-multipoint networks 15660/head
authorAcee Lindem <acee@lindem.com>
Tue, 2 Apr 2024 15:55:49 +0000 (15:55 +0000)
committerAcee Lindem <acee@lindem.com>
Tue, 2 Apr 2024 21:34:29 +0000 (21:34 +0000)
This extends non-broadcast support to point-to-multipoint networks.
Neighbors will be explicitly configured and polled in lieu of multicast
dicovery. Toptotests and documentation updates are included.

Additionally, the ospf neighbor commands have been greatly simplified taking
advantage of DEFPY() capabilities.

The AllOSPFRouters (224.0.0.5) is still joined for non-broadcast networks
since it is joined for NBMA networks. It seems this could be removed but
it should done be in a separate commit.

Signed-off-by: Acee Lindem <acee@lindem.com>
23 files changed:
doc/user/ospf6d.rst
doc/user/ospf_fundamentals.rst
doc/user/ospfd.rst
lib/libospf.h
ospfd/ospf_flood.c
ospfd/ospf_interface.c
ospfd/ospf_interface.h
ospfd/ospf_ism.c
ospfd/ospf_neighbor.c
ospfd/ospf_nsm.c
ospfd/ospf_packet.c
ospfd/ospf_vty.c
ospfd/ospfd.c
tests/topotests/ospf_p2mp/r1/frr-p2mp-non-broadcast.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r1/frr-p2mp.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r2/frr-p2mp-non-broadcast.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r2/frr-p2mp.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r3/frr-p2mp-non-broadcast.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r3/frr-p2mp.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r4/frr-p2mp-non-broadcast.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/r4/frr-p2mp.conf [new file with mode: 0644]
tests/topotests/ospf_p2mp/test_ospf_p2mp_broadcast.py [new file with mode: 0644]
tests/topotests/ospf_p2mp/test_ospf_p2mp_non_broadcast.py [new file with mode: 0644]

index ad5861051d984796cf422c230c7a435143b1a432..ea41ba53b3706bbbae081fbc7507149083d9c0b2 100644 (file)
@@ -494,11 +494,11 @@ Graceful Restart
 
 
    Configure Graceful Restart (RFC 5187) helper support.
-   By default, helper support is disabled for all neighbours.
+   By default, helper support is disabled for all neighbors.
    This config enables/disables helper support on this router
-   for all neighbours.
+   for all neighbors.
    To enable/disable helper support for a specific
-   neighbour, the router-id (A.B.C.D) has to be specified.
+   neighbor, the router-id (A.B.C.D) has to be specified.
 
 .. clicmd:: graceful-restart helper strict-lsa-checking
 
index c566059121791b8de9298d05b713faae2f3b6fc3..3032d2771ee8df08f93deabd41d5ff8321dec388 100644 (file)
@@ -12,7 +12,7 @@ OSPF Fundamentals
 :term:`distance-vector` protocols, such as :abbr:`RIP` or :abbr:`BGP`, where
 routers describe available `paths` (i.e. routes) to each other, in
 :term:`link-state` protocols routers instead describe the state of their links
-to their immediate neighbouring routers.
+to their immediate neighboring routers.
 
 .. index::
    single: Link State Announcement
@@ -127,7 +127,7 @@ LSA Flooding
 """"""""""""
 
 OSPF defines several related mechanisms, used to manage synchronisation of
-:abbr:`LSDB` s between neighbours as neighbours form adjacencies and the
+:abbr:`LSDB` s between neighbors as neighbors form adjacencies and the
 propagation, or `flooding` of new or updated :abbr:`LSA` s.
 
 
@@ -259,7 +259,7 @@ called `intra-area routes`.
      LSA is originated for such a link.
 
      Stub
-        A link with no adjacent neighbours, or a host route.
+        A link with no adjacent neighbors, or a host route.
 
   - Link ID and Data
 
@@ -339,8 +339,8 @@ The example below shows two :abbr:`LSA` s, both originated by the same router
 of different LSA types.
 
 The first LSA being the router LSA describing 192.168.0.49's links: 2 links
-to multi-access networks with fully-adjacent neighbours (i.e. Transit
-links) and 1 being a Stub link (no adjacent neighbours).
+to multi-access networks with fully-adjacent neighbors (i.e. Transit
+links) and 1 being a Stub link (no adjacent neighbors).
 
 The second LSA being a Network LSA, for which 192.168.0.49 is the
 :abbr:`DR`, listing the Router IDs of 4 routers on that network which
index 3bc4487f64a992b820672818219605c94850a99e..47f8fad17b620ea52ae67cdf9cb2606c23f3f0e7 100644 (file)
@@ -239,6 +239,17 @@ To start OSPF process you have to specify the OSPF router.
    This configuration setting MUST be consistent across all routers within the
    OSPF domain.
 
+.. clicmd:: neighbor A.B.C.D [poll-interval (1-65535)] [priority (0-255)]
+
+
+   Configures OSPF neighbors for non-broadcast multi-access (NBMA) networks
+   and point-to-multipoint non-broadcast networks. The `poll-interval`
+   specifies the rate for sending hello packets to neighbors that are not
+   active. When the configured neighbor is discovered, hello packets will be
+   sent at the rate of the hello-interval. The default `poll-interval` is 60
+   seconds. The `priority` is used to for the Designated Router (DR) election
+   on non-broadcast multi-access networks.
+
 .. clicmd:: network A.B.C.D/M area A.B.C.D
 
 .. clicmd:: network A.B.C.D/M area (0-4294967295)
@@ -580,7 +591,7 @@ Interfaces
    Note that OSPF MD5 authentication requires that time never go backwards
    (correct time is NOT important, only that it never goes backwards), even
    across resets, if ospfd is to be able to promptly reestablish adjacencies
-   with its neighbours after restarts/reboots. The host should have system time
+   with its neighbors after restarts/reboots. The host should have system time
    be set at boot from an external or non-volatile source (e.g. battery backed
    clock, NTP, etc.) or else the system clock should be periodically saved to
    non-volatile storage and restored at boot if MD5 authentication is to be
@@ -612,7 +623,7 @@ Interfaces
    Note that OSPF HMAC cryptographic authentication requires that time never go backwards
    (correct time is NOT important, only that it never goes backwards), even
    across resets, if ospfd is to be able to promptly reestablish adjacencies
-   with its neighbours after restarts/reboots. The host should have system time
+   with its neighbors after restarts/reboots. The host should have system time
    be set at boot from an external or non-volatile source (e.g. battery backed
    clock, NTP, etc.) or else the system clock should be periodically saved to
    non-volatile storage and restored at boot if HMAC cryptographic authentication is to be
@@ -679,7 +690,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 [delay-reflood]|point-to-point [dmvpn])
+.. clicmd:: ip ospf network (broadcast|non-broadcast|point-to-multipoint [delay-reflood|non-broadcast]|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
@@ -691,6 +702,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 `non-broadcast`
+   is specified, the network doesn't support broadcast or multicast delivery
+   and neighbors cannot be discovered from OSPF hello received from the
+   OSPFAllRouters (224.0.0.5). Rather, they must be explicitly configured
+   using the :clicmd:`neighbor A.B.C.D` configuration command as they are
+   on non-broadcast networks.
+
    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.
@@ -838,11 +856,11 @@ Graceful Restart
 
 
    Configure Graceful Restart (RFC 3623) helper support.
-   By default, helper support is disabled for all neighbours.
+   By default, helper support is disabled for all neighbors.
    This config enables/disables helper support on this router
-   for all neighbours.
+   for all neighbors.
    To enable/disable helper support for a specific
-   neighbour, the router-id (A.B.C.D) has to be specified.
+   neighbor, the router-id (A.B.C.D) has to be specified.
 
 .. clicmd:: graceful-restart helper strict-lsa-checking
 
@@ -1082,7 +1100,7 @@ Router Information
    respectively the PCE IP address, Autonomous System (AS) numbers of
    controlled domains, neighbor ASs, flag and scope. For flag and scope, please
    refer to :rfc`5088` for the BITPATTERN recognition. Multiple 'pce neighbor'
-   command could be specified in order to specify all PCE neighbours.
+   command could be specified in order to specify all PCE neighbors.
 
 .. clicmd:: show ip ospf router-info
 
index 45e7fb187069c4c9e5bf5e2a0aa2a487c7f16445..0ac490a00ecea03b913036fe1672820feb5a7794 100644 (file)
@@ -69,6 +69,7 @@ extern "C" {
 #define OSPF_MTU_IGNORE_DEFAULT             0
 #define OSPF_FAST_HELLO_DEFAULT             0
 #define OSPF_P2MP_DELAY_REFLOOD_DEFAULT            false
+#define OSPF_P2MP_NON_BROADCAST_DEFAULT            false
 #define OSPF_OPAQUE_CAPABLE_DEFAULT true
 #define OSPF_PREFIX_SUPPRESSION_DEFAULT            false
 #define OSPF_AREA_BACKBONE              0x00000000      /* 0.0.0.0 */
index 95a593ad4dc66cd298706ecf4a18e4fcd808ee33..e15871ac81cd8bc805cbb9f89c4751c710984df7 100644 (file)
@@ -765,8 +765,9 @@ int ospf_flood_through_interface(struct ospf_interface *oi,
            packets must be sent, as unicasts, to each adjacent neighbor
            (i.e., those in state Exchange or greater).  The destination
            IP addresses for these packets are the neighbors' IP
-           addresses.   */
-       if (oi->type == OSPF_IFTYPE_NBMA) {
+           addresses. This behavior is extended to P2MP networks which
+           don't support broadcast. */
+       if (OSPF_IF_NON_BROADCAST(oi)) {
                struct ospf_neighbor *nbr;
 
                for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) {
index 17426d261585519d7a6ca0a85fbfb701083b646e..319db1efe23f02f9358fc36bf16cb4f2c01f22a4 100644 (file)
@@ -147,17 +147,11 @@ void ospf_if_reset(struct interface *ifp)
        }
 }
 
-void ospf_if_reset_variables(struct ospf_interface *oi)
+static void ospf_if_default_variables(struct ospf_interface *oi)
 {
        /* Set default values. */
-       /* don't clear this flag.  oi->flag = OSPF_IF_DISABLE; */
 
-       if (oi->vl_data)
-               oi->type = OSPF_IFTYPE_VIRTUALLINK;
-       else
-               /* preserve network-type */
-               if (oi->type != OSPF_IFTYPE_NBMA)
-               oi->type = OSPF_IFTYPE_BROADCAST;
+       oi->type = OSPF_IFTYPE_BROADCAST;
 
        oi->state = ISM_Down;
 
@@ -254,7 +248,7 @@ struct ospf_interface *ospf_if_new(struct ospf *ospf, struct interface *ifp,
        oi->ls_ack_direct.ls_ack = list_new();
 
        /* Set default values. */
-       ospf_if_reset_variables(oi);
+       ospf_if_default_variables(oi);
 
        /* Set pseudo neighbor to Null */
        oi->nbr_self = NULL;
index 08a2b11273e1d7e2310cc078754379acda579433..721ab1a9d7ea945815fa65905b2e1a47502145c3 100644 (file)
@@ -119,6 +119,9 @@ struct ospf_if_params {
        /* point-to-multipoint delayed reflooding configuration */
        bool p2mp_delay_reflood;
 
+       /* point-to-multipoint doesn't support broadcast */
+       bool p2mp_non_broadcast;
+
        /* Opaque LSA capability at interface level (see RFC5250) */
        DECLARE_IF_PARAM(bool, opaque_capable);
 };
@@ -185,6 +188,10 @@ struct ospf_interface {
 
        /* OSPF Network Type. */
        uint8_t type;
+#define OSPF_IF_NON_BROADCAST(O)                                               \
+       (((O)->type == OSPF_IFTYPE_NBMA) ||                                    \
+        ((((O)->type == OSPF_IFTYPE_POINTOMULTIPOINT) &&                      \
+          (O)->p2mp_non_broadcast)))
 
        /* point-to-point DMVPN configuration */
        uint8_t ptp_dmvpn;
@@ -192,6 +199,9 @@ struct ospf_interface {
        /* point-to-multipoint delayed reflooding */
        bool p2mp_delay_reflood;
 
+       /* point-to-multipoint doesn't support broadcast */
+       bool p2mp_non_broadcast;
+
        /* State of Interface State Machine. */
        uint8_t state;
 
@@ -326,7 +336,6 @@ extern void ospf_if_update_params(struct interface *ifp, struct in_addr addr);
 extern int ospf_if_new_hook(struct interface *ifp);
 extern void ospf_if_init(void);
 extern void ospf_if_stream_unset(struct ospf_interface *oi);
-extern void ospf_if_reset_variables(struct ospf_interface *oi);
 extern int ospf_if_is_enable(struct ospf_interface *oi);
 extern int ospf_if_get_output_cost(struct ospf_interface *oi);
 extern void ospf_if_recalculate_output_cost(struct interface *ifp);
index 2516fa75db43c377c6e2b0c17d3c1dc6f34992dc..878ab725bd3270e8d6c4bc588bd3138ea90d2ed6 100644 (file)
@@ -367,7 +367,7 @@ static int ism_interface_up(struct ospf_interface *oi)
                /* Otherwise, the state transitions to Waiting. */
                next_state = ISM_Waiting;
 
-       if (oi->type == OSPF_IFTYPE_NBMA)
+       if (OSPF_IF_NON_BROADCAST(oi))
                ospf_nbr_nbma_if_update(oi->ospf, oi);
 
        /*  ospf_ism_event (t); */
index c238f051dfeb754d2963fceface4e9f58aab6927..d47d5816057a070e0500e909970fb7a79e107105 100644 (file)
@@ -431,7 +431,7 @@ static struct ospf_neighbor *ospf_nbr_add(struct ospf_interface *oi,
        memcpy(&nbr->address, p, sizeof(struct prefix));
 
        nbr->nbr_nbma = NULL;
-       if (oi->type == OSPF_IFTYPE_NBMA) {
+       if (OSPF_IF_NON_BROADCAST(oi)) {
                struct ospf_nbr_nbma *nbr_nbma;
                struct listnode *node;
 
@@ -485,7 +485,7 @@ struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *oi,
                route_unlock_node(rn);
                nbr = rn->info;
 
-               if (oi->type == OSPF_IFTYPE_NBMA && nbr->state == NSM_Attempt) {
+               if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->state == NSM_Attempt) {
                        nbr->src = iph->ip_src;
                        memcpy(&nbr->address, p, sizeof(struct prefix));
                }
index 08aa1036861313ac94da269247a1ee7f0b1905e1..c466ddcc6f90c04602daa2423dee985818a19d57 100644 (file)
@@ -166,7 +166,7 @@ static int nsm_hello_received(struct ospf_neighbor *nbr)
        OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer,
                          nbr->v_inactivity);
 
-       if (nbr->oi->type == OSPF_IFTYPE_NBMA && nbr->nbr_nbma)
+       if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->nbr_nbma != NULL)
                EVENT_OFF(nbr->nbr_nbma->t_poll);
 
        /* Send proactive ARP requests */
@@ -377,7 +377,7 @@ static int nsm_kill_nbr(struct ospf_neighbor *nbr)
                return 0;
        }
 
-       if (nbr->oi->type == OSPF_IFTYPE_NBMA && nbr->nbr_nbma != NULL) {
+       if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->nbr_nbma != NULL) {
                struct ospf_nbr_nbma *nbr_nbma = nbr->nbr_nbma;
 
                nbr_nbma->nbr = NULL;
index 861b4e022a8701b03664c902e764e1fee8ea73e5..60479ddcd17e534ca7106ca760d12dd99e400eed 100644 (file)
@@ -3395,17 +3395,19 @@ static void ospf_poll_send(struct ospf_nbr_nbma *nbr_nbma)
        if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE)
                return;
 
-       if (oi->type != OSPF_IFTYPE_NBMA)
-               return;
-
        if (nbr_nbma->nbr != NULL && nbr_nbma->nbr->state != NSM_Down)
                return;
 
-       if (PRIORITY(oi) == 0)
-               return;
+       if (oi->type == OSPF_IFTYPE_NBMA) {
+               if (PRIORITY(oi) == 0)
+                       return;
+
+               if (nbr_nbma->priority == 0 && oi->state != ISM_DR &&
+                   oi->state != ISM_Backup)
+                       return;
 
-       if (nbr_nbma->priority == 0 && oi->state != ISM_DR
-           && oi->state != ISM_Backup)
+       } else if (oi->type != OSPF_IFTYPE_POINTOMULTIPOINT ||
+                  !oi->p2mp_non_broadcast)
                return;
 
        ospf_hello_send_sub(oi, nbr_nbma->addr.s_addr);
@@ -3451,7 +3453,7 @@ void ospf_hello_send(struct ospf_interface *oi)
        if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE)
                return;
 
-       if (oi->type == OSPF_IFTYPE_NBMA) {
+       if (OSPF_IF_NON_BROADCAST(oi)) {
                struct ospf_neighbor *nbr;
                struct route_node *rn;
 
@@ -3467,31 +3469,44 @@ void ospf_hello_send(struct ospf_interface *oi)
                                continue;
 
                        /*
-                        * RFC 2328  Section 9.5.1
-                        * If the router is not eligible to become Designated
-                        * Router, it must periodically send Hello Packets to
-                        * both the Designated Router and the Backup
-                        * Designated Router (if they exist).
+                        * Always send to all neighbors on Point-to-Multipoint
+                        * non-braodcast networks.
                         */
-                       if (PRIORITY(oi) == 0 &&
-                           IPV4_ADDR_CMP(&DR(oi), &nbr->address.u.prefix4) &&
-                           IPV4_ADDR_CMP(&BDR(oi), &nbr->address.u.prefix4))
-                               continue;
+                       if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT)
+                               ospf_hello_send_sub(oi, nbr->address.u.prefix4
+                                                               .s_addr);
+                       else {
+                               /*
+                                * RFC 2328  Section 9.5.1
+                                * If the router is not eligible to become Designated
+                                * Router, it must periodically send Hello Packets to
+                                * both the Designated Router and the Backup
+                                * Designated Router (if they exist).
+                                */
+                               if (PRIORITY(oi) == 0 &&
+                                   IPV4_ADDR_CMP(&DR(oi),
+                                                 &nbr->address.u.prefix4) &&
+                                   IPV4_ADDR_CMP(&BDR(oi),
+                                                 &nbr->address.u.prefix4))
+                                       continue;
 
-                       /*
-                        * If the router is eligible to become Designated
-                        * Router, it must periodically send Hello Packets to
-                        * all neighbors that are also eligible. In addition,
-                        * if the router is itself the Designated Router or
-                        * Backup Designated Router, it must also send periodic
-                        * Hello Packets to all other neighbors.
-                        */
-                       if (nbr->priority == 0 && oi->state == ISM_DROther)
-                               continue;
+                               /*
+                                * If the router is eligible to become Designated
+                                * Router, it must periodically send Hello Packets to
+                                * all neighbors that are also eligible. In addition,
+                                * if the router is itself the Designated Router or
+                                * Backup Designated Router, it must also send periodic
+                                * Hello Packets to all other neighbors.
+                                */
+                               if (nbr->priority == 0 &&
+                                   oi->state == ISM_DROther)
+                                       continue;
 
-                       /* if oi->state == Waiting, send
-                        * hello to all neighbors */
-                       ospf_hello_send_sub(oi, nbr->address.u.prefix4.s_addr);
+                               /* if oi->state == Waiting, send
+                                * hello to all neighbors */
+                               ospf_hello_send_sub(oi, nbr->address.u.prefix4
+                                                               .s_addr);
+                       }
                }
        } else {
                /* Decide destination address. */
@@ -3848,11 +3863,10 @@ void ospf_ls_upd_send(struct ospf_neighbor *nbr, struct list *update, int flag,
        else
                p.prefix.s_addr = htonl(OSPF_ALLDROUTERS);
 
-       if (oi->type == OSPF_IFTYPE_NBMA) {
+       if (OSPF_IF_NON_BROADCAST(oi)) {
                if (flag == OSPF_SEND_PACKET_INDIRECT)
-                       flog_warn(
-                               EC_OSPF_PACKET,
-                               "* LS-Update is directly sent on NBMA network.");
+                       flog_warn(EC_OSPF_PACKET,
+                                 "* LS-Update is directly sent on non-broadcast network.");
                if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &p.prefix))
                        flog_warn(EC_OSPF_PACKET,
                                  "* LS-Update is sent to myself.");
@@ -3910,7 +3924,8 @@ static void ospf_ls_ack_send_list(struct ospf_interface *oi, struct list *ack,
 
        /* Decide destination address. */
        if (oi->type == OSPF_IFTYPE_POINTOPOINT ||
-           oi->type == OSPF_IFTYPE_POINTOMULTIPOINT)
+           (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT &&
+            !oi->p2mp_non_broadcast))
                op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS);
        else
                op->dst.s_addr = dst.s_addr;
@@ -3962,7 +3977,7 @@ void ospf_ls_ack_send_delayed(struct ospf_interface *oi)
              networks, delayed Link State Acknowledgment packets must be
              unicast   separately over each adjacency (i.e., neighbor whose
              state is >= Exchange).  */
-       if (oi->type == OSPF_IFTYPE_NBMA) {
+       if (OSPF_IF_NON_BROADCAST(oi)) {
                struct ospf_neighbor *nbr;
                struct route_node *rn;
 
index 93dd12ce4979a5034e1aad4c13508ef29212048d..bf131ff225cbe5c10eb20c7aaff89e10748a14a6 100644 (file)
@@ -2406,130 +2406,30 @@ DEFUN (no_ospf_timers_lsa_min_arrival,
        return CMD_SUCCESS;
 }
 
-DEFUN (ospf_neighbor,
-       ospf_neighbor_cmd,
-       "neighbor A.B.C.D [priority (0-255) [poll-interval (1-65535)]]",
-       NEIGHBOR_STR
-       "Neighbor IP address\n"
-       "Neighbor Priority\n"
-       "Priority\n"
-       "Dead Neighbor Polling interval\n"
-       "Seconds\n")
-{
-       VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
-       int idx_ipv4 = 1;
-       int idx_pri = 3;
-       int idx_poll = 5;
-       struct in_addr nbr_addr;
-       unsigned int priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT;
-       unsigned int interval = OSPF_POLL_INTERVAL_DEFAULT;
-
-       if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) {
-               vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       if (argc > 2)
-               priority = strtoul(argv[idx_pri]->arg, NULL, 10);
-
-       if (argc > 4)
-               interval = strtoul(argv[idx_poll]->arg, NULL, 10);
-
-       ospf_nbr_nbma_set(ospf, nbr_addr);
-
-       if (argc > 2)
-               ospf_nbr_nbma_priority_set(ospf, nbr_addr, priority);
-
-       if (argc > 4)
-               ospf_nbr_nbma_poll_interval_set(ospf, nbr_addr, interval);
-
-       return CMD_SUCCESS;
-}
-
-DEFUN (ospf_neighbor_poll_interval,
-       ospf_neighbor_poll_interval_cmd,
-       "neighbor A.B.C.D poll-interval (1-65535) [priority (0-255)]",
-       NEIGHBOR_STR
-       "Neighbor IP address\n"
-       "Dead Neighbor Polling interval\n"
-       "Seconds\n"
-       "OSPF priority of non-broadcast neighbor\n"
-       "Priority\n")
-{
-       VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
-       int idx_ipv4 = 1;
-       int idx_poll = 3;
-       int idx_pri = 5;
-       struct in_addr nbr_addr;
-       unsigned int priority;
-       unsigned int interval;
-
-       if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) {
-               vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       interval = strtoul(argv[idx_poll]->arg, NULL, 10);
-
-       priority = argc > 4 ? strtoul(argv[idx_pri]->arg, NULL, 10)
-                           : OSPF_NEIGHBOR_PRIORITY_DEFAULT;
-
-       ospf_nbr_nbma_set(ospf, nbr_addr);
-       ospf_nbr_nbma_poll_interval_set(ospf, nbr_addr, interval);
-
-       if (argc > 4)
-               ospf_nbr_nbma_priority_set(ospf, nbr_addr, priority);
-
-       return CMD_SUCCESS;
-}
-
-DEFUN (no_ospf_neighbor,
-       no_ospf_neighbor_cmd,
-       "no neighbor A.B.C.D [priority (0-255) [poll-interval (1-65525)]]",
-       NO_STR
-       NEIGHBOR_STR
-       "Neighbor IP address\n"
-       "Neighbor Priority\n"
-       "Priority\n"
-       "Dead Neighbor Polling interval\n"
-       "Seconds\n")
+DEFPY(ospf_neighbor, ospf_neighbor_cmd,
+      "[no] neighbor A.B.C.D$nbr_address [{priority (0-255)$priority | poll-interval (1-65535)$interval}]",
+      NO_STR
+      NEIGHBOR_STR
+      "Neighbor IP address\n"
+      "Neighbor Priority\n"
+      "Priority\n"
+      "Dead Neighbor Polling interval\n"
+      "Seconds\n")
 {
        VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
-       int idx_ipv4 = 2;
-       struct in_addr nbr_addr;
-
-       if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) {
-               vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       (void)ospf_nbr_nbma_unset(ospf, nbr_addr);
 
-       return CMD_SUCCESS;
-}
-
-DEFUN (no_ospf_neighbor_poll,
-       no_ospf_neighbor_poll_cmd,
-       "no neighbor A.B.C.D poll-interval (1-65535) [priority (0-255)]",
-       NO_STR
-       NEIGHBOR_STR
-       "Neighbor IP address\n"
-       "Dead Neighbor Polling interval\n"
-       "Seconds\n"
-       "Neighbor Priority\n"
-       "Priority\n")
-{
-       VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
-       int idx_ipv4 = 2;
-       struct in_addr nbr_addr;
+       if (no)
+               ospf_nbr_nbma_unset(ospf, nbr_address);
+       else {
+               ospf_nbr_nbma_set(ospf, nbr_address);
+               if (priority_str)
+                       ospf_nbr_nbma_priority_set(ospf, nbr_address, priority);
 
-       if (!inet_aton(argv[idx_ipv4]->arg, &nbr_addr)) {
-               vty_out(vty, "Please specify Neighbor ID by A.B.C.D\n");
-               return CMD_WARNING_CONFIG_FAILED;
+               if (interval_str)
+                       ospf_nbr_nbma_poll_interval_set(ospf, nbr_address,
+                                                       interval);
        }
 
-       (void)ospf_nbr_nbma_unset(ospf, nbr_addr);
-
        return CMD_SUCCESS;
 }
 
@@ -4148,6 +4048,8 @@ static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf,
                ospf_interface_auth_show(vty, oi, json_interface_sub, use_json);
 
                ospf_interface_auth_show(vty, oi, json_oi, use_json);
+
+               /* Point-to-Multipoint Interface options. */
                if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) {
                        if (use_json) {
                                json_object_boolean_add(json_interface_sub,
@@ -4162,6 +4064,19 @@ static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf,
                                        "  %sDelay reflooding LSAs received on P2MP interface\n",
                                        oi->p2mp_delay_reflood ? "" : "Don't ");
                        }
+                       if (use_json) {
+                               json_object_boolean_add(json_interface_sub,
+                                                       "p2mpNonBroadcast",
+                                                       oi->p2mp_non_broadcast);
+
+                               json_object_boolean_add(json_oi,
+                                                       "p2mpNonBroadcast",
+                                                       oi->p2mp_non_broadcast);
+                       } else {
+                               vty_out(vty,
+                                       "  P2MP interface does %ssupport broadcast\n",
+                                       oi->p2mp_non_broadcast ? "not " : "");
+                       }
                }
 
                /* Add ospf_interface object to main json blob using SIP as key
@@ -5970,7 +5885,7 @@ static int show_ip_ospf_neighbor_detail_all_common(struct vty *vty,
                        prev_nbr = nbr;
                }
 
-               if (oi->type != OSPF_IFTYPE_NBMA)
+               if (!OSPF_IF_NON_BROADCAST(oi))
                        continue;
 
                struct listnode *nd;
@@ -8548,7 +8463,7 @@ DEFUN_HIDDEN (no_ospf_hello_interval,
 DEFUN(ip_ospf_network, ip_ospf_network_cmd,
       "ip ospf network <broadcast|"
       "non-broadcast|"
-      "point-to-multipoint [delay-reflood]|"
+      "point-to-multipoint [delay-reflood|non-broadcast]|"
       "point-to-point [dmvpn]>",
       "IP Information\n"
       "OSPF interface commands\n"
@@ -8557,6 +8472,7 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd,
       "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-multipoint network doesn't support broadcast\n"
       "Specify OSPF point-to-point network\n"
       "Specify OSPF point-to-point DMVPN network\n")
 {
@@ -8565,6 +8481,7 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd,
        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;
+       uint8_t old_p2mp_non_broadcast = IF_DEF_PARAMS(ifp)->p2mp_non_broadcast;
        struct route_node *rn;
 
        if (old_type == OSPF_IFTYPE_LOOPBACK) {
@@ -8576,16 +8493,19 @@ 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_DEF_PARAMS(ifp)->p2mp_non_broadcast = OSPF_P2MP_NON_BROADCAST_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)) {
                IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOMULTIPOINT;
                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 (argv_find(argv, argc, "non-broadcast", &idx))
+                       IF_DEF_PARAMS(ifp)->p2mp_non_broadcast = true;
+       } 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-point", &idx)) {
                IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOPOINT;
                if (argv_find(argv, argc, "dmvpn", &idx))
                        IF_DEF_PARAMS(ifp)->ptp_dmvpn = 1;
@@ -8593,7 +8513,8 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd,
 
        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)
+           IF_DEF_PARAMS(ifp)->p2mp_delay_reflood == old_p2mp_delay_reflood &&
+           IF_DEF_PARAMS(ifp)->p2mp_non_broadcast == old_p2mp_non_broadcast)
                return CMD_SUCCESS;
 
        SET_IF_PARAM(IF_DEF_PARAMS(ifp), type);
@@ -8607,13 +8528,16 @@ DEFUN(ip_ospf_network, ip_ospf_network_cmd,
                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;
+               oi->p2mp_non_broadcast = IF_DEF_PARAMS(ifp)->p2mp_non_broadcast;
 
                /*
                 * 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_DEF_PARAMS(ifp)->ptp_dmvpn != old_ptp_dmvpn ||
+                   IF_DEF_PARAMS(ifp)->p2mp_non_broadcast !=
+                           old_p2mp_non_broadcast) {
                        if (oi->state > ISM_Down) {
                                OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown);
                                OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp);
@@ -8657,6 +8581,7 @@ DEFUN (no_ip_ospf_network,
        IF_DEF_PARAMS(ifp)->ptp_dmvpn = 0;
        IF_DEF_PARAMS(ifp)->p2mp_delay_reflood =
                OSPF_P2MP_DELAY_REFLOOD_DEFAULT;
+       IF_DEF_PARAMS(ifp)->p2mp_non_broadcast = OSPF_P2MP_NON_BROADCAST_DEFAULT;
 
        if (IF_DEF_PARAMS(ifp)->type == old_type)
                return CMD_SUCCESS;
@@ -12225,6 +12150,10 @@ static int config_write_interface_one(struct vty *vty, struct vrf *vrf)
                                                    OSPF_IFTYPE_POINTOMULTIPOINT &&
                                            params->p2mp_delay_reflood)
                                                vty_out(vty, " delay-reflood");
+                                       if (params->type ==
+                                                   OSPF_IFTYPE_POINTOMULTIPOINT &&
+                                           params->p2mp_non_broadcast)
+                                               vty_out(vty, " non-broadcast");
                                        if (params != IF_DEF_PARAMS(ifp) && rn)
                                                vty_out(vty, " %pI4",
                                                        &rn->p.u.prefix4);
@@ -13753,11 +13682,8 @@ void ospf_vty_init(void)
        install_element(OSPF_NODE, &ospf_auto_cost_reference_bandwidth_cmd);
        install_element(OSPF_NODE, &no_ospf_auto_cost_reference_bandwidth_cmd);
 
-       /* "neighbor" commands. */
+       /* "neighbor" command. */
        install_element(OSPF_NODE, &ospf_neighbor_cmd);
-       install_element(OSPF_NODE, &ospf_neighbor_poll_interval_cmd);
-       install_element(OSPF_NODE, &no_ospf_neighbor_cmd);
-       install_element(OSPF_NODE, &no_ospf_neighbor_poll_cmd);
 
        /* write multiplier commands */
        install_element(OSPF_NODE, &ospf_write_multiplier_cmd);
index 4c4666db5295460370ae7acab5a8c4bd8e0c1c8a..1d013b260ee6baa255ea0d9de67dcce8b4941976 100644 (file)
@@ -1096,6 +1096,7 @@ struct ospf_interface *add_ospf_interface(struct connected *co,
        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;
+       oi->p2mp_non_broadcast = IF_DEF_PARAMS(co->ifp)->p2mp_non_broadcast;
 
        /* Add pseudo neighbor. */
        ospf_nbr_self_reset(oi, oi->ospf->router_id);
@@ -1989,7 +1990,7 @@ static void ospf_nbr_nbma_add(struct ospf_nbr_nbma *nbr_nbma,
        struct route_node *rn;
        struct prefix p;
 
-       if (oi->type != OSPF_IFTYPE_NBMA)
+       if (!OSPF_IF_NON_BROADCAST(oi))
                return;
 
        if (nbr_nbma->nbr != NULL)
@@ -2036,7 +2037,7 @@ void ospf_nbr_nbma_if_update(struct ospf *ospf, struct ospf_interface *oi)
        struct route_node *rn;
        struct prefix_ipv4 p;
 
-       if (oi->type != OSPF_IFTYPE_NBMA)
+       if (!OSPF_IF_NON_BROADCAST(oi))
                return;
 
        for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn))
@@ -2095,7 +2096,7 @@ int ospf_nbr_nbma_set(struct ospf *ospf, struct in_addr nbr_addr)
        rn->info = nbr_nbma;
 
        for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) {
-               if (oi->type == OSPF_IFTYPE_NBMA)
+               if (OSPF_IF_NON_BROADCAST(oi))
                        if (prefix_match(oi->address, (struct prefix *)&p)) {
                                ospf_nbr_nbma_add(nbr_nbma, oi);
                                break;
diff --git a/tests/topotests/ospf_p2mp/r1/frr-p2mp-non-broadcast.conf b/tests/topotests/ospf_p2mp/r1/frr-p2mp-non-broadcast.conf
new file mode 100644 (file)
index 0000000..ca84349
--- /dev/null
@@ -0,0 +1,26 @@
+!
+hostname r1
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r1-eth0
+ ip address 10.1.0.1/24
+ ip ospf network point-to-multipoint non-broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r1-eth1
+ ip address 10.1.1.1/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+  ospf router-id 1.1.1.1
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.1.0/24 area 0
+  neighbor 10.1.0.2 poll-interval 5
+  neighbor 10.1.0.3 poll-interval 5
+  neighbor 10.1.0.4 poll-interval 5
+!
diff --git a/tests/topotests/ospf_p2mp/r1/frr-p2mp.conf b/tests/topotests/ospf_p2mp/r1/frr-p2mp.conf
new file mode 100644 (file)
index 0000000..cb4538c
--- /dev/null
@@ -0,0 +1,23 @@
+!
+hostname r1
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r1-eth0
+ ip address 10.1.0.1/24
+ ip ospf network point-to-multipoint
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r1-eth1
+ ip address 10.1.1.1/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+  ospf router-id 1.1.1.1
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.1.0/24 area 0
+!
diff --git a/tests/topotests/ospf_p2mp/r2/frr-p2mp-non-broadcast.conf b/tests/topotests/ospf_p2mp/r2/frr-p2mp-non-broadcast.conf
new file mode 100644 (file)
index 0000000..6e26897
--- /dev/null
@@ -0,0 +1,29 @@
+!
+hostname r2
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r2-eth0
+ ip address 10.1.0.2/24
+ ip ospf network point-to-multipoint non-broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r2-eth1
+ ip address 10.1.2.2/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+!
+!
+router ospf
+  ospf router-id 2.2.2.2
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.2.0/24 area 0
+  neighbor 10.1.0.1 poll-interval 5
+  neighbor 10.1.0.3 poll-interval 5
+  neighbor 10.1.0.4 poll-interval 5
+!
diff --git a/tests/topotests/ospf_p2mp/r2/frr-p2mp.conf b/tests/topotests/ospf_p2mp/r2/frr-p2mp.conf
new file mode 100644 (file)
index 0000000..0ca8aec
--- /dev/null
@@ -0,0 +1,26 @@
+!
+hostname r2
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r2-eth0
+ ip address 10.1.0.2/24
+ ip ospf network point-to-multipoint
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r2-eth1
+ ip address 10.1.2.2/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+!
+!
+router ospf
+  ospf router-id 2.2.2.2
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.2.0/24 area 0
+!
diff --git a/tests/topotests/ospf_p2mp/r3/frr-p2mp-non-broadcast.conf b/tests/topotests/ospf_p2mp/r3/frr-p2mp-non-broadcast.conf
new file mode 100644 (file)
index 0000000..a69e055
--- /dev/null
@@ -0,0 +1,28 @@
+!
+hostname r3
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r3-eth0
+ ip address 10.1.0.3/24
+ ip ospf network point-to-multipoint non-broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+interface r3-eth1
+ ip address 10.1.3.3/24
+ ip ospf network broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+router ospf
+  ospf router-id 3.3.3.3
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.3.0/24 area 0
+  neighbor 10.1.0.1 poll-interval 5
+  neighbor 10.1.0.2 poll-interval 5
+  neighbor 10.1.0.4 poll-interval 5
diff --git a/tests/topotests/ospf_p2mp/r3/frr-p2mp.conf b/tests/topotests/ospf_p2mp/r3/frr-p2mp.conf
new file mode 100644 (file)
index 0000000..41ea70d
--- /dev/null
@@ -0,0 +1,25 @@
+!
+hostname r3
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r3-eth0
+ ip address 10.1.0.3/24
+ ip ospf network point-to-multipoint
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+interface r3-eth1
+ ip address 10.1.3.3/24
+ ip ospf network broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+router ospf
+  ospf router-id 3.3.3.3
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.3.0/24 area 0
diff --git a/tests/topotests/ospf_p2mp/r4/frr-p2mp-non-broadcast.conf b/tests/topotests/ospf_p2mp/r4/frr-p2mp-non-broadcast.conf
new file mode 100644 (file)
index 0000000..1b83885
--- /dev/null
@@ -0,0 +1,28 @@
+!
+hostname r4
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r4-eth0
+ ip address 10.1.0.4/24
+ ip ospf network point-to-multipoint non-broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+interface r4-eth1
+ ip address 10.1.4.4/24
+ ip ospf network broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+router ospf
+  ospf router-id 4.4.4.4
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.4.0/24 area 0
+  neighbor 10.1.0.1 poll-interval 5
+  neighbor 10.1.0.2 poll-interval 5
+  neighbor 10.1.0.3 poll-interval 5
diff --git a/tests/topotests/ospf_p2mp/r4/frr-p2mp.conf b/tests/topotests/ospf_p2mp/r4/frr-p2mp.conf
new file mode 100644 (file)
index 0000000..21fa9c7
--- /dev/null
@@ -0,0 +1,25 @@
+!
+hostname r4
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r4-eth0
+ ip address 10.1.0.4/24
+ ip ospf network point-to-multipoint
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+interface r4-eth1
+ ip address 10.1.4.4/24
+ ip ospf network broadcast
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+router ospf
+  ospf router-id 4.4.4.4
+  distance 20
+  network 10.1.0.0/24 area 0
+  network 10.1.4.0/24 area 0
diff --git a/tests/topotests/ospf_p2mp/test_ospf_p2mp_broadcast.py b/tests/topotests/ospf_p2mp/test_ospf_p2mp_broadcast.py
new file mode 100644 (file)
index 0000000..352180b
--- /dev/null
@@ -0,0 +1,346 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+#
+# test_ospf_prefix_p2mp_broadcast.py
+#
+# Copyright (c) 2024 LabN Consulting
+# Acee Lindem
+#
+
+import os
+import sys
+import json
+from time import sleep
+from functools import partial
+import pytest
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+from lib.common_config import (
+    run_frr_cmd,
+    shutdown_bringup_interface,
+    start_router_daemons,
+    step,
+)
+
+
+"""
+test_ospf_p2mp_broadcast.py: Test OSPF Point-to-multipoint
+"""
+
+TOPOLOGY = """
+            +-----+             +-----+
+10.1.1.0/24 | r1  |             | r2  | 10.1.2.0/24
+ -----------+     |             |     +----------
+            +--+--+             +--+--+
+               |    10.1.0.0/24    |
+               |     +-------+     |
+               +---- |       |-----+
+                     | P2MP  |
+               +---- |       |-----+
+               |     +-------+     |
+               |                   |
+               |                   |
+            +--+--+              +-+---+
+10.1.3.0/24 | r3  |              | r4  | 10.1.4.0/24
+ -----------+     |              |     +----------
+            +-----+              +-----+
+
+
+"""
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.ospfd, pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+    "Build function"
+
+    # Create 4 routers
+    tgen.add_router("r1")
+    tgen.add_router("r2")
+    tgen.add_router("r3")
+    tgen.add_router("r4")
+
+    # Interconect them all to the P2MP network
+    switch = tgen.add_switch("s0-p2mp")
+    switch.add_link(tgen.gears["r1"])
+    switch.add_link(tgen.gears["r2"])
+    switch.add_link(tgen.gears["r3"])
+    switch.add_link(tgen.gears["r4"])
+
+    # Add standalone network to router 1
+    switch = tgen.add_switch("s-r1-1")
+    switch.add_link(tgen.gears["r1"])
+
+    # Add standalone network to router 2
+    switch = tgen.add_switch("s-r2-1")
+    switch.add_link(tgen.gears["r2"])
+
+    # Add standalone network to router 3
+    switch = tgen.add_switch("s-r3-1")
+    switch.add_link(tgen.gears["r3"])
+
+    # Add standalone network to router 4
+    switch = tgen.add_switch("s-r4-1")
+    switch.add_link(tgen.gears["r4"])
+
+
+def setup_module(mod):
+    logger.info("OSPF Point-to-MultiPoint:\n {}".format(TOPOLOGY))
+
+    tgen = Topogen(build_topo, mod.__name__)
+    tgen.start_topology()
+
+    # Starting Routers
+    router_list = tgen.routers()
+
+    for rname, router in router_list.items():
+        logger.info("Loading router %s" % rname)
+        router.load_frr_config(os.path.join(CWD, "{}/frr-p2mp.conf".format(rname)))
+
+    # Initialize all routers.
+    tgen.start_router()
+
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def verify_p2mp_interface(tgen):
+    "Verify the P2MP Configuration and interface settings"
+
+    r1 = tgen.gears["r1"]
+
+    step("Test running configuration for P2MP configuration")
+    rc = 0
+    rc, _, _ = tgen.net["r1"].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"
+    assert rc, assertmsg
+
+    step("Test OSPF interface for P2MP settings")
+    input_dict = {
+        "interfaces": {
+            "r1-eth0": {
+                "ospfEnabled": True,
+                "interfaceIp": {
+                    "10.1.0.1": {
+                        "ipAddress": "10.1.0.1",
+                        "ipAddressPrefixlen": 24,
+                        "ospfIfType": "Broadcast",
+                        "routerId": "1.1.1.1",
+                        "networkType": "POINTOMULTIPOINT",
+                        "cost": 10,
+                        "state": "Point-To-Point",
+                        "nbrCount": 3,
+                        "nbrAdjacentCount": 3,
+                        "prefixSuppression": False,
+                        "p2mpDelayReflood": False,
+                        "p2mpNonBroadcast": False,
+                    }
+                },
+                "ipAddress": "10.1.0.1",
+                "ipAddressPrefixlen": 24,
+                "ospfIfType": "Broadcast",
+                "area": "0.0.0.0",
+                "routerId": "1.1.1.1",
+                "networkType": "POINTOMULTIPOINT",
+                "cost": 10,
+                "state": "Point-To-Point",
+                "opaqueCapable": True,
+                "nbrCount": 3,
+                "nbrAdjacentCount": 3,
+                "prefixSuppression": False,
+                "p2mpDelayReflood": False,
+                "p2mpNonBroadcast": False,
+            }
+        }
+    }
+    test_func = partial(
+        topotest.router_json_cmp, r1, "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"
+    assert result is None, assertmsg
+
+
+def verify_non_p2mp_interface(tgen):
+    "Verify the removal of P2MP Configuration and interface settings"
+    r1 = tgen.gears["r1"]
+
+    step("Test running configuration for removal of P2MP configuration")
+    rc = 0
+    rc, _, _ = tgen.net["r1"].cmd_status(
+        "show running ospfd | grep -q 'ip ospf network point-to-multipoint'", warn=False
+    )
+    assertmsg = "'ip ospf network point-to-multipoint' not applied, but present in r1 configuration"
+    assert rc, assertmsg
+
+    step("Test OSPF interface for default settings")
+    input_dict = {
+        "interfaces": {
+            "r1-eth0": {
+                "ospfEnabled": True,
+                "interfaceIp": {
+                    "10.1.0.1": {
+                        "ipAddress": "10.1.0.1",
+                        "ipAddressPrefixlen": 24,
+                        "ospfIfType": "Broadcast",
+                        "routerId": "1.1.1.1",
+                        "networkType": "BROADCAST",
+                        "cost": 10,
+                        "prefixSuppression": False,
+                    }
+                },
+                "ipAddress": "10.1.0.1",
+                "ipAddressPrefixlen": 24,
+                "ospfIfType": "Broadcast",
+                "area": "0.0.0.0",
+                "routerId": "1.1.1.1",
+                "networkType": "BROADCAST",
+                "cost": 10,
+                "opaqueCapable": True,
+                "prefixSuppression": False,
+            }
+        }
+    }
+    test_func = partial(
+        topotest.router_json_cmp, r1, "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"
+    assert result is None, assertmsg
+
+
+def verify_p2mp_neighbor(tgen, router, neighbor, state, intf_addr, interface):
+    topo_router = tgen.gears[router]
+
+    step("Verify neighbor " + neighbor + " in " + state + " state")
+    input_dict = {
+        "default": {
+            neighbor: [
+                {
+                    "nbrState": state,
+                    "ifaceAddress": intf_addr,
+                    "ifaceName": interface,
+                }
+            ],
+        }
+    }
+    test_func = partial(
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip ospf neighbor " + neighbor + " json",
+        input_dict,
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = "P2MP Neighbor " + neighbor + " not in " + state
+    assert result is None, assertmsg
+
+
+def verify_p2mp_route(tgen, router, prefix, prefix_len, nexthop, interface):
+    topo_router = tgen.gears[router]
+
+    step("Verify router " + router + " p2mp route " + prefix + " installed")
+    input_dict = {
+        prefix: [
+            {
+                "prefix": prefix,
+                "prefixLen": prefix_len,
+                "protocol": "ospf",
+                "nexthops": [
+                    {
+                        "ip": nexthop,
+                        "interfaceName": interface,
+                    }
+                ],
+            }
+        ]
+    }
+    test_func = partial(
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip route " + prefix + " json",
+        input_dict,
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = prefix + " not installed on router " + router
+    assert result is None, assertmsg
+
+
+def test_p2mp_broadcast_interface():
+    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)
+
+    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("Verify router r1 p2mp routes installed")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.2", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.3.0/24", 24, "10.1.0.3", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.4", "r1-eth0")
+
+    step("Verify router r1 interface r1-eth0 p2mp configuration removal")
+    r1 = tgen.gears["r1"]
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nno ip ospf network point-to-multipoint")
+    verify_non_p2mp_interface(tgen)
+
+    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)
+
+    step("Verify restablishment of r1-eth0 p2mp 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("Verify router r1 p2mp routes reinstalled")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.2", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.3.0/24", 24, "10.1.0.3", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.4", "r1-eth0")
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    if not tgen.is_memleak_enabled():
+        pytest.skip("Memory leak test/report is disabled")
+
+    tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
diff --git a/tests/topotests/ospf_p2mp/test_ospf_p2mp_non_broadcast.py b/tests/topotests/ospf_p2mp/test_ospf_p2mp_non_broadcast.py
new file mode 100644 (file)
index 0000000..175dca7
--- /dev/null
@@ -0,0 +1,467 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_ospf_prefix_p2mp_non_broadcast.py
+#
+# Copyright (c) 2024 LabN Consulting
+# Acee Lindem
+#
+
+import os
+import sys
+import json
+from time import sleep
+from functools import partial
+import pytest
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+from lib.common_config import (
+    run_frr_cmd,
+    shutdown_bringup_interface,
+    start_router_daemons,
+    step,
+)
+
+
+"""
+test_ospf_p2mp_non_broadcast.py: Test OSPF Point-to-multipoint Non-Broadcast
+                                 Full Mesh
+"""
+
+TOPOLOGY = """
+            +-----+             +-----+
+10.1.1.0/24 | r1  |             | r2  | 10.1.2.0/24
+ -----------+     |             |     +----------
+            +--+--+             +--+--+
+               |    10.1.0.0/24    |
+               |     +-------+     |
+               +---- |       |-----+
+                     | P2MP  |
+               +---- |       |-----+
+               |     +-------+     |
+               |                   |
+               |                   |
+            +--+--+              +-+---+
+10.1.3.0/24 | r3  |              | r4  | 10.1.4.0/24
+ -----------+     |              |     +----------
+            +-----+              +-----+
+
+
+"""
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.ospfd, pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+    "Build function"
+
+    # Create 4 routers
+    tgen.add_router("r1")
+    tgen.add_router("r2")
+    tgen.add_router("r3")
+    tgen.add_router("r4")
+
+    # Interconect them all to the P2MP network
+    switch = tgen.add_switch("s0-p2mp")
+    switch.add_link(tgen.gears["r1"])
+    switch.add_link(tgen.gears["r2"])
+    switch.add_link(tgen.gears["r3"])
+    switch.add_link(tgen.gears["r4"])
+
+    # Add standalone network to router 1
+    switch = tgen.add_switch("s-r1-1")
+    switch.add_link(tgen.gears["r1"])
+
+    # Add standalone network to router 2
+    switch = tgen.add_switch("s-r2-1")
+    switch.add_link(tgen.gears["r2"])
+
+    # Add standalone network to router 3
+    switch = tgen.add_switch("s-r3-1")
+    switch.add_link(tgen.gears["r3"])
+
+    # Add standalone network to router 4
+    switch = tgen.add_switch("s-r4-1")
+    switch.add_link(tgen.gears["r4"])
+
+
+def setup_module(mod):
+    logger.info("OSPF Point-to-MultiPoint Non-Broadcast:\n {}".format(TOPOLOGY))
+
+    tgen = Topogen(build_topo, mod.__name__)
+    tgen.start_topology()
+
+    # Starting Routers
+    router_list = tgen.routers()
+
+    for rname, router in router_list.items():
+        logger.info("Loading router %s" % rname)
+        router.load_frr_config(
+            os.path.join(CWD, "{}/frr-p2mp-non-broadcast.conf".format(rname))
+        )
+
+    # Initialize all routers.
+    tgen.start_router()
+
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def verify_p2mp_interface(tgen, router, nbr_cnt, nbr_adj_cnt, non_broadcast):
+    "Verify the P2MP Configuration and interface settings"
+
+    topo_router = tgen.gears[router]
+
+    step("Test running configuration for P2MP configuration")
+    rc = 0
+    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 "
+        + router
+        + "configuration"
+    )
+    assert rc, assertmsg
+
+    step("Test OSPF interface for P2MP settings")
+    input_dict = {
+        "interfaces": {
+            "r1-eth0": {
+                "ospfEnabled": True,
+                "interfaceIp": {
+                    "10.1.0.1": {
+                        "ipAddress": "10.1.0.1",
+                        "ipAddressPrefixlen": 24,
+                        "ospfIfType": "Broadcast",
+                        "routerId": "1.1.1.1",
+                        "networkType": "POINTOMULTIPOINT",
+                        "cost": 10,
+                        "state": "Point-To-Point",
+                        "nbrCount": nbr_cnt,
+                        "nbrAdjacentCount": nbr_adj_cnt,
+                        "prefixSuppression": False,
+                        "p2mpDelayReflood": False,
+                        "p2mpNonBroadcast": non_broadcast,
+                    }
+                },
+                "ipAddress": "10.1.0.1",
+                "ipAddressPrefixlen": 24,
+                "ospfIfType": "Broadcast",
+                "area": "0.0.0.0",
+                "routerId": "1.1.1.1",
+                "networkType": "POINTOMULTIPOINT",
+                "cost": 10,
+                "state": "Point-To-Point",
+                "opaqueCapable": True,
+                "nbrCount": nbr_cnt,
+                "nbrAdjacentCount": nbr_adj_cnt,
+                "prefixSuppression": False,
+                "p2mpDelayReflood": False,
+                "p2mpNonBroadcast": non_broadcast,
+            }
+        }
+    }
+    test_func = partial(
+        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"
+    assert result is None, assertmsg
+
+
+def verify_p2mp_neighbor(tgen, router, neighbor, state, intf_addr, interface):
+    topo_router = tgen.gears[router]
+
+    step("Verify neighbor " + neighbor + " in " + state + " state")
+    input_dict = {
+        "default": {
+            neighbor: [
+                {
+                    "nbrState": state,
+                    "ifaceAddress": intf_addr,
+                    "ifaceName": interface,
+                }
+            ],
+        }
+    }
+    test_func = partial(
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip ospf neighbor " + neighbor + " json",
+        input_dict,
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = "P2MP Neighbor " + neighbor + " not in " + state
+    assert result is None, assertmsg
+
+
+def verify_p2mp_route(tgen, router, prefix, prefix_len, nexthop, interface):
+    topo_router = tgen.gears[router]
+
+    step("Verify router " + router + " p2mp route " + prefix + " installed")
+    input_dict = {
+        prefix: [
+            {
+                "prefix": prefix,
+                "prefixLen": prefix_len,
+                "protocol": "ospf",
+                "nexthops": [
+                    {
+                        "ip": nexthop,
+                        "interfaceName": interface,
+                    }
+                ],
+            }
+        ]
+    }
+    test_func = partial(
+        topotest.router_json_cmp,
+        topo_router,
+        "show ip route " + prefix + " json",
+        input_dict,
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = prefix + " not installed on router " + router
+    assert result is None, assertmsg
+
+
+def test_p2mp_non_broadcast_connectivity():
+    tgen = get_topogen()
+    r1 = tgen.gears["r1"]
+
+    if tgen.routers_have_failure():
+        pytest.skip("Skipped because of router(s) failure")
+
+    step("Verify router r1 interface OSPF point-to-multipoint non-broadcast interface")
+    verify_p2mp_interface(tgen, "r1", 3, 3, True)
+
+    step("Verify router r1 interface r1-eth0 p2mp non-broadcast configuration")
+    rc, _, _ = tgen.net["r1"].cmd_status(
+        "show running ospfd | grep -q 'ip ospf network point-to-multipoint non-broadcast'",
+        warn=False,
+    )
+    assertmsg = "'ip ospf network point-to-multipoint non-broadcast' applied, but not present in R1 configuration"
+    assert rc, assertmsg
+
+    step("Verify router r1 OSPF point-to-multipoint 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("Verify router r1 OSPF point-to-multipoint routes are installed")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.2", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.3.0/24", 24, "10.1.0.3", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.4", "r1-eth0")
+
+    step("Remove r1 interface r1-eth0 p2mp non-broadcast configuration")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nip ospf network point-to-multipoint")
+    rc, _, _ = tgen.net["r1"].cmd_status(
+        "show running ospfd | grep -q 'ip ospf network point-to-multipoint non-broadcast'",
+        warn=False,
+    )
+    assertmsg = "'ip ospf network point-to-multipoint non-broadcast' not applied, but present in r1 configuration"
+    assert rc, assertmsg
+
+    step("Verify router r1 interface OSPF point-to-multipoint broadcast interface")
+    verify_p2mp_interface(tgen, "r1", 3, 3, False)
+
+    step("Add r1 interface r1-eth0 p2mp non-broadcast configuration back")
+    r1.vtysh_cmd(
+        "conf t\ninterface r1-eth0\nip ospf network point-to-multipoint non-broadcast"
+    )
+    rc, _, _ = tgen.net["r1"].cmd_status(
+        "show running ospfd | grep 'ip ospf network point-to-multipoint non-broadcast'",
+        warn=False,
+    )
+    assertmsg = "'ip ospf netrwork point-to-multipoint non-broadcast' applied, but not present in R1 configuration"
+    assert rc, assertmsg
+
+    step("Verify router r1 interface OSPF point-to-multipoint non-broadcast interface")
+    verify_p2mp_interface(tgen, "r1", 3, 3, True)
+
+    step(
+        "Verify router r1 OSPF point-to-multipoint neighbors adjacencies restablished."
+    )
+    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"
+    )
+
+
+def test_p2mp_non_broadcast_partial_mesh_connectivity():
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip("Skipped because of router(s) failure")
+
+    """
+    test_ospf_p2mp_non_broadcast.py: Test OSPF Point-to-multipoint Non-Broadcast
+                                     Partial Mesh
+    """
+
+    TOPOLOGY = """
+                +-----+             +------+
+    10.1.1.0/24 | r1  |             | r4   | 10.1.4.0/24
+     -----------+     |             |      +----------
+                +-+---+             +--+-+-+
+                  |        P2MP        |
+                  |     Non-Broadcast  |
+                  |     10.1.0.0/24    |
+                +-+---+              +-+---+
+    10.1.2.0/24 | r2  |              | r3  | 10.1.3.0/24
+     -----------+     +--------------+     +----------
+                +-----+              +-----+
+
+
+     """
+    logger.info("OSPF Point-to-MultiPoint Non-Broadcast:\n {}".format(TOPOLOGY))
+
+    step("Change configuration to a partial mesh")
+    step("Delete neighbors in full mesh")
+    r1 = tgen.gears["r1"]
+    r1.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.3")
+    r1.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.4")
+    r2 = tgen.gears["r2"]
+    r2.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.4")
+    r3 = tgen.gears["r3"]
+    r3.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.1")
+    r4 = tgen.gears["r4"]
+    r4.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.1")
+    r4.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.2")
+
+    step("Flap interfaces on P2MP network to avoid transients")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nshut")
+    r2.vtysh_cmd("conf t\ninterface r2-eth0\nshut")
+    r3.vtysh_cmd("conf t\ninterface r3-eth0\nshut")
+    r4.vtysh_cmd("conf t\ninterface r4-eth0\nshut")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nno shut")
+    r2.vtysh_cmd("conf t\ninterface r2-eth0\nno shut")
+    r3.vtysh_cmd("conf t\ninterface r3-eth0\nno shut")
+    r4.vtysh_cmd("conf t\ninterface r4-eth0\nno shut")
+
+    step("Verify router r1 interface OSPF point-to-multipoint non-broadcast interface")
+    verify_p2mp_interface(tgen, "r1", 1, 1, True)
+
+    step("Verify router r1 interface r1-eth0 p2mp neighbor")
+    verify_p2mp_neighbor(
+        tgen, "r1", "2.2.2.2", "Full/DROther", "10.1.0.2", "r1-eth0:10.1.0.1"
+    )
+
+    step("Verify router r1 p2mp routes are installed")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.2", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.3.0/24", 24, "10.1.0.2", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.2", "r1-eth0")
+
+    """
+    test_ospf_p2mp_non_broadcast.py: Test OSPF Point-to-multipoint Non-Broadcast
+                                     Modified Partial Mesh
+    """
+
+    TOPOLOGY = """
+                +-----+             +------+
+    10.1.1.0/24 | r1  |             | r4   | 10.1.4.0/24
+     -----------+     +-------------+      +----------
+                +-----+             +--+-+-+
+                           P2MP        |
+                        Non-Broadcast  |
+                        10.1.0.0/24    |
+                +-+---+              +-+---+
+    10.1.2.0/24 | r2  |              | r3  | 10.1.3.0/24
+     -----------+     +--------------+     +----------
+                +-----+              +-----+
+
+
+     """
+    logger.info("OSPF Point-to-MultiPoint Non-Broadcast:\n {}".format(TOPOLOGY))
+
+    step("Change configuration to a partial mesh")
+    step("Modify neighbors in partial mesh")
+    r1 = tgen.gears["r1"]
+    r1.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.2")
+    r1.vtysh_cmd("conf t\nrouter ospf\nneighbor 10.1.0.4 poll-interval 5")
+    r2 = tgen.gears["r2"]
+    r2.vtysh_cmd("conf t\nrouter ospf\nno neighbor 10.1.0.1")
+    r4 = tgen.gears["r4"]
+    r4.vtysh_cmd("conf t\nrouter ospf\nneighbor 10.1.0.1")
+
+    step("Flap interfaces on P2MP network to avoid transients")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nshut")
+    r2.vtysh_cmd("conf t\ninterface r2-eth0\nshut")
+    r3.vtysh_cmd("conf t\ninterface r3-eth0\nshut")
+    r4.vtysh_cmd("conf t\ninterface r4-eth0\nshut")
+    r1.vtysh_cmd("conf t\ninterface r1-eth0\nno shut")
+    r2.vtysh_cmd("conf t\ninterface r2-eth0\nno shut")
+    r3.vtysh_cmd("conf t\ninterface r3-eth0\nno shut")
+    r4.vtysh_cmd("conf t\ninterface r4-eth0\nno shut")
+
+    step("Verify router r1 interface r1-eth0")
+    step("Verify router r1 interface OSPF point-to-multipoint non-broadcast interface")
+    verify_p2mp_interface(tgen, "r1", 1, 1, True)
+
+    step("Verify router r1 interface r1-eth0 p2mp neighbor")
+    input_dict = {
+        "neighbors": {
+            "4.4.4.4": [
+                {
+                    "nbrState": "Full/DROther",
+                    "ifaceAddress": "10.1.0.4",
+                    "ifaceName": "r1-eth0:10.1.0.1",
+                }
+            ],
+        }
+    }
+    test_func = partial(
+        topotest.router_json_cmp, r1, "show ip ospf neighbor json", input_dict
+    )
+    _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+    assertmsg = "P2MP Non-Broadcast Neighbors not adjacent on router r1"
+    assert result is None, assertmsg
+
+    step("Verify router r1 interface r1-eth0 p2mp routes are installed")
+    verify_p2mp_route(tgen, "r1", "10.1.2.0/24", 24, "10.1.0.4", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.3.0/24", 24, "10.1.0.4", "r1-eth0")
+    verify_p2mp_route(tgen, "r1", "10.1.4.0/24", 24, "10.1.0.4", "r1-eth0")
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    if not tgen.is_memleak_enabled():
+        pytest.skip("Memory leak test/report is disabled")
+
+    tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))