[ovs-dev,ovn,v2,3/3] Send service monitor health checks
diff mbox series

Message ID 20191105092316.3658815-1-numans@ovn.org
State Accepted
Headers show
Series
  • Health check feature for Load balancer backends
Related show

Commit Message

Numan Siddique Nov. 5, 2019, 9:23 a.m. UTC
From: Numan Siddique <numans@ovn.org>

ovn-controller will periodically sends out the service monitor packets
for the services configured in the SB DB Service_Monitor table. This
patch makes use of the action - handle_svc_check to handle the service
monitor reply packets from the service.

This patch supports IPv4 TCP and UDP service monitoring. For TCP services,
it sends out a TCP SYN packet and expects TCP ACK packet in response.
If the response is received on time, the status of the service is set to
"online", otherwise it is set to "offline".

For UDP services, it sends out a empty UDP packet and doesn't expect any
reply. In case the service is down, the host running the service, sends out
ICMP type 3 code 4 (destination unreachable) packet. If ovn-controller receives this
ICMP packet, it sets the status of the service to "offline".

Right now only IPv4 service monitoring is supported. An upcoming patch will add
the support for IPv6.

Acked-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 controller/ovn-controller.c   |   2 +
 controller/pinctrl.c          | 775 ++++++++++++++++++++++++++++++++--
 controller/pinctrl.h          |   2 +
 northd/ovn-northd.8.xml       |  10 +
 northd/ovn-northd.c           |  18 +
 tests/ovn.at                  | 119 ++++++
 tests/system-common-macros.at |   1 +
 tests/system-ovn.at           | 180 ++++++++
 8 files changed, 1081 insertions(+), 26 deletions(-)

Patch
diff mbox series

diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 9ab98be5c..27cb4885b 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2083,6 +2083,8 @@  main(int argc, char *argv[])
                                 sbrec_dns_table_get(ovnsb_idl_loop.idl),
                                 sbrec_controller_event_table_get(
                                     ovnsb_idl_loop.idl),
+                                sbrec_service_monitor_table_get(
+                                    ovnsb_idl_loop.idl),
                                 br_int, chassis,
                                 &ed_runtime_data.local_datapaths,
                                 &ed_runtime_data.active_tunnels);
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index a90ee73d6..8fc31d38a 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -38,6 +38,7 @@ 
 #include "openvswitch/ofp-switch.h"
 #include "openvswitch/ofp-util.h"
 #include "openvswitch/vlog.h"
+#include "lib/random.h"
 
 #include "lib/dhcp.h"
 #include "ovn-controller.h"
@@ -282,6 +283,22 @@  static void run_put_vport_bindings(
 static void wait_put_vport_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
 static void pinctrl_handle_bind_vport(const struct flow *md,
                                       struct ofpbuf *userdata);
+static void pinctrl_handle_svc_check(struct rconn *swconn,
+                                     const struct flow *ip_flow,
+                                     struct dp_packet *pkt_in,
+                                     const struct match *md);
+static void init_svc_monitors(void);
+static void destroy_svc_monitors(void);
+static void sync_svc_monitors(
+    struct ovsdb_idl_txn *ovnsb_idl_txn,
+    const struct sbrec_service_monitor_table *svc_mon_table,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    const struct sbrec_chassis *our_chassis)
+    OVS_REQUIRES(pinctrl_mutex);
+static void svc_monitors_run(struct rconn *swconn,
+                             long long int *svc_monitors_next_run_time)
+    OVS_REQUIRES(pinctrl_mutex);
+static void svc_monitors_wait(long long int svc_monitors_next_run_time);
 
 COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
 COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
@@ -444,6 +461,7 @@  pinctrl_init(void)
     init_event_table();
     ip_mcast_snoop_init();
     init_put_vport_bindings();
+    init_svc_monitors();
     pinctrl.br_int_name = NULL;
     pinctrl_handler_seq = seq_create();
     pinctrl_main_seq = seq_create();
@@ -1981,6 +1999,13 @@  process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
         ovs_mutex_unlock(&pinctrl_mutex);
         break;
 
+    case ACTION_OPCODE_HANDLE_SVC_CHECK:
+        ovs_mutex_lock(&pinctrl_mutex);
+        pinctrl_handle_svc_check(swconn, &headers, &packet,
+                                 &pin.flow_metadata);
+        ovs_mutex_unlock(&pinctrl_mutex);
+        break;
+
     default:
         VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
                      ntohl(ah->opcode));
@@ -2050,6 +2075,7 @@  pinctrl_handler(void *arg_)
     static long long int send_garp_rarp_time = LLONG_MAX;
     /* Next multicast query (IGMP) in ms. */
     static long long int send_mcast_query_time = LLONG_MAX;
+    static long long int svc_monitors_next_run_time = LLONG_MAX;
 
     swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
 
@@ -2110,11 +2136,16 @@  pinctrl_handler(void *arg_)
             }
         }
 
+        ovs_mutex_lock(&pinctrl_mutex);
+        svc_monitors_run(swconn, &svc_monitors_next_run_time);
+        ovs_mutex_unlock(&pinctrl_mutex);
+
         rconn_run_wait(swconn);
         rconn_recv_wait(swconn);
         send_garp_rarp_wait(send_garp_rarp_time);
         ipv6_ra_wait(send_ipv6_ra_time);
         ip_mcast_querier_wait(send_mcast_query_time);
+        svc_monitors_wait(svc_monitors_next_run_time);
 
         new_seq = seq_read(pinctrl_handler_seq);
         seq_wait(pinctrl_handler_seq, new_seq);
@@ -2140,6 +2171,7 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_ip_multicast_opts,
             const struct sbrec_dns_table *dns_table,
             const struct sbrec_controller_event_table *ce_table,
+            const struct sbrec_service_monitor_table *svc_mon_table,
             const struct ovsrec_bridge *br_int,
             const struct sbrec_chassis *chassis,
             const struct hmap *local_datapaths,
@@ -2174,6 +2206,8 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                   sbrec_ip_multicast_opts);
     run_buffered_binding(sbrec_mac_binding_by_lport_ip,
                          local_datapaths);
+    sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name,
+                      chassis);
     ovs_mutex_unlock(&pinctrl_mutex);
 }
 
@@ -2612,6 +2646,7 @@  pinctrl_destroy(void)
     destroy_put_vport_bindings();
     destroy_dns_cache();
     ip_mcast_snoop_destroy();
+    destroy_svc_monitors();
     seq_destroy(pinctrl_main_seq);
     seq_destroy(pinctrl_handler_seq);
 }
@@ -3050,6 +3085,36 @@  send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
     return garp_rarp->announce_time;
 }
 
+static void
+pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src,
+                     struct eth_addr eth_dst, ovs_be32 ipv4_src,
+                     ovs_be32 ipv4_dst, uint8_t ip_proto, uint8_t ttl,
+                     uint16_t ip_payload_len)
+{
+    dp_packet_clear(packet);
+    packet->packet_type = htonl(PT_ETH);
+
+    struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh);
+    eh->eth_dst = eth_dst;
+    eh->eth_src = eth_src;
+
+    struct ip_header *nh = dp_packet_put_zeros(packet, sizeof *nh);
+
+    eh->eth_type = htons(ETH_TYPE_IP);
+    dp_packet_set_l3(packet, nh);
+    nh->ip_ihl_ver = IP_IHL_VER(5, 4);
+    nh->ip_tot_len = htons(sizeof(struct ip_header) + ip_payload_len);
+    nh->ip_tos = IP_DSCP_CS6;
+    nh->ip_proto = ip_proto;
+    nh->ip_frag_off = htons(IP_DF);
+
+    /* Setting tos and ttl to 0 and 1 respectively. */
+    packet_set_ipv4(packet, ipv4_src, ipv4_dst, 0, ttl);
+
+    nh->ip_csum = 0;
+    nh->ip_csum = csum(nh, sizeof *nh);
+}
+
 /*
  * Multicast snooping configuration.
  */
@@ -3667,32 +3732,11 @@  ip_mcast_querier_send(struct rconn *swconn, struct ip_mcast_snoop *ip_ms,
     struct dp_packet packet;
 
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-
-    uint8_t ip_tos = 0;
-    uint8_t igmp_ttl = 1;
-
-    dp_packet_clear(&packet);
-    packet.packet_type = htonl(PT_ETH);
-
-    struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh);
-    eh->eth_dst = ip_ms->cfg.query_eth_dst;
-    eh->eth_src = ip_ms->cfg.query_eth_src;
-
-    struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
-
-    eh->eth_type = htons(ETH_TYPE_IP);
-    dp_packet_set_l3(&packet, nh);
-    nh->ip_ihl_ver = IP_IHL_VER(5, 4);
-    nh->ip_tot_len = htons(sizeof(struct ip_header) +
-                            sizeof(struct igmpv3_query_header));
-    nh->ip_tos = IP_DSCP_CS6;
-    nh->ip_proto = IPPROTO_IGMP;
-    nh->ip_frag_off = htons(IP_DF);
-    packet_set_ipv4(&packet, ip_ms->cfg.query_ipv4_src,
-                    ip_ms->cfg.query_ipv4_dst, ip_tos, igmp_ttl);
-
-    nh->ip_csum = 0;
-    nh->ip_csum = csum(nh, sizeof *nh);
+    pinctrl_compose_ipv4(&packet, ip_ms->cfg.query_eth_src,
+                         ip_ms->cfg.query_eth_dst,
+                         ip_ms->cfg.query_ipv4_src,
+                         ip_ms->cfg.query_ipv4_dst,
+                         IPPROTO_IGMP, 1, sizeof(struct igmpv3_query_header));
 
     struct igmpv3_query_header *igh =
         dp_packet_put_zeros(&packet, sizeof *igh);
@@ -4586,3 +4630,682 @@  pinctrl_handle_bind_vport(
 
     notify_pinctrl_main();
 }
+
+enum svc_monitor_state {
+    SVC_MON_S_INIT,
+    SVC_MON_S_WAITING,
+    SVC_MON_S_ONLINE,
+    SVC_MON_S_OFFLINE,
+};
+
+enum svc_monitor_status {
+    SVC_MON_ST_UNKNOWN,
+    SVC_MON_ST_OFFLINE,
+    SVC_MON_ST_ONLINE,
+};
+
+enum svc_monitor_protocol {
+    SVC_MON_PROTO_TCP,
+    SVC_MON_PROTO_UDP,
+};
+
+/* Service monitor health checks. */
+struct svc_monitor {
+    struct hmap_node hmap_node;
+    struct ovs_list list_node;
+
+    /* Should be accessed only with in the main ovn-controller
+     * thread. */
+    const struct sbrec_service_monitor *sb_svc_mon;
+
+    /* key */
+    struct in6_addr ip;
+    uint32_t dp_key;
+    uint32_t port_key;
+    uint32_t proto_port; /* tcp/udp port */
+
+    struct eth_addr ea;
+    long long int timestamp;
+    bool is_ip6;
+
+    long long int wait_time;
+    long long int next_send_time;
+
+    struct smap options;
+    /* The interval, in milli seconds, between service monitor checks. */
+    int interval;
+
+    /* The time, in milli seconds, after which the service monitor check
+     * times out. */
+    int svc_timeout;
+
+    /* The number of successful checks after which the service is
+     * considered online. */
+    int success_count;
+    int n_success;
+
+    /* The number of failure checks after which the service is
+     * considered offline. */
+    int failure_count;
+    int n_failures;
+
+    enum svc_monitor_protocol protocol;
+    enum svc_monitor_state state;
+    enum svc_monitor_status status;
+    struct dp_packet pkt;
+
+    uint32_t seq_no;
+    ovs_be16 tp_src;
+
+    bool delete;
+};
+
+static struct hmap svc_monitors_map;
+static struct ovs_list svc_monitors;
+
+static void
+init_svc_monitors(void)
+{
+    hmap_init(&svc_monitors_map);
+    ovs_list_init(&svc_monitors);
+}
+
+static void
+destroy_svc_monitors(void)
+{
+    struct svc_monitor *svc;
+    HMAP_FOR_EACH_POP (svc, hmap_node, &svc_monitors_map) {
+
+    }
+
+    hmap_destroy(&svc_monitors_map);
+
+    LIST_FOR_EACH_POP (svc, list_node, &svc_monitors) {
+        smap_destroy(&svc->options);
+        free(svc);
+    }
+}
+
+
+static struct svc_monitor *
+pinctrl_find_svc_monitor(uint32_t dp_key, uint32_t port_key,
+                         const struct in6_addr *ip_key, uint32_t port,
+                         enum svc_monitor_protocol protocol,
+                         uint32_t hash)
+{
+    struct svc_monitor *svc;
+    HMAP_FOR_EACH_WITH_HASH (svc, hmap_node, hash, &svc_monitors_map) {
+        if (svc->dp_key == dp_key
+            && svc->port_key == port_key
+            && svc->proto_port == port
+            && IN6_ARE_ADDR_EQUAL(&svc->ip, ip_key)
+            && svc->protocol == protocol) {
+            return svc;
+        }
+    }
+    return NULL;
+}
+
+static void
+sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                  const struct sbrec_service_monitor_table *svc_mon_table,
+                  struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                  const struct sbrec_chassis *our_chassis)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    bool changed = false;
+    struct svc_monitor *svc_mon;
+
+    LIST_FOR_EACH (svc_mon, list_node, &svc_monitors) {
+        svc_mon->delete = true;
+    }
+
+    const struct sbrec_service_monitor *sb_svc_mon;
+    SBREC_SERVICE_MONITOR_TABLE_FOR_EACH (sb_svc_mon, svc_mon_table) {
+        const struct sbrec_port_binding *pb
+            = lport_lookup_by_name(sbrec_port_binding_by_name,
+                                   sb_svc_mon->logical_port);
+        if (!pb) {
+            continue;
+        }
+
+        if (pb->chassis != our_chassis) {
+            continue;
+        }
+
+        struct in6_addr ip_addr;
+        ovs_be32 ip4;
+        if (ip_parse(sb_svc_mon->ip, &ip4)) {
+            ip_addr = in6_addr_mapped_ipv4(ip4);
+        } else {
+            continue;
+        }
+
+        struct eth_addr ea;
+        bool mac_found = false;
+        for (size_t i = 0; i < pb->n_mac; i++) {
+            struct lport_addresses laddrs;
+            if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
+                continue;
+            }
+
+            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+                if (ip4 == laddrs.ipv4_addrs[j].addr) {
+                    ea = laddrs.ea;
+                    mac_found = true;
+                    break;
+                }
+            }
+
+            if (mac_found) {
+                break;
+            }
+        }
+
+        if (!mac_found) {
+            continue;
+        }
+
+        uint32_t dp_key = pb->datapath->tunnel_key;
+        uint32_t port_key = pb->tunnel_key;
+        uint32_t hash =
+            hash_bytes(&ip_addr, sizeof ip_addr,
+                       hash_3words(dp_key, port_key, sb_svc_mon->port));
+
+        enum svc_monitor_protocol protocol;
+        if (!sb_svc_mon->protocol || strcmp(sb_svc_mon->protocol, "udp")) {
+            protocol = SVC_MON_PROTO_TCP;
+        } else {
+            protocol = SVC_MON_PROTO_UDP;
+        }
+
+        svc_mon = pinctrl_find_svc_monitor(dp_key, port_key, &ip_addr,
+                                           sb_svc_mon->port, protocol, hash);
+
+        if (!svc_mon) {
+            svc_mon = xmalloc(sizeof *svc_mon);
+            svc_mon->dp_key = dp_key;
+            svc_mon->port_key = port_key;
+            svc_mon->proto_port = sb_svc_mon->port;
+            svc_mon->ip = ip_addr;
+            svc_mon->is_ip6 = false;
+            svc_mon->state = SVC_MON_S_INIT;
+            svc_mon->status = SVC_MON_ST_UNKNOWN;
+            svc_mon->protocol = protocol;
+
+            smap_init(&svc_mon->options);
+            svc_mon->interval =
+                smap_get_int(&svc_mon->options, "interval", 5) * 1000;
+            svc_mon->svc_timeout =
+                smap_get_int(&svc_mon->options, "timeout", 3) * 1000;
+            svc_mon->success_count =
+                smap_get_int(&svc_mon->options, "success_count", 1);
+            svc_mon->failure_count =
+                smap_get_int(&svc_mon->options, "failure_count", 1);
+            svc_mon->n_success = 0;
+            svc_mon->n_failures = 0;
+
+            hmap_insert(&svc_monitors_map, &svc_mon->hmap_node, hash);
+            ovs_list_push_back(&svc_monitors, &svc_mon->list_node);
+            changed = true;
+        }
+
+        svc_mon->sb_svc_mon = sb_svc_mon;
+        svc_mon->ea = ea;
+        if (!smap_equal(&svc_mon->options, &sb_svc_mon->options)) {
+            smap_destroy(&svc_mon->options);
+            smap_clone(&svc_mon->options, &sb_svc_mon->options);
+            svc_mon->interval =
+                smap_get_int(&svc_mon->options, "interval", 5) * 1000;
+            svc_mon->svc_timeout =
+                smap_get_int(&svc_mon->options, "timeout", 3) * 1000;
+            svc_mon->success_count =
+                smap_get_int(&svc_mon->options, "success_count", 1);
+            svc_mon->failure_count =
+                smap_get_int(&svc_mon->options, "failure_count", 1);
+            changed = true;
+        }
+
+        svc_mon->delete = false;
+    }
+
+    struct svc_monitor *next;
+    LIST_FOR_EACH_SAFE (svc_mon, next, list_node, &svc_monitors) {
+        if (svc_mon->delete) {
+            hmap_remove(&svc_monitors_map, &svc_mon->hmap_node);
+            ovs_list_remove(&svc_mon->list_node);
+            smap_destroy(&svc_mon->options);
+            free(svc_mon);
+            changed = true;
+        } else if (ovnsb_idl_txn) {
+            /* Update the status of the service monitor. */
+            if (svc_mon->status != SVC_MON_ST_UNKNOWN) {
+                if (svc_mon->status == SVC_MON_ST_ONLINE) {
+                    sbrec_service_monitor_set_status(svc_mon->sb_svc_mon,
+                                                     "online");
+                } else {
+                    sbrec_service_monitor_set_status(svc_mon->sb_svc_mon,
+                                                     "offline");
+                }
+            }
+        }
+    }
+
+    if (changed) {
+        notify_pinctrl_handler();
+    }
+
+}
+
+static uint16_t
+get_random_src_port(void)
+{
+    uint16_t random_src_port = random_uint16();
+    while (random_src_port < 1024) {
+        random_src_port = random_uint16();
+    }
+
+    return random_src_port;
+}
+
+static void
+svc_monitor_send_tcp_health_check__(struct rconn *swconn,
+                                    struct svc_monitor *svc_mon,
+                                    uint16_t ctl_flags,
+                                    ovs_be32 tcp_seq,
+                                    ovs_be32 tcp_ack,
+                                    ovs_be16 tcp_src)
+{
+    if (svc_mon->is_ip6) {
+        return;
+    }
+
+    /* Compose a TCP-SYN packet. */
+    uint64_t packet_stub[128 / 8];
+    struct dp_packet packet;
+
+    struct eth_addr eth_src;
+    eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
+    ovs_be32 ip4_src;
+    ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
+                         ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+                         IPPROTO_TCP, 63, TCP_HEADER_LEN);
+
+    struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th);
+    dp_packet_set_l4(&packet, th);
+    th->tcp_dst = htons(svc_mon->proto_port);
+    th->tcp_src = tcp_src;
+
+    th->tcp_ctl = htons((5 << 12) | ctl_flags);
+    put_16aligned_be32(&th->tcp_seq, tcp_seq);
+    put_16aligned_be32(&th->tcp_ack, tcp_ack);
+
+    th->tcp_winsz = htons(65160);
+
+    uint32_t csum;
+    csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
+    csum = csum_continue(csum, th, dp_packet_size(&packet) -
+                         ((const unsigned char *)th -
+                         (const unsigned char *)dp_packet_eth(&packet)));
+    th->tcp_csum = csum_finish(csum);
+
+    uint64_t ofpacts_stub[4096 / 8];
+    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->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;
+    resubmit->table_id = OFTABLE_LOCAL_OUTPUT;
+
+    struct ofputil_packet_out po = {
+        .packet = dp_packet_data(&packet),
+        .packet_len = dp_packet_size(&packet),
+        .buffer_id = UINT32_MAX,
+        .ofpacts = ofpacts.data,
+        .ofpacts_len = ofpacts.size,
+    };
+    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&ofpacts);
+}
+
+static void
+svc_monitor_send_udp_health_check(struct rconn *swconn,
+                                  struct svc_monitor *svc_mon,
+                                  ovs_be16 udp_src)
+{
+    if (svc_mon->is_ip6) {
+        return;
+    }
+
+    struct eth_addr eth_src;
+    eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
+    ovs_be32 ip4_src;
+    ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+
+    uint64_t packet_stub[128 / 8];
+    struct dp_packet packet;
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
+                         ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+                         IPPROTO_UDP, 63, UDP_HEADER_LEN + 8);
+
+    struct udp_header *uh = dp_packet_put_zeros(&packet, sizeof *uh);
+    dp_packet_set_l4(&packet, uh);
+    uh->udp_dst = htons(svc_mon->proto_port);
+    uh->udp_src = udp_src;
+    uh->udp_len = htons(UDP_HEADER_LEN + 8);
+    uh->udp_csum = 0;
+    dp_packet_put_zeros(&packet, 8);
+
+    uint64_t ofpacts_stub[4096 / 8];
+    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->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;
+    resubmit->table_id = OFTABLE_LOCAL_OUTPUT;
+
+    struct ofputil_packet_out po = {
+        .packet = dp_packet_data(&packet),
+        .packet_len = dp_packet_size(&packet),
+        .buffer_id = UINT32_MAX,
+        .ofpacts = ofpacts.data,
+        .ofpacts_len = ofpacts.size,
+    };
+    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&ofpacts);
+}
+
+static void
+svc_monitor_send_health_check(struct rconn *swconn,
+                              struct svc_monitor *svc_mon)
+{
+    if (svc_mon->protocol == SVC_MON_PROTO_TCP) {
+        svc_mon->seq_no = random_uint32();
+        svc_mon->tp_src = htons(get_random_src_port());
+        svc_monitor_send_tcp_health_check__(swconn, svc_mon,
+                                            TCP_SYN,
+                                            htonl(svc_mon->seq_no), htonl(0),
+                                            svc_mon->tp_src);
+    } else {
+        if (!svc_mon->tp_src) {
+            svc_mon->tp_src = htons(get_random_src_port());
+        }
+        svc_monitor_send_udp_health_check(swconn, svc_mon, svc_mon->tp_src);
+    }
+
+    svc_mon->wait_time = time_msec() + svc_mon->svc_timeout;
+    svc_mon->state = SVC_MON_S_WAITING;
+}
+
+static void
+svc_monitors_run(struct rconn *swconn,
+                 long long int *svc_monitors_next_run_time)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    *svc_monitors_next_run_time = LLONG_MAX;
+    struct svc_monitor *svc_mon;
+    LIST_FOR_EACH (svc_mon, list_node, &svc_monitors) {
+        char ip_[INET6_ADDRSTRLEN + 1];
+        memset(ip_, 0, INET6_ADDRSTRLEN + 1);
+        ipv6_string_mapped(ip_, &svc_mon->ip);
+
+        long long int current_time = time_msec();
+        long long int next_run_time = LLONG_MAX;
+        enum svc_monitor_status old_status = svc_mon->status;
+        switch (svc_mon->state) {
+        case SVC_MON_S_INIT:
+            svc_monitor_send_health_check(swconn, svc_mon);
+            next_run_time = svc_mon->wait_time;
+            break;
+
+        case SVC_MON_S_WAITING:
+            if (current_time > svc_mon->wait_time) {
+                if (svc_mon->protocol ==  SVC_MON_PROTO_TCP) {
+                    svc_mon->n_failures++;
+                    svc_mon->state = SVC_MON_S_OFFLINE;
+                } else {
+                    svc_mon->n_success++;
+                    svc_mon->state = SVC_MON_S_ONLINE;
+                }
+                svc_mon->next_send_time = current_time + svc_mon->interval;
+                next_run_time = svc_mon->next_send_time;
+            } else {
+                next_run_time = svc_mon->wait_time - current_time;
+                next_run_time = svc_mon->wait_time;
+            }
+            break;
+
+        case SVC_MON_S_ONLINE:
+            if (svc_mon->n_success >= svc_mon->success_count) {
+                svc_mon->status = SVC_MON_ST_ONLINE;
+                svc_mon->n_success = 0;
+            }
+            if (current_time >= svc_mon->next_send_time) {
+                svc_monitor_send_health_check(swconn, svc_mon);
+                next_run_time = svc_mon->wait_time;
+            } else {
+                next_run_time = svc_mon->next_send_time;
+            }
+            break;
+
+        case SVC_MON_S_OFFLINE:
+            if (svc_mon->n_failures >= svc_mon->failure_count) {
+                svc_mon->status = SVC_MON_ST_OFFLINE;
+                svc_mon->n_failures = 0;
+            }
+
+            if (current_time >= svc_mon->next_send_time) {
+                svc_monitor_send_health_check(swconn, svc_mon);
+                next_run_time = svc_mon->wait_time;
+            } else {
+                next_run_time = svc_mon->next_send_time;
+            }
+            break;
+
+        default:
+            OVS_NOT_REACHED();
+        }
+
+        if (*svc_monitors_next_run_time > next_run_time) {
+            *svc_monitors_next_run_time = next_run_time;
+        }
+
+        if (old_status != svc_mon->status) {
+            /* Notify the main thread to update the status in the SB DB. */
+            notify_pinctrl_main();
+        }
+    }
+}
+
+static void
+svc_monitors_wait(long long int svc_monitors_next_run_time)
+{
+    if (!ovs_list_is_empty(&svc_monitors)) {
+        poll_timer_wait_until(svc_monitors_next_run_time);
+    }
+}
+
+static bool
+pinctrl_handle_tcp_svc_check(struct rconn *swconn,
+                             struct dp_packet *pkt_in,
+                             struct svc_monitor *svc_mon)
+{
+    struct tcp_header *th = dp_packet_l4(pkt_in);
+
+    if (!th) {
+        return false;
+    }
+
+    uint32_t tcp_seq = ntohl(get_16aligned_be32(&th->tcp_seq));
+    uint32_t tcp_ack = ntohl(get_16aligned_be32(&th->tcp_ack));
+
+    if (th->tcp_dst != svc_mon->tp_src) {
+       return false;
+    }
+
+    if (tcp_ack != (svc_mon->seq_no + 1)) {
+        return false;
+    }
+
+    /* Check for SYN flag and Ack flag. */
+    if ((TCP_FLAGS(th->tcp_ctl) & (TCP_SYN | TCP_ACK))
+         == (TCP_SYN | TCP_ACK)) {
+        svc_mon->n_success++;
+        svc_mon->state = SVC_MON_S_ONLINE;
+
+        /* Send RST-ACK packet. */
+        svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST | TCP_ACK,
+                                            htonl(tcp_ack + 1),
+                                            htonl(tcp_seq + 1), th->tcp_dst);
+        /* Calculate next_send_time. */
+        svc_mon->next_send_time = time_msec() + svc_mon->interval;
+        return true;
+    }
+
+    /* Check if RST flag is set. */
+    if (TCP_FLAGS(th->tcp_ctl) & TCP_RST) {
+        svc_mon->n_failures++;
+        svc_mon->state = SVC_MON_S_OFFLINE;
+
+        /* Calculate next_send_time. */
+        svc_mon->next_send_time = time_msec() + svc_mon->interval;
+        return false;
+    }
+
+    return false;
+}
+
+static void
+pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
+                         struct dp_packet *pkt_in, const struct match *md)
+{
+    uint32_t dp_key = ntohll(md->flow.metadata);
+    uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0];
+    struct in6_addr ip_addr;
+    struct eth_header *in_eth = dp_packet_data(pkt_in);
+    struct ip_header *in_ip = dp_packet_l3(pkt_in);
+
+    if (in_ip->ip_proto != IPPROTO_TCP && in_ip->ip_proto != IPPROTO_ICMP) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl,
+                     "handle service check: Unsupported protocol - [%x]",
+                     in_ip->ip_proto);
+        return;
+    }
+
+    uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
+    if (in_ip_len < IP_HEADER_LEN) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl,
+                     "IP packet with invalid length (%u)",
+                     in_ip_len);
+        return;
+    }
+
+    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
+        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
+    } else {
+        ip_addr = ip_flow->ipv6_dst;
+    }
+
+    if (in_ip->ip_proto == IPPROTO_TCP) {
+        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, &ip_addr,
+                                     ntohs(ip_flow->tp_src),
+                                     SVC_MON_PROTO_TCP, hash);
+        if (!svc_mon) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "handle service check: Service monitor "
+                         "not found");
+            return;
+        }
+        pinctrl_handle_tcp_svc_check(swconn, pkt_in, svc_mon);
+    } else {
+        /* It's ICMP packet. */
+        struct icmp_header *ih = dp_packet_l4(pkt_in);
+        if (!ih) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "ICMPv4 packet with invalid header");
+            return;
+        }
+
+        if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
+            return;
+        }
+
+        const char *end =
+            (char *)dp_packet_l4(pkt_in) + dp_packet_l4_size(pkt_in);
+
+        const struct ip_header *orig_ip_hr =
+            dp_packet_get_icmp_payload(pkt_in);
+        if (!orig_ip_hr) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
+                         "ICMP packet");
+            return;
+        }
+
+        if (ntohs(orig_ip_hr->ip_tot_len) !=
+            (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "Invalid original IP datagram length present "
+                         "in ICMP packet");
+            return;
+        }
+
+        struct udp_header *orig_uh = (struct udp_header *) (orig_ip_hr + 1);
+        if ((char *)orig_uh >= end) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "Invalid UDP header in the original "
+                         "IP datagram");
+            return;
+        }
+
+        uint32_t hash =
+            hash_bytes(&ip_addr, sizeof ip_addr,
+                       hash_3words(dp_key, port_key, ntohs(orig_uh->udp_dst)));
+
+        struct svc_monitor *svc_mon =
+            pinctrl_find_svc_monitor(dp_key, port_key, &ip_addr,
+                                     ntohs(orig_uh->udp_dst),
+                                     SVC_MON_PROTO_UDP, hash);
+        if (!svc_mon) {
+            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 packet");
+            return;
+        }
+
+        if (orig_uh->udp_src != svc_mon->tp_src) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "handle service check: UDP src port doesn't "
+                         "match in the Original IP datagram of ICMP packet");
+            return;
+        }
+
+        /* The UDP service monitor is down. */
+        svc_mon->n_failures++;
+        svc_mon->state = SVC_MON_S_OFFLINE;
+
+        /* Calculate next_send_time. */
+        svc_mon->next_send_time = time_msec() + svc_mon->interval;
+    }
+}
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index 80da28d34..8fa4baae9 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -30,6 +30,7 @@  struct ovsrec_bridge;
 struct sbrec_chassis;
 struct sbrec_dns_table;
 struct sbrec_controller_event_table;
+struct sbrec_service_monitor_table;
 
 void pinctrl_init(void);
 void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
@@ -42,6 +43,7 @@  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                  struct ovsdb_idl_index *sbrec_ip_multicast_opts,
                  const struct sbrec_dns_table *,
                  const struct sbrec_controller_event_table *,
+                 const struct sbrec_service_monitor_table *,
                  const struct ovsrec_bridge *, const struct sbrec_chassis *,
                  const struct hmap *local_datapaths,
                  const struct sset *active_tunnels);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 2e38e2d90..78fcf49c2 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1009,6 +1009,16 @@  output;
     </p>
 
     <ul>
+      <li>
+        A priorirty-110 flow with the match
+        <code>eth.src == <var>E</var></code> for all logical switch
+        datapaths and applies the action <code>handle_svc_check(inport)</code>.
+        Where <var>E</var> is the service monitor mac defined in the
+        <ref column="options:svc_monitor_mac" table="NB_Global"
+        db="OVN_Northbound"/> colum of <ref table="NB_Global"
+        db="OVN_Northbound"/> table.
+      </li>
+
       <li>
         A priority-100 flow that punts all IGMP packets to
         <code>ovn-controller</code> if IGMP snooping is enabled on the
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index b0f513de6..fd5f60306 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -5993,6 +5993,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
+    char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
     /* Ingress table 17: Destination lookup, broadcast and multicast handling
      * (priority 70 - 100). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
@@ -6000,6 +6001,9 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, svc_check_match,
+                      "handle_svc_check(inport);");
+
         struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
 
         if (mcast_sw_info->enabled) {
@@ -6059,6 +6063,7 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast",
                       "outport = \""MC_FLOOD"\"; output;");
     }
+    free(svc_check_match);
 
     /* Ingress table 17: Add IP multicast flows learnt from IGMP
      * (priority 90). */
@@ -10298,6 +10303,11 @@  static const char *rbac_mac_binding_auth[] =
 static const char *rbac_mac_binding_update[] =
     {"logical_port", "ip", "mac", "datapath"};
 
+static const char *rbac_svc_monitor_auth[] =
+    {""};
+static const char *rbac_svc_monitor_auth_update[] =
+    {"status"};
+
 static struct rbac_perm_cfg {
     const char *table;
     const char **auth;
@@ -10339,6 +10349,14 @@  static struct rbac_perm_cfg {
         .update = rbac_mac_binding_update,
         .n_update = ARRAY_SIZE(rbac_mac_binding_update),
         .row = NULL
+    },{
+        .table = "Service_Monitor",
+        .auth = rbac_svc_monitor_auth,
+        .n_auth = ARRAY_SIZE(rbac_svc_monitor_auth),
+        .insdel = false,
+        .update = rbac_svc_monitor_auth_update,
+        .n_update = ARRAY_SIZE(rbac_svc_monitor_auth_update),
+        .row = NULL
     },{
         .table = NULL,
         .auth = NULL,
diff --git a/tests/ovn.at b/tests/ovn.at
index b30f12c9a..da96d1508 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16687,5 +16687,124 @@  as hv4 ovs-ofctl show br-phys
 as hv4 ovs-appctl fdb/show br-phys
 
 OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
+AT_CLEANUP
+
+AT_SETUP([ovn -- Load balancer health checks])
+AT_KEYWORDS([lb])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+    set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+    options:tx_pcap=hv2/vif1-tx.pcap \
+    options:rxq_pcap=hv2/vif1-rx.pcap \
+    ofport-request=1
+
+ovn-nbctl ls-add sw0
+
+ovn-nbctl lsp-add sw0 sw0-p1
+ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+
+ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+# Create the second logical switch with one port
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-p1
+ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
 
+# Create a logical router and attach both logical switches
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+ovn-nbctl lsp-add sw0 sw0-lr0
+ovn-nbctl lsp-set-type sw0-lr0 router
+ovn-nbctl lsp-set-addresses sw0-lr0 router
+ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+ovn-nbctl lsp-add sw1 sw1-lr0
+ovn-nbctl lsp-set-type sw1-lr0 router
+ovn-nbctl lsp-set-addresses sw1-lr0 router
+ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
+
+ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
+
+ovn-nbctl --wait=sb -- --id=@hc create \
+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
+health_check @hc
+
+ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+
+OVN_POPULATE_ARP
+ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \
+service_monitor | sed '/^$/d' | wc -l`])
+
+ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
+AT_CHECK([cat lflows.txt], [0], [dnl
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(10.0.0.3:80,20.0.0.3:80);)
+])
+
+# get the svc monitor mac.
+svc_mon_src_mac=`ovn-nbctl get NB_Global . options:svc_monitor_mac | \
+sed s/":"//g | sed s/\"//g`
+
+OVS_WAIT_UNTIL(
+    [test 1 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
+grep "505400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+    [test 1 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | \
+grep "405400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor | grep offline | wc -l`])
+
+OVS_WAIT_UNTIL(
+    [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
+grep "505400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+    [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | \
+grep "405400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
+| grep priority=120 > lflows.txt
+AT_CHECK([cat lflows.txt], [0], [dnl
+  table=10(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
+])
+
+OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 64bf5ec63..c8fa6f03f 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -267,6 +267,7 @@  m4_define([OVS_CHECK_FIREWALL],
 #
 m4_define([OVS_START_L7],
    [PIDFILE=$(mktemp $2XXX.pid)
+    echo $PIDFILE > l7_pid_file
     NETNS_DAEMONIZE([$1], [[$PYTHON $srcdir/test-l7.py $2]], [$PIDFILE])
 
     dnl netstat doesn't print http over IPv6 as "http6"; drop the number.
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index b3f90aae2..7d1c65d85 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -2523,3 +2523,183 @@  as
 OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 /connection dropped.*/d"])
 AT_CLEANUP
+
+AT_SETUP([ovn -- Load balancer health checks])
+AT_KEYWORDS([lb])
+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
+
+ovn-nbctl ls-add sw0
+
+ovn-nbctl lsp-add sw0 sw0-p1
+ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+
+ovn-nbctl lsp-add sw0 sw0-p2
+ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+# Create the second logical switch with one port
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-p1
+ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+
+# Create a logical router and attach both logical switches
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+ovn-nbctl lsp-add sw0 sw0-lr0
+ovn-nbctl lsp-set-type sw0-lr0 router
+ovn-nbctl lsp-set-addresses sw0-lr0 router
+ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+ovn-nbctl lsp-add sw1 sw1-lr0
+ovn-nbctl lsp-set-type sw1-lr0 router
+ovn-nbctl lsp-set-addresses sw1-lr0 router
+ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
+
+ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
+
+ovn-nbctl --wait=sb -- --id=@hc create \
+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
+health_check @hc
+
+ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+
+OVN_POPULATE_ARP
+ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+         "10.0.0.1")
+
+ADD_NAMESPACES(sw1-p1)
+ADD_VETH(sw1-p1, sw1-p1, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+         "20.0.0.1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+         "10.0.0.1")
+
+# Wait until all the services are set to offline.
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor | sed '/^$/d' | grep offline | wc -l`])
+
+# Start webservers in 'sw0-p1' and 'sw1-p1'.
+OVS_START_L7([sw0-p1], [http])
+sw0_p1_pid_file=`cat l7_pid_file`
+OVS_START_L7([sw1-p1], [http])
+
+# Wait until the services are set to online.
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor | sed '/^$/d' | grep online | wc -l`])
+
+OVS_WAIT_UNTIL(
+    [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep "ip4.dst == 10.0.0.10" > lflows.txt
+     test 1 = `cat lflows.txt | grep "ct_lb(10.0.0.3:80,20.0.0.3:80)" | wc -l`]
+)
+
+# From sw0-p2 send traffic to vip - 10.0.0.10
+for i in `seq 1 20`; do
+    echo Request $i
+    ovn-sbctl list service_monitor
+    NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=10.0.0.4,dst=10.0.0.10,sport=<cleared>,dport=<cleared>),reply=(src=10.0.0.3,dst=10.0.0.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+tcp,orig=(src=10.0.0.4,dst=10.0.0.10,sport=<cleared>,dport=<cleared>),reply=(src=20.0.0.3,dst=10.0.0.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+# Stop webserer in sw0-p1
+kill `cat $sw0_p1_pid_file`
+
+# Wait until service_monitor for sw0-p1 is set to offline
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns status find \
+service_monitor logical_port=sw0-p1 | sed '/^$/d' | grep offline | wc -l`])
+
+OVS_WAIT_UNTIL(
+    [ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | grep "ip4.dst == 10.0.0.10" > lflows.txt
+     test 1 = `cat lflows.txt | grep "ct_lb(20.0.0.3:80)" | wc -l`]
+)
+
+ovs-appctl dpctl/flush-conntrack
+# From sw0-p2 send traffic to vip - 10.0.0.10
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=10.0.0.4,dst=10.0.0.10,sport=<cleared>,dport=<cleared>),reply=(src=20.0.0.3,dst=10.0.0.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+])
+
+# Create udp load balancer.
+ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp
+lb_udp=`ovn-nbctl lb-list | grep udp | awk '{print $1}'`
+
+echo "lb udp uuid = $lb_udp"
+
+ovn-nbctl list load_balancer
+
+ovn-nbctl --wait=sb set load_balancer $lb_udp ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+ovn-nbctl --wait=sb set load_balancer $lb_udp ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
+
+ovn-nbctl --wait=sb -- --id=@hc create \
+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer $lb_udp \
+health_check @hc
+
+ovn-nbctl --wait=sb ls-lb-add sw0 lb2
+ovn-nbctl --wait=sb ls-lb-add sw1 lb2
+ovn-nbctl --wait=sb lr-lb-add lr0 lb2
+
+sleep 10
+
+ovn-nbctl list load_balancer
+echo "*******Next is health check*******"
+ovn-nbctl list Load_Balancer_Health_Check
+echo "********************"
+ovn-sbctl list service_monitor
+
+# Wait until udp service_monitor are set to offline
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+AT_CLEANUP