Add support for health check monitoring on logical switch ports via
new NB table Logical_Switch_Port_Health_Check. Supports ICMP/TCP/UDP
probes with configurable source IP and options.

- Extend service monitor for LSP monitoring type
- Add ICMP probe handling with sequence validation
- Generate necessary ARP/ND responses for health checks

Signed-off-by: Alexandra Rukomoinikova <[email protected]>
---
 controller/pinctrl.c     |  88 +++++++----
 northd/en-northd.c       |   2 +
 northd/inc-proc-northd.c |   5 +-
 northd/northd.c          | 310 ++++++++++++++++++++++++++++++++-------
 northd/northd.h          |   3 +
 tests/ovn-northd.at      | 134 +++++++++++++++++
 tests/system-ovn.at      |  69 +++++++++
 7 files changed, 525 insertions(+), 86 deletions(-)

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 2fb2dcded..3faba9b8e 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -6857,6 +6857,8 @@ enum svc_monitor_type {
     SVC_MON_TYPE_LB,
     /* network function */
     SVC_MON_TYPE_NF,
+    /* logical switch port */
+    SVC_MON_TYPE_LSP,
 };
 
 /* Service monitor health checks. */
@@ -6981,20 +6983,26 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
     const struct sbrec_service_monitor *sb_svc_mon;
     SBREC_SERVICE_MONITOR_TABLE_FOR_EACH (sb_svc_mon, svc_mon_table) {
         enum svc_monitor_type mon_type;
-        if (sb_svc_mon->type && !strcmp(sb_svc_mon->type,
-                                        "network-function")) {
+        enum svc_monitor_protocol protocol;
+
+        if (sb_svc_mon->type &&
+            !strcmp(sb_svc_mon->type, "network-function")) {
             mon_type = SVC_MON_TYPE_NF;
+        } else if (sb_svc_mon->type &&
+                   !strcmp(sb_svc_mon->type, "logical-switch-port")) {
+            mon_type = SVC_MON_TYPE_LSP;
         } else {
             mon_type = SVC_MON_TYPE_LB;
         }
 
-        enum svc_monitor_protocol protocol;
         if (!strcmp(sb_svc_mon->protocol, "udp")) {
-            protocol = SVC_MON_PROTO_UDP;
+            protocol = (mon_type == SVC_MON_TYPE_NF) ?
+                        SVC_MON_PROTO_ICMP : SVC_MON_PROTO_UDP;
         } else if (!strcmp(sb_svc_mon->protocol, "icmp")) {
             protocol = SVC_MON_PROTO_ICMP;
         } else {
-            protocol = SVC_MON_PROTO_TCP;
+            protocol = (mon_type == SVC_MON_TYPE_NF) ?
+                        SVC_MON_PROTO_ICMP : SVC_MON_PROTO_TCP;
         }
 
         const struct sbrec_port_binding *pb
@@ -7031,9 +7039,6 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
         bool mac_found = false;
 
         if (mon_type == SVC_MON_TYPE_NF) {
-            if (protocol != SVC_MON_PROTO_ICMP) {
-                continue;
-            }
             input_pb = lport_lookup_by_name(sbrec_port_binding_by_name,
                                             sb_svc_mon->logical_input_port);
             if (!input_pb) {
@@ -7048,11 +7053,6 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
                 }
             }
         } else {
-            if (protocol != SVC_MON_PROTO_TCP &&
-                protocol != SVC_MON_PROTO_UDP) {
-                continue;
-            }
-
             for (size_t i = 0; i < pb->n_mac && !mac_found; i++) {
                 struct lport_addresses laddrs;
 
@@ -8011,6 +8011,7 @@ static void
 svc_monitor_send_icmp_health_check__(struct rconn *swconn,
                                      struct svc_monitor *svc_mon)
 {
+    bool svc_mon_nf = (svc_mon->type == SVC_MON_TYPE_NF) ? true : false;
     uint64_t packet_stub[128 / 8];
     struct dp_packet packet;
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
@@ -8057,7 +8058,8 @@ svc_monitor_send_icmp_health_check__(struct rconn *swconn,
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
     enum ofp_version version = rconn_get_version(swconn);
     put_load(svc_mon->dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-    put_load(svc_mon->input_port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
+    put_load(svc_mon_nf ? svc_mon->input_port_key : svc_mon->port_key,
+             MFF_LOG_OUTPORT, 0, 32, &ofpacts);
     put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY, 1, &ofpacts);
     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
     resubmit->in_port = OFPP_CONTROLLER;
@@ -8335,6 +8337,7 @@ pinctrl_handle_svc_check(struct rconn *swconn, const 
struct flow *ip_flow,
                          "not found");
             return;
         }
+
         pinctrl_handle_tcp_svc_check(swconn, pkt_in, svc_mon);
     } else {
         const char *end =
@@ -8347,48 +8350,69 @@ pinctrl_handle_svc_check(struct rconn *swconn, const 
struct flow *ip_flow,
             return;
         }
 
-        /* Handle ICMP ECHO REQUEST probes for Network Function services */
+        /* Handle ICMP ECHO REQUEST probes for Network Function and
+         * Logical Switch Port services */
         if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
             struct icmp_header *ih = l4h;
             /* It's ICMP packet. */
-            if (ih->icmp_type == ICMP4_ECHO_REQUEST && ih->icmp_code == 0) {
-                uint32_t hash = hash_bytes(&dst_ip_addr, sizeof dst_ip_addr,
-                                           hash_3words(dp_key, port_key, 0));
-                struct svc_monitor *svc_mon =
-                    pinctrl_find_svc_monitor(dp_key, port_key, &dst_ip_addr, 0,
+            if ((ih->icmp_type == ICMP4_ECHO_REQUEST ||
+                ih->icmp_type == ICMP4_ECHO_REPLY) && ih->icmp_code == 0) {
+                uint32_t hash =
+                    hash_bytes(&ip_addr, sizeof ip_addr,
+                               hash_3words(dp_key, port_key,
+                               ntohs(ip_flow->tp_src)));
+
+                 struct svc_monitor *svc_mon =
+                    pinctrl_find_svc_monitor(dp_key, port_key,
+                                             ih->icmp_type ==
+                                             ICMP4_ECHO_REQUEST ?
+                                             &dst_ip_addr : &ip_addr, 0,
                                              SVC_MON_PROTO_ICMP, hash);
                 if (!svc_mon) {
-                    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
-                        1, 5);
+                    static struct vlog_rate_limit rl
+                        = VLOG_RATE_LIMIT_INIT(1, 5);
                     VLOG_WARN_RL(&rl, "handle service check: Service monitor "
                                  "not found for ICMP request");
                     return;
                 }
-                if (svc_mon->type == SVC_MON_TYPE_NF) {
-                    pinctrl_handle_icmp_svc_check(pkt_in, svc_mon);
-                }
+
+                /* Type validation done during creation -
+                 * asserts on unsupported types. */
+                ovs_assert(svc_mon->type != SVC_MON_TYPE_NF ||
+                           svc_mon->type != SVC_MON_TYPE_LSP);
+
+                pinctrl_handle_icmp_svc_check(pkt_in, svc_mon);
+
                 return;
             }
         } else if (in_eth->eth_type == htons(ETH_TYPE_IPV6)) {
             struct icmp6_data_header *ih6 = l4h;
             /* It's ICMPv6 packet. */
-            if (ih6->icmp6_base.icmp6_type == ICMP6_ECHO_REQUEST &&
+            if ((ih6->icmp6_base.icmp6_type == ICMP6_ECHO_REQUEST ||
+                ih6->icmp6_base.icmp6_type == ICMP6_ECHO_REPLY) &&
                 ih6->icmp6_base.icmp6_code == 0) {
                 uint32_t hash = hash_bytes(&dst_ip_addr, sizeof dst_ip_addr,
                                            hash_3words(dp_key, port_key, 0));
                 struct svc_monitor *svc_mon =
-                    pinctrl_find_svc_monitor(dp_key, port_key, &dst_ip_addr, 0,
+                    pinctrl_find_svc_monitor(dp_key, port_key,
+                                             ih6->icmp6_base.icmp6_type ==
+                                             ICMP6_ECHO_REQUEST ?
+                                             &dst_ip_addr : &ip_addr, 0,
                                              SVC_MON_PROTO_ICMP, hash);
                 if (!svc_mon) {
-                    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(
-                        1, 5);
+                    static struct vlog_rate_limit rl =
+                        VLOG_RATE_LIMIT_INIT(1, 5);
                     VLOG_WARN_RL(&rl, "handle service check: Service monitor "
                                  "not found for ICMPv6 request");
                     return;
                 }
-                if (svc_mon->type == SVC_MON_TYPE_NF) {
-                    pinctrl_handle_icmp_svc_check(pkt_in, svc_mon);
-                }
+
+                /* Type validation done during creation
+                 * - asserts on unsupported types. */
+                ovs_assert(svc_mon->type != SVC_MON_TYPE_NF ||
+                           svc_mon->type != SVC_MON_TYPE_LSP);
+
+                pinctrl_handle_icmp_svc_check(pkt_in, svc_mon);
                 return;
             }
         }
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 6815e6e39..9b37f3eee 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -82,6 +82,8 @@ northd_get_input_data(struct engine_node *node,
         EN_OVSDB_GET(engine_get_input("NB_network_function", node));
     input_data->nbrec_network_function_group_table =
         EN_OVSDB_GET(engine_get_input("NB_network_function_group", node));
+    input_data->nbrec_lsp_hc_table = EN_OVSDB_GET(engine_get_input(
+        "NB_logical_switch_port_health_check", node));
 
     input_data->sbrec_port_binding_table =
         EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 94199de12..5c41fd3ac 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -74,7 +74,8 @@ static unixctl_cb_func chassis_features_list;
     NB_NODE(chassis_template_var) \
     NB_NODE(sampling_app) \
     NB_NODE(network_function) \
-    NB_NODE(network_function_group)
+    NB_NODE(network_function_group) \
+    NB_NODE(logical_switch_port_health_check)
 
     enum nb_engine_node {
 #define NB_NODE(NAME) NB_##NAME,
@@ -254,6 +255,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
     engine_add_input(&en_northd, &en_nb_network_function, NULL);
     engine_add_input(&en_northd, &en_nb_network_function_group, NULL);
+    engine_add_input(&en_northd, &en_nb_logical_switch_port_health_check,
+                     NULL);
 
     engine_add_input(&en_northd, &en_sb_chassis, NULL);
     engine_add_input(&en_northd, &en_sb_mirror, NULL);
diff --git a/northd/northd.c b/northd/northd.c
index cdf12ec86..a9c700bb2 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3027,14 +3027,14 @@ get_service_mon(const struct hmap 
*local_svc_monitors_map,
     return NULL;
 }
 
-static void
+static inline void
 set_service_mon_options(const struct sbrec_service_monitor *sbrec_mon,
-                        const struct smap *nb_hc_options,
+                        const struct smap *options,
                         const char *target_az_name)
 {
     struct smap sb_svc_options = SMAP_INITIALIZER(&sb_svc_options);
 
-    smap_clone(&sb_svc_options, nb_hc_options);
+    smap_clone(&sb_svc_options, options);
     if (target_az_name) {
         smap_add(&sb_svc_options, "az-name", target_az_name);
     }
@@ -3042,6 +3042,40 @@ set_service_mon_options(const struct 
sbrec_service_monitor *sbrec_mon,
     smap_destroy(&sb_svc_options);
 }
 
+static inline void
+configure_service_mon_rec(const struct sbrec_service_monitor *sbrec_mon,
+                          const char *src_ip,
+                          const char *target_az_name,
+                          const char *svc_monitor_mac,
+                          const struct eth_addr *svc_monitor_mac_ea,
+                          const struct smap *options)
+{
+    set_service_mon_options(sbrec_mon, options, target_az_name);
+
+    struct eth_addr ea;
+    if (!sbrec_mon->src_mac ||
+        !eth_addr_from_string(sbrec_mon->src_mac, &ea) ||
+        !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
+        sbrec_service_monitor_set_src_mac(sbrec_mon, svc_monitor_mac);
+    }
+
+    if (!sbrec_mon->src_ip ||
+        strcmp(sbrec_mon->src_ip, src_ip)) {
+        sbrec_service_monitor_set_src_ip(sbrec_mon, src_ip);
+    }
+}
+
+static inline void
+update_status_to_service_mon(const struct sbrec_service_monitor *sbrec_mon,
+                             const struct ovn_port *op,
+                             bool remote_backend)
+{
+    if (!remote_backend && (!op->sb->n_up || !op->sb->up[0]) &&
+        sbrec_mon->status && !strcmp(sbrec_mon->status, "online")) {
+        sbrec_service_monitor_set_status(sbrec_mon, "offline");
+    }
+}
+
 static struct service_monitor_info *
 create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
                           struct hmap *local_svc_monitors_map,
@@ -3055,7 +3089,8 @@ create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
     struct service_monitor_info *mon_info =
         get_service_mon(local_svc_monitors_map,
                         ic_learned_svc_monitors_map,
-                        ip, logical_port, service_port, protocol);
+                        ip, logical_port,
+                        service_port, protocol);
 
     if (mon_info) {
         if (chassis_name && strcmp(mon_info->sbrec_mon->chassis_name,
@@ -3191,6 +3226,33 @@ ovn_nf_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
     mon_info->required = true;
 }
 
+static inline bool
+check_svc_port_available(const char *logical_port,
+                         const bool remote_backend,
+                         struct hmap *ls_ports,
+                         struct sset *svc_monitor_lsps,
+                         struct ovn_port **op,
+                         char **chassis_name)
+{
+    sset_add(svc_monitor_lsps, logical_port);
+
+    struct ovn_port *op_p = ovn_port_find(ls_ports, logical_port);
+
+    if (!remote_backend &&
+        (!op_p || !lsp_is_enabled(op_p->nbsp))) {
+        return false;
+    }
+
+    if (!remote_backend &&
+        op_p->sb && op_p->sb->chassis) {
+        *chassis_name = op_p->sb->chassis->name;
+    }
+
+    *op = op_p;
+
+    return true;
+}
+
 static void
 ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                   const struct ovn_northd_lb *lb,
@@ -3214,30 +3276,26 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                 vector_get_ptr(&lb_vip->backends, j);
             struct ovn_northd_lb_backend *backend_nb =
                 &lb_vip_nb->backends_nb[j];
+            const char *protocol = lb->nlb->protocol;
+            struct ovn_port *op = NULL;
+            char *chassis_name = NULL;
 
             if (!backend_nb->health_check) {
                 continue;
             }
 
-            sset_add(svc_monitor_lsps, backend_nb->logical_port);
-            struct ovn_port *op = ovn_port_find(ls_ports,
-                                                backend_nb->logical_port);
-
-            if (!backend_nb->remote_backend &&
-                (!op || !lsp_is_enabled(op->nbsp))) {
-                continue;
+            if (!check_svc_port_available(backend_nb->logical_port,
+                                          backend_nb->remote_backend,
+                                          ls_ports,
+                                          svc_monitor_lsps,
+                                          &op, &chassis_name)) {
+               continue;
             }
 
-            const char *protocol = lb->nlb->protocol;
             if (!protocol || !protocol[0]) {
                 protocol = "tcp";
             }
 
-            const char *chassis_name = NULL;
-            if (!backend_nb->remote_backend && op->sb->chassis) {
-                chassis_name = op->sb->chassis->name;
-            }
-
             struct service_monitor_info *mon_info =
                 create_or_get_service_mon(ovnsb_txn,
                                           local_svc_monitors_map,
@@ -3251,38 +3309,115 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                                           chassis_name,
                                           backend_nb->remote_backend);
             ovs_assert(mon_info);
-            set_service_mon_options(mon_info->sbrec_mon,
-                                    &lb_vip_nb->lb_health_check->options,
-                                    backend_nb->az_name);
-            struct eth_addr ea;
-            if (!mon_info->sbrec_mon->src_mac ||
-                !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
-                !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
-                sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
-                                                  svc_monitor_mac);
-            }
 
-            if (!mon_info->sbrec_mon->src_ip ||
-                strcmp(mon_info->sbrec_mon->src_ip,
-                       backend_nb->svc_mon_src_ip)) {
-                sbrec_service_monitor_set_src_ip(
-                    mon_info->sbrec_mon,
-                    backend_nb->svc_mon_src_ip);
-            }
+            configure_service_mon_rec(mon_info->sbrec_mon,
+                                      backend_nb->svc_mon_src_ip,
+                                      backend_nb->az_name,
+                                      svc_monitor_mac,
+                                      svc_monitor_mac_ea,
+                                      &lb_vip_nb->lb_health_check->options);
 
-            if (!backend_nb->remote_backend &&
-                (!op->sb->n_up || !op->sb->up[0])
-                && mon_info->sbrec_mon->status
-                && !strcmp(mon_info->sbrec_mon->status, "online")) {
-                sbrec_service_monitor_set_status(mon_info->sbrec_mon,
-                                                 "offline");
-            }
+            update_status_to_service_mon(mon_info->sbrec_mon,
+                                         op, backend_nb->remote_backend);
 
             mon_info->required = true;
         }
     }
 }
 
+static void
+ovn_lsp_svc_monitor_add_address(struct ovsdb_idl_txn *ovnsb_txn,
+    struct nbrec_logical_switch_port_health_check *lsp_hc,
+    const struct ovn_port *op,
+    const char *address,
+    const char *svc_monitor_mac,
+    const struct eth_addr *svc_monitor_mac_ea,
+    struct hmap *local_svc_monitors_map)
+{
+    struct service_monitor_info *mon_info =
+        create_or_get_service_mon(ovnsb_txn,
+                                  local_svc_monitors_map,
+                                  NULL,
+                                  "logical-switch-port",
+                                  address,
+                                  op->key,
+                                  NULL,
+                                  lsp_hc->port,
+                                  lsp_hc->protocol,
+                                  (op->sb && op->sb->chassis) ?
+                                  op->sb->chassis->name : NULL,
+                                  false);
+
+    ovs_assert(mon_info);
+
+    configure_service_mon_rec(mon_info->sbrec_mon,
+                              lsp_hc->src_ip,
+                              NULL,
+                              svc_monitor_mac,
+                              svc_monitor_mac_ea,
+                              &lsp_hc->options);
+
+    update_status_to_service_mon(mon_info->sbrec_mon, op, false);
+
+    mon_info->required = true;
+}
+
+static void
+ovn_lsp_svc_create_record(struct ovsdb_idl_txn *ovnsb_txn,
+    struct nbrec_logical_switch_port_health_check *lsp_hc,
+    const struct ovn_port *op,
+    const char *svc_monitor_mac,
+    const struct eth_addr *svc_monitor_mac_ea,
+    struct hmap *local_monitor_map)
+{
+    if (!lsp_hc->n_addresses) {
+        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
+                ovn_lsp_svc_monitor_add_address(
+                    ovnsb_txn, lsp_hc, op,
+                    op->lsp_addrs[i].ipv4_addrs[j].addr_s,
+                    svc_monitor_mac, svc_monitor_mac_ea,
+                    local_monitor_map);
+            }
+            for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
+                ovn_lsp_svc_monitor_add_address(
+                    ovnsb_txn, lsp_hc, op,
+                    op->lsp_addrs[i].ipv6_addrs[j].addr_s,
+                    svc_monitor_mac, svc_monitor_mac_ea,
+                    local_monitor_map);
+            }
+        }
+    } else {
+        for (size_t j = 0; j < lsp_hc->n_addresses; j++) {
+            ovn_lsp_svc_monitor_add_address(
+                ovnsb_txn, lsp_hc, op,
+                lsp_hc->addresses[j],
+                svc_monitor_mac, svc_monitor_mac_ea,
+                local_monitor_map);
+        }
+    }
+}
+
+static void
+ovn_lsp_svc_monitors_process_port(struct ovsdb_idl_txn *ovnsb_txn,
+                                  const struct ovn_port *op,
+                                  const char *svc_monitor_mac,
+                                  const struct eth_addr *svc_monitor_mac_ea,
+                                  struct hmap *local_monitor_map,
+                                  struct sset *svc_monitor_lsps)
+{
+    sset_add(svc_monitor_lsps, op->key);
+
+    for (size_t i = 0; i < op->nbsp->n_health_checks; i++) {
+        ovn_lsp_svc_create_record(ovnsb_txn,
+                                  op->nbsp->health_checks[i],
+                                  op,
+                                  svc_monitor_mac,
+                                  svc_monitor_mac_ea,
+                                  local_monitor_map);
+    }
+}
+
 static bool
 build_lb_vip_actions(const struct ovn_northd_lb *lb,
                      const struct ovn_lb_vip *lb_vip,
@@ -3491,7 +3626,7 @@ build_lb_datapaths(const struct hmap *lbs, const struct 
hmap *lb_groups,
 }
 
 static void
-build_svcs(
+build_svc_monitors_data(
     struct ovsdb_idl_txn *ovnsb_txn,
     struct ovsdb_idl_index *sbrec_service_monitor_by_learned_type,
     const char *svc_monitor_mac,
@@ -3503,7 +3638,8 @@ build_svcs(
     const struct nbrec_network_function_table *nbrec_network_function_table,
     struct sset *svc_monitor_lsps,
     struct hmap *local_svc_monitors_map,
-    struct hmap *ic_learned_svc_monitors_map)
+    struct hmap *ic_learned_svc_monitors_map,
+    struct hmapx *monitored_ports_map)
 {
     const struct sbrec_service_monitor *sbrec_mon;
     struct sbrec_service_monitor *key =
@@ -3512,6 +3648,11 @@ build_svcs(
 
     sbrec_service_monitor_index_set_ic_learned(key, false);
 
+    /* Hash only the IP, port, and logical port.
+     * This is necessary because two service monitors with the same IP and
+     * logical port will have different `port` values
+     * (e.g., an ICMP monitor defaults its port to 0).
+     */
     SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sbrec_mon, key,
         sbrec_service_monitor_by_learned_type) {
         uint32_t hash = sbrec_mon->port;
@@ -3553,6 +3694,18 @@ build_svcs(
         }
     }
 
+    struct hmapx_node *hmapx_node;
+    const struct ovn_port *op;
+    HMAPX_FOR_EACH (hmapx_node, monitored_ports_map) {
+        op = hmapx_node->data;
+        ovn_lsp_svc_monitors_process_port(ovnsb_txn,
+                                          op,
+                                          svc_monitor_mac,
+                                          svc_monitor_mac_ea,
+                                          local_svc_monitors_map,
+                                          svc_monitor_lsps);
+    }
+
     struct service_monitor_info *mon_info;
     HMAP_FOR_EACH_SAFE (mon_info, hmap_node, local_svc_monitors_map) {
         if (!mon_info->required) {
@@ -4017,6 +4170,16 @@ ovn_port_allocate_key(struct ovn_port *op)
     return true;
 }
 
+static inline void
+add_monitored_port(struct ovn_port *op, struct hmapx *monitored_ports_map)
+{
+    if (op->nbsp &&
+        op->nbsp->n_health_checks &&
+        lsp_is_enabled(op->nbsp)) {
+        hmapx_add(monitored_ports_map, op);
+    }
+}
+
 /* Updates the southbound Port_Binding table so that it contains the logical
  * switch ports specified by the northbound database.
  *
@@ -4033,7 +4196,8 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
     struct ovsdb_idl_index *sbrec_chassis_by_hostname,
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name,
     struct hmap *ls_datapaths, struct hmap *lr_datapaths,
-    struct hmap *ls_ports, struct hmap *lr_ports)
+    struct hmap *ls_ports, struct hmap *lr_ports,
+    struct hmapx *monitored_ports_map)
 {
     struct ovs_list sb_only, nb_only, both;
     /* XXX: Add tag_alloc_table and queue_id_bitmap as part of northd_data
@@ -4109,6 +4273,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
                               &active_ha_chassis_grps);
         op->od->is_transit_router |= is_transit_router_port(op);
         ovs_list_remove(&op->list);
+        add_monitored_port(op, monitored_ports_map);
     }
 
     /* Add southbound record for each unmatched northbound record. */
@@ -4122,6 +4287,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
                               &active_ha_chassis_grps);
         sbrec_port_binding_set_logical_port(op->sb, op->key);
         op->od->is_transit_router |= is_transit_router_port(op);
+        add_monitored_port(op, monitored_ports_map);
         ovs_list_remove(&op->list);
     }
 
@@ -15250,6 +15416,34 @@ build_arp_resolve_flows_for_lsp(
     }
 }
 
+static void
+build_arp_nd_lflow_for_lsp_svc_hc(struct ovn_port *op,
+                                  const char *svc_monitor_mac,
+                                  struct lflow_table *lflows,
+                                  struct ds *match,
+                                  struct ds *actions)
+{
+    const struct nbrec_logical_switch_port *nbsp = op->nbsp;
+    for (size_t i = 0; i < nbsp->n_health_checks; i++) {
+        struct nbrec_logical_switch_port_health_check *lsp_hc =
+            nbsp->health_checks[i];
+            ds_clear(match);
+            ds_clear(actions);
+
+            bool is_ipv4 = strchr(lsp_hc->src_ip, '.') ? true : false;
+
+            build_arp_nd_service_monitor_lflow(svc_monitor_mac,
+                lsp_hc->src_ip, actions, match, is_ipv4);
+
+            ovn_lflow_add_with_hint(lflows,
+                                    op->od,
+                                    S_SWITCH_IN_ARP_ND_RSP, 110,
+                                    ds_cstr(match), ds_cstr(actions),
+                                    &op->nbsp->header_,
+                                    op->lflow_ref);
+    }
+}
+
 #define ICMP4_NEED_FRAG_FORMAT                           \
     "icmp4_error {"                                      \
     "%s"                                                 \
@@ -18668,6 +18862,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct 
ovn_port *op,
                                          const struct shash *meter_groups,
                                          struct ds *match,
                                          struct ds *actions,
+                                         const char *svc_monitor_mac,
                                          struct lflow_table *lflows)
 {
     ovs_assert(op->nbsp);
@@ -18691,6 +18886,10 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct 
ovn_port *op,
 
     /* Build Logical Router Flows. */
     build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
+    if (op->nbsp->n_health_checks) {
+        build_arp_nd_lflow_for_lsp_svc_hc(op, svc_monitor_mac, lflows,
+                                          match, actions);
+    }
 }
 
 /* Helper function to combine all lflow generation which is iterated by logical
@@ -18794,11 +18993,8 @@ build_lflows_thread(void *arg)
                         return NULL;
                     }
                     build_lswitch_and_lrouter_iterate_by_lsp(op, lsi->ls_ports,
-                                                             lsi->lr_ports,
-                                                             lsi->meter_groups,
-                                                             &lsi->match,
-                                                             &lsi->actions,
-                                                             lsi->lflows);
+                        lsi->lr_ports, lsi->meter_groups, &lsi->match,
+                        &lsi->actions, lsi->svc_monitor_mac, lsi->lflows);
                     build_lbnat_lflows_iterate_by_lsp(
                         op, lsi->lr_stateful_table, &lsi->match,
                         &lsi->actions, lsi->lflows);
@@ -19065,6 +19261,7 @@ build_lswitch_and_lrouter_flows(
                                                      lsi.meter_groups,
                                                      &lsi.match,
                                                      &lsi.actions,
+                                                     lsi.svc_monitor_mac,
                                                      lsi.lflows);
             build_lbnat_lflows_iterate_by_lsp(op, lsi.lr_stateful_table,
                                               &lsi.match,
@@ -19376,6 +19573,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
                                                  lflow_input->lr_ports,
                                                  lflow_input->meter_groups,
                                                  &match, &actions,
+                                                 lflow_input->svc_monitor_mac,
                                                  lflows);
         /* Sync the new flows to SB. */
         bool handled = lflow_ref_sync_lflows(
@@ -19435,7 +19633,9 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
         build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
                                                  lflow_input->lr_ports,
                                                  lflow_input->meter_groups,
-                                                 &match, &actions, lflows);
+                                                 &match, &actions,
+                                                 lflow_input->svc_monitor_mac,
+                                                 lflows);
 
         /* Sync the newly added flows to SB. */
         bool handled = lflow_ref_sync_lflows(
@@ -20101,6 +20301,7 @@ northd_init(struct northd_data *data)
     hmap_init(&data->lb_group_datapaths_map);
     sset_init(&data->svc_monitor_lsps);
     hmap_init(&data->local_svc_monitors_map);
+    hmapx_init(&data->monitored_ports_map);
     init_northd_tracked_data(data);
 }
 
@@ -20186,6 +20387,7 @@ northd_destroy(struct northd_data *data)
                                 &data->ls_ports, &data->lr_ports);
 
     sset_destroy(&data->svc_monitor_lsps);
+    hmapx_destroy(&data->monitored_ports_map);
     destroy_northd_tracked_data(data);
 }
 
@@ -20291,11 +20493,12 @@ ovnnb_db_run(struct northd_input *input_data,
                 input_data->sbrec_chassis_by_hostname,
                 input_data->sbrec_ha_chassis_grp_by_name,
                 &data->ls_datapaths.datapaths, &data->lr_datapaths.datapaths,
-                &data->ls_ports, &data->lr_ports);
+                &data->ls_ports, &data->lr_ports,
+                &data->monitored_ports_map);
     build_lb_port_related_data(&data->lr_datapaths, &data->ls_datapaths,
                                &data->lb_datapaths_map,
                                &data->lb_group_datapaths_map);
-    build_svcs(ovnsb_txn,
+    build_svc_monitors_data(ovnsb_txn,
                input_data->sbrec_service_monitor_by_learned_type,
                input_data->svc_monitor_mac,
                &input_data->svc_monitor_mac_ea,
@@ -20305,7 +20508,8 @@ ovnnb_db_run(struct northd_input *input_data,
                &data->ls_ports, &data->lb_datapaths_map,
                input_data->nbrec_network_function_table,
                &data->svc_monitor_lsps, &data->local_svc_monitors_map,
-               input_data->ic_learned_svc_monitors_map);
+               input_data->ic_learned_svc_monitors_map,
+               &data->monitored_ports_map);
     build_lb_count_dps(&data->lb_datapaths_map);
     build_network_function_active(
         input_data->nbrec_network_function_group_table,
diff --git a/northd/northd.h b/northd/northd.h
index 32134d36e..e16f5cbfe 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -41,6 +41,8 @@ struct northd_input {
     const struct nbrec_network_function_table *nbrec_network_function_table;
     const struct nbrec_network_function_group_table
         *nbrec_network_function_group_table;
+    const struct nbrec_logical_switch_port_health_check_table
+        *nbrec_lsp_hc_table;
 
     /* Southbound table references */
     const struct sbrec_port_binding_table *sbrec_port_binding_table;
@@ -202,6 +204,7 @@ struct northd_data {
     struct hmap lb_group_datapaths_map;
     struct sset svc_monitor_lsps;
     struct hmap local_svc_monitors_map;
+    struct hmapx monitored_ports_map;
 
     /* Change tracking data. */
     struct northd_tracked_data trk_data;
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 448bc66ae..409dcb7d5 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -18558,3 +18558,137 @@ check_row_count Advertised_MAC_Binding 0
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Logical Switch Port Health Check - lflow/service monitor 
synchronization])
+ovn_start
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add ls1 lport1 -- \
+    lsp-set-addresses lport1 "f0:00:0f:01:02:04 192.168.0.10" 
"f0:00:0f:01:02:06 192.168.0.11"
+
+check ovn-nbctl lsp-add ls1 lport2 -- \
+    lsp-set-addresses lport2 "f0:00:0f:01:02:08 192.168.0.12"
+
+check ovn-nbctl set NB_Global . options:svc_monitor_mac="11:11:11:11:11:11"
+
+# ??reate a service monitor for all addresses on one of lsp.
+check ovn-nbctl lsp-hc-add lport1 icmp 192.168.0.255
+check_row_count nb:Logical_Switch_Port_Health_Check 1
+lport1_uuid1=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=icmp)
+
+# Check lflow and service monitor synchronization
+AT_CHECK([ovn-sbctl lflow-list ls1 | grep 'ls_in_arp_rsp'| grep "priority=110" 
| ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=110  , match=(arp.tpa == 
192.168.0.255 && arp.op == 1), action=(eth.dst = eth.src; eth.src = 
11:11:11:11:11:11; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 
11:11:11:11:11:11; arp.tpa = arp.spa; arp.spa = 192.168.0.255; outport = 
inport; flags.loopback = 1; output;)
+])
+
+check_row_count sb:Service_Monitor 2
+check_column "false false" sb:Service_Monitor ic_learned logical_port=lport1
+check_column "false false" sb:Service_Monitor remote logical_port=lport1
+check_column "192.168.0.10 192.168.0.11" sb:Service_Monitor ip 
logical_port=lport1
+check_column "icmp icmp" sb:Service_Monitor protocol logical_port=lport1
+check_column "192.168.0.255 192.168.0.255" sb:Service_Monitor src_ip 
logical_port=lport1
+check_column "0 0" sb:Service_Monitor port logical_port=lport1
+check_column "logical-switch-port logical-switch-port" sb:Service_Monitor type 
logical_port=lport1
+check_column "11:11:11:11:11:11 11:11:11:11:11:11" sb:Service_Monitor src_mac 
logical_port=lport1
+
+# ??reate one more service monitor for all addresses on one of lsp.
+check ovn-nbctl lsp-hc-add lport1 tcp 192.168.0.254 80
+check_row_count nb:Logical_Switch_Port_Health_Check 2
+lport1_uuid2=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=tcp)
+
+# Check that 2 records (2 addresses) have been created for each protocol.
+check_row_count sb:Service_Monitor 4
+
+AT_CHECK([ovn-sbctl lflow-list ls1 | grep 'ls_in_arp_rsp'| grep "priority=110" 
| ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=110  , match=(arp.tpa == 
192.168.0.254 && arp.op == 1), action=(eth.dst = eth.src; eth.src = 
11:11:11:11:11:11; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 
11:11:11:11:11:11; arp.tpa = arp.spa; arp.spa = 192.168.0.254; outport = 
inport; flags.loopback = 1; output;)
+  table=??(ls_in_arp_rsp      ), priority=110  , match=(arp.tpa == 
192.168.0.255 && arp.op == 1), action=(eth.dst = eth.src; eth.src = 
11:11:11:11:11:11; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 
11:11:11:11:11:11; arp.tpa = arp.spa; arp.spa = 192.168.0.255; outport = 
inport; flags.loopback = 1; output;)
+])
+
+# Change addresses on lport - addresses for service monitors should be changed
+check ovn-nbctl lsp-set-addresses lport1 "f0:00:0f:01:02:04 192.168.0.20"
+
+check_row_count sb:Service_Monitor 2
+check_column "false false" sb:Service_Monitor ic_learned logical_port=lport1
+check_column "false false " sb:Service_Monitor remote logical_port=lport1
+check_column "192.168.0.20 192.168.0.20" sb:Service_Monitor ip 
logical_port=lport1
+check_column "icmp tcp" sb:Service_Monitor protocol logical_port=lport1
+check_column "192.168.0.255 192.168.0.254" sb:Service_Monitor src_ip 
logical_port=lport1
+check_column "0 80" sb:Service_Monitor port logical_port=lport1
+check_column "logical-switch-port logical-switch-port" sb:Service_Monitor type 
logical_port=lport1
+check_column "11:11:11:11:11:11 11:11:11:11:11:11" sb:Service_Monitor src_mac 
logical_port=lport1
+
+# Check options propogations
+hc_lport1_uuid=$(fetch_column nb:logical_switch_port_health_check _uuid 
src_ip="192.168.0.255")
+
+check ovn-nbctl set logical_switch_port_health_check $hc_lport1_uuid 
options:interval=3
+check ovn-nbctl set logical_switch_port_health_check $hc_lport1_uuid 
options:timeout=30
+check ovn-nbctl set logical_switch_port_health_check $hc_lport1_uuid 
options:success_count=1
+check ovn-nbctl set logical_switch_port_health_check $hc_lport1_uuid 
options:failure_count=2
+
+
+sm_lport1_uuid=$(fetch_column sb:service_monitor _uuid protocol=icmp)
+
+AT_CHECK([ovn-sbctl get Service_Monitor $sm_lport1_uuid options:interval],
+[0], ["3"
+])
+AT_CHECK([ovn-sbctl get Service_Monitor $sm_lport1_uuid options:failure_count],
+[0], ["2"
+])
+AT_CHECK([ovn-sbctl get Service_Monitor $sm_lport1_uuid options:success_count],
+[0], ["1"
+])
+AT_CHECK([ovn-sbctl get Service_Monitor $sm_lport1_uuid options:timeout],
+[0], ["30"
+])
+
+ovn-nbctl list logical_switch_port
+
+check ovn-nbctl lsp-hc-del lport1
+
+check_row_count nb:Logical_Switch_Port_Health_Check 0
+
+# Change the service monitor to monitor only one address.
+check ovn-nbctl lsp-hc-add lport1 icmp 192.168.0.255 192.168.0.20
+check_row_count nb:Logical_Switch_Port_Health_Check 1
+lport1_uuid3=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=icmp)
+
+check_row_count sb:Service_Monitor 1
+
+AT_CHECK([ovn-sbctl lflow-list ls1 | grep 'ls_in_arp_rsp'| grep "priority=110" 
| ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=110  , match=(arp.tpa == 
192.168.0.255 && arp.op == 1), action=(eth.dst = eth.src; eth.src = 
11:11:11:11:11:11; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 
11:11:11:11:11:11; arp.tpa = arp.spa; arp.spa = 192.168.0.255; outport = 
inport; flags.loopback = 1; output;)
+])
+
+check_column "false" sb:Service_Monitor ic_learned logical_port=lport1
+check_column "false" sb:Service_Monitor remote logical_port=lport1
+check_column "192.168.0.20" sb:Service_Monitor ip logical_port=lport1
+check_column "icmp" sb:Service_Monitor protocol logical_port=lport1
+check_column "192.168.0.255" sb:Service_Monitor src_ip logical_port=lport1
+check_column "0" sb:Service_Monitor port logical_port=lport1
+check_column "logical-switch-port" sb:Service_Monitor type logical_port=lport1
+check_column "11:11:11:11:11:11" sb:Service_Monitor src_mac logical_port=lport1
+
+# Create one more monitor
+check ovn-nbctl lsp-hc-add lport2 icmp 192.168.0.255 192.168.0.12
+lport1_uuid4=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
addresses="192.168.0.12")
+check_row_count nb:Logical_Switch_Port_Health_Check 2
+
+check_row_count sb:Service_Monitor 2
+
+# One record for arp replay
+AT_CHECK([ovn-sbctl lflow-list ls1 | grep 'ls_in_arp_rsp'| grep "priority=110" 
| ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=110  , match=(arp.tpa == 
192.168.0.255 && arp.op == 1), action=(eth.dst = eth.src; eth.src = 
11:11:11:11:11:11; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 
11:11:11:11:11:11; arp.tpa = arp.spa; arp.spa = 192.168.0.255; outport = 
inport; flags.loopback = 1; output;)
+])
+
+ovn-nbctl list logical_switch_port_health_check
+
+check ovn-nbctl lsp-hc-del lport1
+check ovn-nbctl lsp-hc-del lport2
+check_row_count nb:Logical_Switch_Port_Health_Check 0
+check_row_count sb:Service_Monitor 0
+
+AT_CHECK([ovn-sbctl lflow-list ls1 | grep 'ls_in_arp_rsp'| grep "priority=110" 
| ovn_strip_lflows], [0], [])
+
+AT_CLEANUP
+])
+
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 76f73d96e..6dd12787a 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -19283,3 +19283,72 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
 /.*terminating with signal 15.*/d"])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Logical Switch Port Health Check])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl ls-add ls1
+
+ADD_NAMESPACES(lport)
+ADD_VETH(lport, lport, br-int, "192.168.0.10", "f0:00:0f:01:02:04", \
+         "192.168.0.1")
+NS_EXEC([lport], [ip r del default via 192.168.0.1 dev lport])
+NS_EXEC([lport], [ip r add default dev lport])
+
+check ovn-nbctl lsp-add ls1 lport -- \
+    lsp-set-addresses lport "f0:00:0f:01:02:04 192.168.0.10"
+
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl lsp-hc-add lport icmp 192.168.0.255
+lport1_uuid1=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=icmp)
+
+check_row_count sb:Service_Monitor 1
+
+NETNS_START_TCPDUMP([lport], [-n -c 2 -i lport], [lport])
+OVS_WAIT_UNTIL([
+    total_queries=`grep "ICMP" -c lport.tcpdump`
+    test "${total_queries}" = "2"
+])
+
+# Wait until the services are set to online.
+wait_row_count Service_Monitor 1 status=online
+
+# Create one more health check on logical switch port
+NETNS_DAEMONIZE([lport], [nc -l -k 192.168.0.10 4041], [lport_tcp.pid])
+
+check ovn-nbctl lsp-hc-add lport tcp 192.168.0.255 4041
+lport1_uuid2=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=tcp)
+
+check_row_count sb:Service_Monitor 2
+
+# Wait until the services are set to online.
+wait_row_count Service_Monitor 2 status=online
+
+NETNS_DAEMONIZE([lport], [nc -ulp 4042], [lport_udp.pid])
+
+check ovn-nbctl lsp-hc-add lport udp 192.168.0.255 4042
+lport1_uuid3=$(fetch_column nb:Logical_Switch_Port_Health_Check _uuid 
protocol=udp)
+
+check_row_count sb:Service_Monitor 3
+# Wait until the services are set to online.
+wait_row_count Service_Monitor 3 status=online
+
+AT_CLEANUP
+])
+
-- 
2.48.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to