diff mbox series

[ovs-dev] Add IPv6 support for lb health-check

Message ID 61c9bb288444c044a1e3945860cd72a2e5936f49.1669139578.git.lorenzo.bianconi@redhat.com
State Changes Requested
Headers show
Series [ovs-dev] Add IPv6 support for lb health-check | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Lorenzo Bianconi Nov. 22, 2022, 5:54 p.m. UTC
Add Similar to IPv4 counterpart, introduce IPv6 load-balancer health
check support.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2136094
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 controller/pinctrl.c    | 213 ++++++++++++++++++++++++-------------
 northd/northd.c         |  77 ++++++++++----
 northd/ovn-northd.8.xml |  17 +++
 ovn-nb.xml              |   8 +-
 tests/ovn.at            | 201 ++++++++++++++++++++++++++++++++++-
 tests/system-ovn.at     | 230 +++++++++++++++++++++++++++++++++++++++-
 6 files changed, 647 insertions(+), 99 deletions(-)

Comments

Numan Siddique Dec. 15, 2022, 5:36 p.m. UTC | #1
On Tue, Nov 22, 2022 at 12:54 PM Lorenzo Bianconi
<lorenzo.bianconi@redhat.com> wrote:
>
> Add Similar to IPv4 counterpart, introduce IPv6 load-balancer health
> check support.
>
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2136094
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>

Hi Lorenzo,

Thanks for adding this missing feature.  Please see a few comments below.


> ---
>  controller/pinctrl.c    | 213 ++++++++++++++++++++++++-------------
>  northd/northd.c         |  77 ++++++++++----
>  northd/ovn-northd.8.xml |  17 +++
>  ovn-nb.xml              |   8 +-
>  tests/ovn.at            | 201 ++++++++++++++++++++++++++++++++++-
>  tests/system-ovn.at     | 230 +++++++++++++++++++++++++++++++++++++++-
>  6 files changed, 647 insertions(+), 99 deletions(-)
>
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index f44775c7e..89c2f207b 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -6676,7 +6676,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
>          ovs_be32 ip4;
>          if (ip_parse(sb_svc_mon->ip, &ip4)) {
>              ip_addr = in6_addr_mapped_ipv4(ip4);
> -        } else {
> +        } else if (!ipv6_parse(sb_svc_mon->ip, &ip_addr)) {
>              continue;
>          }
>
> @@ -6689,16 +6689,27 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                  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 (IN6_IS_ADDR_V4MAPPED(&ip_addr)) {
> +                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;
> +                    }
> +                }
> +            } else {
> +                for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) {
> +                    if (!memcmp(&ip_addr, &laddrs.ipv6_addrs[j].addr,
> +                        sizeof(ovs_be32[4]))) {

It's a bit odd to use size of ovs_be32 here since we are memcmping
'struct in6_addr'.

I'd suggest using the macro - IN6_ARE_ADDR_EQUAL here.


> +                        ea = laddrs.ea;
> +                        mac_found = true;
> +                        break;
> +                    }
>                  }
>              }
>
> -            if (!mac_found && !laddrs.n_ipv4_addrs) {
> -                /* IPv4 address(es) are not configured. Use the first mac. */
> +            if (!mac_found && !laddrs.n_ipv4_addrs && !laddrs.n_ipv6_addrs) {
> +                /* IP address(es) are not configured. Use the first mac. */
>                  ea = laddrs.ea;
>                  mac_found = true;
>              }
> @@ -6732,7 +6743,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
>              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->is_ip6 = !IN6_IS_ADDR_V4MAPPED(&ip_addr);
>              svc_mon->state = SVC_MON_S_INIT;
>              svc_mon->status = SVC_MON_ST_UNKNOWN;
>              svc_mon->protocol = protocol;
> @@ -7500,26 +7511,30 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
>                                      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;
> +    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
>
>      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);
> +    if (svc_mon->is_ip6) {
> +        struct in6_addr ip6_src;
> +        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
> +        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
> +                             &ip6_src, &svc_mon->ip, IPPROTO_TCP,
> +                             63, TCP_HEADER_LEN);
> +    } else {
> +        ovs_be32 ip4_src;
> +        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
> +        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_l4(&packet);
>      dp_packet_set_l4(&packet, th);
> +    th->tcp_csum = 0;
>      th->tcp_dst = htons(svc_mon->proto_port);
>      th->tcp_src = tcp_src;
>
> @@ -7530,7 +7545,11 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
>      th->tcp_winsz = htons(65160);
>
>      uint32_t csum;
> -    csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
> +    if (svc_mon->is_ip6) {
> +        csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
> +    } else {
> +        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)));
> @@ -7565,21 +7584,26 @@ 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);
> +
> +    if (svc_mon->is_ip6) {
> +        struct in6_addr ip6_src;
> +        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
> +        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
> +                             &ip6_src, &svc_mon->ip, IPPROTO_UDP,
> +                             63, UDP_HEADER_LEN + 8);
> +    } else {
> +        ovs_be32 ip4_src;
> +        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
> +        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_l4(&packet);
>      dp_packet_set_l4(&packet, uh);
> @@ -7587,6 +7611,16 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
>      uh->udp_src = udp_src;
>      uh->udp_len = htons(UDP_HEADER_LEN + 8);
>      uh->udp_csum = 0;
> +    if (svc_mon->is_ip6) {
> +        uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
> +        csum = csum_continue(csum, uh, dp_packet_size(&packet) -
> +                             ((const unsigned char *) uh -
> +                              (const unsigned char *) dp_packet_eth(&packet)));
> +        uh->udp_csum = csum_finish(csum);
> +        if (!uh->udp_csum) {
> +            uh->udp_csum = htons(0xffff);
> +        }
> +    }
>
>      uint64_t ofpacts_stub[4096 / 8];
>      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
> @@ -7649,6 +7683,7 @@ svc_monitors_run(struct rconn *swconn,
>          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);
> @@ -7779,32 +7814,38 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
>      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);
> +    uint8_t ip_proto;
>
> -    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;
> +    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
> +        struct ip_header *in_ip = dp_packet_l3(pkt_in);
> +        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;
> +        }
> +
> +        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
> +        ip_proto = in_ip->ip_proto;
> +    } else {
> +        struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
> +        ip_addr = ip_flow->ipv6_src;
> +        ip_proto = in_ip->ip6_nxt;
>      }
>
> -    uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
> -    if (in_ip_len < IP_HEADER_LEN) {
> +    if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_ICMP &&
> +        ip_proto != IPPROTO_ICMPV6) {
>          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);
> +                     "handle service check: Unsupported protocol - [%x]",
> +                     ip_proto);
>          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) {
> +    if (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)));
> @@ -7821,44 +7862,68 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
>          }
>          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;
> -        }
> -
> +        struct udp_header *orig_uh;
>          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) {
> +        void *l4h = dp_packet_l4(pkt_in);
> +        if (!l4h) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> -            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
> -                         "ICMP packet");
> +            VLOG_WARN_RL(&rl, "ICMP packet with invalid header");
>              return;
>          }
>
> -        if (ntohs(orig_ip_hr->ip_tot_len) !=
> -            (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
> +        const void *in_ip = dp_packet_get_icmp_payload(pkt_in);
> +        if (!in_ip) {
>              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");
> +            VLOG_WARN_RL(&rl, "Original IP datagram not 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;
> +        if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
> +            struct icmp_header *ih = l4h;
> +            /* It's ICMP packet. */
> +            if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
> +                return;
> +            }
> +
> +            const struct ip_header *orig_ip_hr = in_ip;
> +            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;
> +            }
> +
> +            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;
> +            }
> +        } else {
> +            struct icmp6_header *ih6 = l4h;
> +            if (ih6->icmp6_type != 1 || ih6->icmp6_code != 4) {
> +                return;
> +            }
> +
> +            const struct ovs_16aligned_ip6_hdr *ip6_hdr = in_ip;
> +            if (ntohs(ip6_hdr->ip6_plen) != 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");
> +            }
> +
> +            orig_uh = (struct udp_header *) (ip6_hdr + 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 =
> diff --git a/northd/northd.c b/northd/northd.c
> index 00ff8f933..68c7655ba 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3751,8 +3751,15 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
>
>              struct ovn_port *op = NULL;
>              char *svc_mon_src_ip = NULL;
> +
> +            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
> +            struct ds key = DS_EMPTY_INITIALIZER;
> +            ds_put_format(&key, "%s%s%s",
> +                          ipv6 ? "[" : "", backend->ip_str,
> +                          ipv6 ? "]" : "");
> +
>              const char *s = smap_get(&lb->nlb->ip_port_mappings,
> -                                     backend->ip_str);
> +                                     ds_cstr(&key));
>              if (s) {
>                  char *port_name = xstrdup(s);
>                  char *p = strstr(port_name, ":");
> @@ -3760,10 +3767,21 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
>                      *p = 0;
>                      p++;
>                      op = ovn_port_find(ports, port_name);
> +                    if (ipv6) {
> +                        p = strstr(p, "[");
> +                        if (p) {
> +                            p++;
> +                        }
> +                        char *q = strstr(p, "]");
> +                        if (q) {
> +                            *q = 0;
> +                        }
> +                    }

This above code seems problematic to me.

I could crash ovn-northd by running the below command

 ovn-nbctl --wait=sb set load_balancer .
ip_port_mappings:\"[2002::3]\"=\"sw1-p1:foo\"

>                      svc_mon_src_ip = xstrdup(p);
>                  }
>                  free(port_name);
>              }
> +            ds_destroy(&key);
>
>              backend_nb->op = op;
>              backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> @@ -3841,8 +3859,10 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
>              }
>
>              n_active_backends++;
> -            ds_put_format(action, "%s:%"PRIu16",",
> -                          backend->ip_str, backend->port);
> +            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip);
> +            ds_put_format(action, "%s%s%s:%"PRIu16",",
> +                          ipv6 ? "[" : "", backend->ip_str,
> +                          ipv6 ? "]" : "", backend->port);
>          }
>
>          if (!n_active_backends) {
> @@ -8435,6 +8455,7 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
>              continue;
>          }
>
> +        struct ovn_lb_vip *lb_vip = &lb->vips[i];
>          for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
>              struct ovn_northd_lb_backend *backend_nb =
>                  &lb_vip_nb->backends_nb[j];
> @@ -8443,22 +8464,42 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
>              }
>
>              ds_clear(match);
> -            ds_put_format(match, "arp.tpa == %s && arp.op == 1",
> -                          backend_nb->svc_mon_src_ip);
>              ds_clear(actions);
> -            ds_put_format(actions,
> -                "eth.dst = eth.src; "
> -                "eth.src = %s; "
> -                "arp.op = 2; /* ARP reply */ "
> -                "arp.tha = arp.sha; "
> -                "arp.sha = %s; "
> -                "arp.tpa = arp.spa; "
> -                "arp.spa = %s; "
> -                "outport = inport; "
> -                "flags.loopback = 1; "
> -                "output;",
> -                svc_monitor_mac, svc_monitor_mac,
> -                backend_nb->svc_mon_src_ip);
> +            if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> +                ds_put_format(match, "arp.tpa == %s && arp.op == 1",
> +                              backend_nb->svc_mon_src_ip);
> +                ds_put_format(actions,
> +                    "eth.dst = eth.src; "
> +                    "eth.src = %s; "
> +                    "arp.op = 2; /* ARP reply */ "
> +                    "arp.tha = arp.sha; "
> +                    "arp.sha = %s; "
> +                    "arp.tpa = arp.spa; "
> +                    "arp.spa = %s; "
> +                    "outport = inport; "
> +                    "flags.loopback = 1; "
> +                    "output;",
> +                    svc_monitor_mac, svc_monitor_mac,
> +                    backend_nb->svc_mon_src_ip);
> +            } else {
> +                ds_put_format(match, "nd_ns && nd.target == %s",
> +                              backend_nb->svc_mon_src_ip);
> +                ds_put_format(actions,
> +                        "nd_na { "
> +                        "eth.dst = eth.src; "
> +                        "eth.src = %s; "
> +                        "ip6.src = %s; "
> +                        "nd.target = %s; "
> +                        "nd.tll = %s; "
> +                        "outport = inport; "
> +                        "flags.loopback = 1; "
> +                        "output; "
> +                        "};",
> +                        svc_monitor_mac,
> +                        backend_nb->svc_mon_src_ip,
> +                        backend_nb->svc_mon_src_ip,
> +                        svc_monitor_mac);
> +            }
>              ovn_lflow_add_with_hint(lflows,
>                                      backend_nb->op->od,
>                                      S_SWITCH_IN_ARP_ND_RSP, 110,
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 4b712cec4..d9439ea78 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -1418,6 +1418,23 @@ output;
>            These flows are required if an ARP request is sent for the IP
>            <var>SVC_MON_SRC_IP</var>.
>          </p>
> +
> +        <p>
> +          For IPv6 the similar flow is added with the following action
> +        </p>
> +
> +        <pre>
> +nd_na {
> +    eth.dst = eth.src;
> +    eth.src = <var>E</var>;
> +    ip6.src = <var>A</var>;
> +    nd.target = <var>A</var>;
> +    nd.tll = <var>E</var>;
> +    outport = inport;
> +    flags.loopback = 1;
> +    output;
> +};
> +        </pre>
>        </li>
>
>        <li>
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 0a4340529..e9340131c 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1793,9 +1793,8 @@
>
>      <group title="Health Checks">
>        <p>
> -        OVN supports health checks for load balancer endpoints, for IPv4 load
> -        balancers only.  When health checks are enabled, the load balancer uses
> -        only healthy endpoints.
> +        OVN supports health checks for load balancer endpoints. When health
> +        checks are enabled, the load balancer uses only healthy endpoints.
>        </p>
>
>        <p>
> @@ -1941,8 +1940,7 @@
>
>    <table name="Load_Balancer_Health_Check" title="load balancer">
>      <p>
> -      Each row represents one load balancer health check. Health checks
> -      are supported for IPv4 load balancers only.
> +      Each row represents one load balancer health check.
>      </p>
>

I'd also suggest updating the documentation on how to set the ip port
mappings for  IPv6. with an example.

Thanks
Numan

>      <column name="vip">
> diff --git a/tests/ovn.at b/tests/ovn.at
> index dde0f582b..73d7eeaa6 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -23625,7 +23625,7 @@ AT_CLEANUP
>  ])
>
>  OVN_FOR_EACH_NORTHD([
> -AT_SETUP([Load balancer health checks])
> +AT_SETUP([Load balancer health checks - IPv4])
>  AT_KEYWORDS([lb])
>  ovn_start
>
> @@ -23823,6 +23823,205 @@ OVN_CLEANUP([hv1], [hv2])
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Load balancer health checks - IPv6])
> +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
> +check 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
> +check 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
> +check ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +check 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
> +
> +check ovn-nbctl ls-add sw0
> +
> +check ovn-nbctl lsp-add sw0 sw0-p1
> +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 2001::3"
> +check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
> +
> +# Create port group and ACLs for sw0 ports.
> +check ovn-nbctl pg-add pg0_drop sw0-p1
> +check ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
> +check ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
> +
> +# Create the second logical switch with one port
> +check ovn-nbctl ls-add sw1
> +check ovn-nbctl lsp-add sw1 sw1-p1
> +check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 2002::3"
> +check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
> +
> +# Create port group and ACLs for sw1 ports.
> +check ovn-nbctl pg-add pg1_drop sw1-p1
> +check ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
> +check ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
> +
> +check ovn-nbctl pg-add pg1 sw1-p1
> +check ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
> +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> +
> +# Create a logical router and attach both logical switches
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001::1/64
> +check ovn-nbctl lsp-add sw0 sw0-lr0
> +check ovn-nbctl lsp-set-type sw0-lr0 router
> +check ovn-nbctl lsp-set-addresses sw0-lr0 router
> +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> +
> +check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2001::a/64
> +check ovn-nbctl lsp-add sw1 sw1-lr0
> +check ovn-nbctl lsp-set-type sw1-lr0 router
> +check ovn-nbctl lsp-set-addresses sw1-lr0 router
> +check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
> +
> +check ovn-nbctl lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
> +OVN_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1)
> +check ovn-nbctl set load_balancer ${OVN_LB_ID} selection_fields="ip_dst,ip_src,tp_dst,tp_src"
> +#
> +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
> +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
> +
> +AT_CHECK([ovn-nbctl --wait=sb \
> +          -- --id=@hc create Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\:80" \
> +             options:failure_count=100 \
> +          -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0>
> +])
> +
> +check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> +check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
> +check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
> +
> +check ovn-nbctl ls-add public
> +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 2003::1/64
> +check ovn-nbctl lsp-add public public-lr0
> +check ovn-nbctl lsp-set-type public-lr0 router
> +check ovn-nbctl lsp-set-addresses public-lr0 router
> +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
> +
> +# localnet port
> +check ovn-nbctl lsp-add public ln-public
> +check ovn-nbctl lsp-set-type ln-public localnet
> +check ovn-nbctl lsp-set-addresses ln-public unknown
> +check ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# schedule the gw router port to a chassis. Change the name of the chassis
> +check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
> +
> +OVN_POPULATE_ARP
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +wait_row_count Service_Monitor 2
> +
> +AT_CAPTURE_FILE([sbflows])
> +OVS_WAIT_FOR_OUTPUT(
> +  [ovn-sbctl dump-flows > sbflows
> +   ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
> +  [dnl
> +  (ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(reg0[[1]] = 0; ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> +])
> +
> +AT_CAPTURE_FILE([sbflows2])
> +OVS_WAIT_FOR_OUTPUT(
> +  [ovn-sbctl dump-flows > sbflows2
> +   ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
> +  [  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> +])
> +
> +# 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`]
> +)
> +
> +check ovn-nbctl set load_balancer_health_check [[2001::a]]:80 options:failure_count=1
> +wait_row_count Service_Monitor 2 status=offline
> +
> +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`]
> +)
> +
> +AT_CAPTURE_FILE([sbflows3])
> +ovn-sbctl dump-flows sw0 > sbflows3
> +AT_CHECK(
> +  [grep "ip6.dst == 2001::a && tcp.dst == 80" sbflows3 | grep priority=120 |\
> +   sed 's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
> +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(drop;)
> +])
> +
> +AT_CAPTURE_FILE([sbflows4])
> +ovn-sbctl dump-flows lr0 > sbflows4
> +AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
> +  (lr_in_dnat         ), priority=120  , match=(ct.est && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
> +  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
> +])
> +
> +# Delete sw0-p1
> +check ovn-nbctl lsp-del sw0-p1
> +
> +wait_row_count Service_Monitor 1
> +
> +# Add back sw0-p1 but without any IP address.
> +check ovn-nbctl lsp-add sw0 sw0-p1
> +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \
> +    lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
> +
> +wait_row_count Service_Monitor 2 status=offline
> +
> +check ovn-nbctl lsp-del sw0-p1
> +check ovn-nbctl lsp-del sw1-p1
> +wait_row_count Service_Monitor 0
> +
> +# Add back sw0-p1 but without any address set.
> +check ovn-nbctl lsp-add sw0 sw0-p1
> +
> +wait_row_count Service_Monitor 1
> +wait_row_count Service_Monitor 0 status=offline
> +wait_row_count Service_Monitor 0 status=online
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([SCTP Load balancer health checks])
>  AT_KEYWORDS([lb sctp])
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index cb3412717..358929522 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -4388,7 +4388,7 @@ AT_CLEANUP
>  ])
>
>  OVN_FOR_EACH_NORTHD([
> -AT_SETUP([Load balancer health checks])
> +AT_SETUP([Load balancer health checks - IPv4])
>  AT_KEYWORDS([lb])
>  ovn_start
>
> @@ -4615,6 +4615,234 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Load balancer health checks - IPv6])
> +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 2001::3"
> +ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
> +
> +ovn-nbctl lsp-add sw0 sw0-p2
> +ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 2001::4"
> +ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 2001::4"
> +
> +# Create port group and ACLs for sw0 ports.
> +ovn-nbctl pg-add pg0_drop sw0-p1 sw0-p2
> +ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
> +ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
> +
> +ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
> +ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip6" allow-related
> +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> +
> +# 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 2002::3"
> +ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
> +
> +# Create port group and ACLs for sw1 ports.
> +ovn-nbctl pg-add pg1_drop sw1-p1
> +ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
> +ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
> +
> +ovn-nbctl pg-add pg1 sw1-p1
> +ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
> +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> +
> +# 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 2001::1/64
> +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 2002::1/64
> +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 --reject lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
> +
> +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
> +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
> +
> +ovn-nbctl --wait=sb -- --id=@hc create \
> +Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\: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, "2001::3/64", "50:54:00:00:00:03", \
> +         "2001::1")
> +
> +ADD_NAMESPACES(sw1-p1)
> +ADD_VETH(sw1-p1, sw1-p1, br-int, "2002::3/64", "40:54:00:00:00:03", \
> +         "2002::1")
> +
> +ADD_NAMESPACES(sw0-p2)
> +ADD_VETH(sw0-p2, sw0-p2, br-int, "2001::4/64", "50:54:00:00:00:04", \
> +         "2001::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], [http6])
> +sw0_p1_pid_file=$(cat l7_pid_file)
> +OVS_START_L7([sw1-p1], [http6])
> +
> +# 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
> +     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2001::3\]]:80,[\[2002::3\]]:80)" | wc -l`]
> +)
> +
> +# From sw0-p2 send traffic to vip - 2001::a
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    ovn-sbctl list service_monitor
> +    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -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(2001::a) | grep -v fe80 | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2001::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> +])
> +
> +# Stop webserver 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
> +     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2002::3\]]:80)" | wc -l`]
> +)
> +
> +ovs-appctl dpctl/flush-conntrack
> +# From sw0-p2 send traffic to vip - 2001::a
> +for i in `seq 1 20`; do
> +    echo Request $i
> +    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
> +done
> +
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(2001::a) | grep -v fe80 | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> +])
> +
> +# trigger port binding release and check if status changed to offline
> +ovs-vsctl remove interface ovs-sw1-p1 external_ids iface-id
> +wait_row_count Service_Monitor 2
> +wait_row_count Service_Monitor 2 status=offline
> +
> +ovs-vsctl set interface ovs-sw1-p1 external_ids:iface-id=sw1-p1
> +wait_row_count Service_Monitor 2
> +wait_row_count Service_Monitor 1 status=online
> +
> +# 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`])
> +#
> +## Stop webserver in sw1-p1
> +#pid_file=$(cat l7_pid_file)
> +#NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)])
> +#
> +#NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &])
> +#OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
> +#service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`])
> +#NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4])
> +#
> +#OVS_WAIT_UNTIL([
> +#    n_reset=$(cat rst.pcap | wc -l)
> +#    test "${n_reset}" = "1"
> +#])
> +
> +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([NORTHD_TYPE])
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/connection dropped.*/d
> +/Service monitor not found.*/d"])
> +
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([Load Balancer LS hairpin IPv4])
>  AT_SKIP_IF([test $HAVE_NC = no])
> --
> 2.38.1
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Lorenzo Bianconi Dec. 16, 2022, 4:25 p.m. UTC | #2
> On Tue, Nov 22, 2022 at 12:54 PM Lorenzo Bianconi
> <lorenzo.bianconi@redhat.com> wrote:
> >
> > Add Similar to IPv4 counterpart, introduce IPv6 load-balancer health
> > check support.
> >
> > Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2136094
> > Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> 
> Hi Lorenzo,
> 
> Thanks for adding this missing feature.  Please see a few comments below.

Hi Numan,

thx for the review :)

> 
> 
> > ---
> >  controller/pinctrl.c    | 213 ++++++++++++++++++++++++-------------
> >  northd/northd.c         |  77 ++++++++++----
> >  northd/ovn-northd.8.xml |  17 +++
> >  ovn-nb.xml              |   8 +-
> >  tests/ovn.at            | 201 ++++++++++++++++++++++++++++++++++-
> >  tests/system-ovn.at     | 230 +++++++++++++++++++++++++++++++++++++++-
> >  6 files changed, 647 insertions(+), 99 deletions(-)
> >
> > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > index f44775c7e..89c2f207b 100644
> > --- a/controller/pinctrl.c
> > +++ b/controller/pinctrl.c
> > @@ -6676,7 +6676,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >          ovs_be32 ip4;
> >          if (ip_parse(sb_svc_mon->ip, &ip4)) {
> >              ip_addr = in6_addr_mapped_ipv4(ip4);
> > -        } else {
> > +        } else if (!ipv6_parse(sb_svc_mon->ip, &ip_addr)) {
> >              continue;
> >          }
> >
> > @@ -6689,16 +6689,27 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >                  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 (IN6_IS_ADDR_V4MAPPED(&ip_addr)) {
> > +                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;
> > +                    }
> > +                }
> > +            } else {
> > +                for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) {
> > +                    if (!memcmp(&ip_addr, &laddrs.ipv6_addrs[j].addr,
> > +                        sizeof(ovs_be32[4]))) {
> 
> It's a bit odd to use size of ovs_be32 here since we are memcmping
> 'struct in6_addr'.
> 
> I'd suggest using the macro - IN6_ARE_ADDR_EQUAL here.

ack, I will fix it in v2.

> 
> 
> > +                        ea = laddrs.ea;
> > +                        mac_found = true;
> > +                        break;
> > +                    }
> >                  }
> >              }
> >
> > -            if (!mac_found && !laddrs.n_ipv4_addrs) {
> > -                /* IPv4 address(es) are not configured. Use the first mac. */
> > +            if (!mac_found && !laddrs.n_ipv4_addrs && !laddrs.n_ipv6_addrs) {
> > +                /* IP address(es) are not configured. Use the first mac. */
> >                  ea = laddrs.ea;
> >                  mac_found = true;
> >              }
> > @@ -6732,7 +6743,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >              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->is_ip6 = !IN6_IS_ADDR_V4MAPPED(&ip_addr);
> >              svc_mon->state = SVC_MON_S_INIT;
> >              svc_mon->status = SVC_MON_ST_UNKNOWN;
> >              svc_mon->protocol = protocol;
> > @@ -7500,26 +7511,30 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
> >                                      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;
> > +    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
> >
> >      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);
> > +    if (svc_mon->is_ip6) {
> > +        struct in6_addr ip6_src;
> > +        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
> > +        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
> > +                             &ip6_src, &svc_mon->ip, IPPROTO_TCP,
> > +                             63, TCP_HEADER_LEN);
> > +    } else {
> > +        ovs_be32 ip4_src;
> > +        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
> > +        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_l4(&packet);
> >      dp_packet_set_l4(&packet, th);
> > +    th->tcp_csum = 0;
> >      th->tcp_dst = htons(svc_mon->proto_port);
> >      th->tcp_src = tcp_src;
> >
> > @@ -7530,7 +7545,11 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
> >      th->tcp_winsz = htons(65160);
> >
> >      uint32_t csum;
> > -    csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
> > +    if (svc_mon->is_ip6) {
> > +        csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
> > +    } else {
> > +        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)));
> > @@ -7565,21 +7584,26 @@ 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);
> > +
> > +    if (svc_mon->is_ip6) {
> > +        struct in6_addr ip6_src;
> > +        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
> > +        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
> > +                             &ip6_src, &svc_mon->ip, IPPROTO_UDP,
> > +                             63, UDP_HEADER_LEN + 8);
> > +    } else {
> > +        ovs_be32 ip4_src;
> > +        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
> > +        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_l4(&packet);
> >      dp_packet_set_l4(&packet, uh);
> > @@ -7587,6 +7611,16 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
> >      uh->udp_src = udp_src;
> >      uh->udp_len = htons(UDP_HEADER_LEN + 8);
> >      uh->udp_csum = 0;
> > +    if (svc_mon->is_ip6) {
> > +        uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
> > +        csum = csum_continue(csum, uh, dp_packet_size(&packet) -
> > +                             ((const unsigned char *) uh -
> > +                              (const unsigned char *) dp_packet_eth(&packet)));
> > +        uh->udp_csum = csum_finish(csum);
> > +        if (!uh->udp_csum) {
> > +            uh->udp_csum = htons(0xffff);
> > +        }
> > +    }
> >
> >      uint64_t ofpacts_stub[4096 / 8];
> >      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
> > @@ -7649,6 +7683,7 @@ svc_monitors_run(struct rconn *swconn,
> >          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);
> > @@ -7779,32 +7814,38 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
> >      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);
> > +    uint8_t ip_proto;
> >
> > -    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;
> > +    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
> > +        struct ip_header *in_ip = dp_packet_l3(pkt_in);
> > +        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;
> > +        }
> > +
> > +        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
> > +        ip_proto = in_ip->ip_proto;
> > +    } else {
> > +        struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
> > +        ip_addr = ip_flow->ipv6_src;
> > +        ip_proto = in_ip->ip6_nxt;
> >      }
> >
> > -    uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
> > -    if (in_ip_len < IP_HEADER_LEN) {
> > +    if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_ICMP &&
> > +        ip_proto != IPPROTO_ICMPV6) {
> >          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);
> > +                     "handle service check: Unsupported protocol - [%x]",
> > +                     ip_proto);
> >          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) {
> > +    if (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)));
> > @@ -7821,44 +7862,68 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
> >          }
> >          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;
> > -        }
> > -
> > +        struct udp_header *orig_uh;
> >          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) {
> > +        void *l4h = dp_packet_l4(pkt_in);
> > +        if (!l4h) {
> >              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> > -            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
> > -                         "ICMP packet");
> > +            VLOG_WARN_RL(&rl, "ICMP packet with invalid header");
> >              return;
> >          }
> >
> > -        if (ntohs(orig_ip_hr->ip_tot_len) !=
> > -            (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
> > +        const void *in_ip = dp_packet_get_icmp_payload(pkt_in);
> > +        if (!in_ip) {
> >              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");
> > +            VLOG_WARN_RL(&rl, "Original IP datagram not 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;
> > +        if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
> > +            struct icmp_header *ih = l4h;
> > +            /* It's ICMP packet. */
> > +            if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
> > +                return;
> > +            }
> > +
> > +            const struct ip_header *orig_ip_hr = in_ip;
> > +            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;
> > +            }
> > +
> > +            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;
> > +            }
> > +        } else {
> > +            struct icmp6_header *ih6 = l4h;
> > +            if (ih6->icmp6_type != 1 || ih6->icmp6_code != 4) {
> > +                return;
> > +            }
> > +
> > +            const struct ovs_16aligned_ip6_hdr *ip6_hdr = in_ip;
> > +            if (ntohs(ip6_hdr->ip6_plen) != 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");
> > +            }
> > +
> > +            orig_uh = (struct udp_header *) (ip6_hdr + 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 =
> > diff --git a/northd/northd.c b/northd/northd.c
> > index 00ff8f933..68c7655ba 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -3751,8 +3751,15 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
> >
> >              struct ovn_port *op = NULL;
> >              char *svc_mon_src_ip = NULL;
> > +
> > +            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
> > +            struct ds key = DS_EMPTY_INITIALIZER;
> > +            ds_put_format(&key, "%s%s%s",
> > +                          ipv6 ? "[" : "", backend->ip_str,
> > +                          ipv6 ? "]" : "");
> > +
> >              const char *s = smap_get(&lb->nlb->ip_port_mappings,
> > -                                     backend->ip_str);
> > +                                     ds_cstr(&key));
> >              if (s) {
> >                  char *port_name = xstrdup(s);
> >                  char *p = strstr(port_name, ":");
> > @@ -3760,10 +3767,21 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
> >                      *p = 0;
> >                      p++;
> >                      op = ovn_port_find(ports, port_name);
> > +                    if (ipv6) {
> > +                        p = strstr(p, "[");
> > +                        if (p) {
> > +                            p++;
> > +                        }
> > +                        char *q = strstr(p, "]");
> > +                        if (q) {
> > +                            *q = 0;
> > +                        }
> > +                    }
> 
> This above code seems problematic to me.
> 
> I could crash ovn-northd by running the below command
> 
>  ovn-nbctl --wait=sb set load_balancer .
> ip_port_mappings:\"[2002::3]\"=\"sw1-p1:foo\"

ack, I will fix it in v2.

> 
> >                      svc_mon_src_ip = xstrdup(p);
> >                  }
> >                  free(port_name);
> >              }
> > +            ds_destroy(&key);
> >
> >              backend_nb->op = op;
> >              backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> > @@ -3841,8 +3859,10 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
> >              }
> >
> >              n_active_backends++;
> > -            ds_put_format(action, "%s:%"PRIu16",",
> > -                          backend->ip_str, backend->port);
> > +            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip);
> > +            ds_put_format(action, "%s%s%s:%"PRIu16",",
> > +                          ipv6 ? "[" : "", backend->ip_str,
> > +                          ipv6 ? "]" : "", backend->port);
> >          }
> >
> >          if (!n_active_backends) {
> > @@ -8435,6 +8455,7 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
> >              continue;
> >          }
> >
> > +        struct ovn_lb_vip *lb_vip = &lb->vips[i];
> >          for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
> >              struct ovn_northd_lb_backend *backend_nb =
> >                  &lb_vip_nb->backends_nb[j];
> > @@ -8443,22 +8464,42 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
> >              }
> >
> >              ds_clear(match);
> > -            ds_put_format(match, "arp.tpa == %s && arp.op == 1",
> > -                          backend_nb->svc_mon_src_ip);
> >              ds_clear(actions);
> > -            ds_put_format(actions,
> > -                "eth.dst = eth.src; "
> > -                "eth.src = %s; "
> > -                "arp.op = 2; /* ARP reply */ "
> > -                "arp.tha = arp.sha; "
> > -                "arp.sha = %s; "
> > -                "arp.tpa = arp.spa; "
> > -                "arp.spa = %s; "
> > -                "outport = inport; "
> > -                "flags.loopback = 1; "
> > -                "output;",
> > -                svc_monitor_mac, svc_monitor_mac,
> > -                backend_nb->svc_mon_src_ip);
> > +            if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> > +                ds_put_format(match, "arp.tpa == %s && arp.op == 1",
> > +                              backend_nb->svc_mon_src_ip);
> > +                ds_put_format(actions,
> > +                    "eth.dst = eth.src; "
> > +                    "eth.src = %s; "
> > +                    "arp.op = 2; /* ARP reply */ "
> > +                    "arp.tha = arp.sha; "
> > +                    "arp.sha = %s; "
> > +                    "arp.tpa = arp.spa; "
> > +                    "arp.spa = %s; "
> > +                    "outport = inport; "
> > +                    "flags.loopback = 1; "
> > +                    "output;",
> > +                    svc_monitor_mac, svc_monitor_mac,
> > +                    backend_nb->svc_mon_src_ip);
> > +            } else {
> > +                ds_put_format(match, "nd_ns && nd.target == %s",
> > +                              backend_nb->svc_mon_src_ip);
> > +                ds_put_format(actions,
> > +                        "nd_na { "
> > +                        "eth.dst = eth.src; "
> > +                        "eth.src = %s; "
> > +                        "ip6.src = %s; "
> > +                        "nd.target = %s; "
> > +                        "nd.tll = %s; "
> > +                        "outport = inport; "
> > +                        "flags.loopback = 1; "
> > +                        "output; "
> > +                        "};",
> > +                        svc_monitor_mac,
> > +                        backend_nb->svc_mon_src_ip,
> > +                        backend_nb->svc_mon_src_ip,
> > +                        svc_monitor_mac);
> > +            }
> >              ovn_lflow_add_with_hint(lflows,
> >                                      backend_nb->op->od,
> >                                      S_SWITCH_IN_ARP_ND_RSP, 110,
> > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> > index 4b712cec4..d9439ea78 100644
> > --- a/northd/ovn-northd.8.xml
> > +++ b/northd/ovn-northd.8.xml
> > @@ -1418,6 +1418,23 @@ output;
> >            These flows are required if an ARP request is sent for the IP
> >            <var>SVC_MON_SRC_IP</var>.
> >          </p>
> > +
> > +        <p>
> > +          For IPv6 the similar flow is added with the following action
> > +        </p>
> > +
> > +        <pre>
> > +nd_na {
> > +    eth.dst = eth.src;
> > +    eth.src = <var>E</var>;
> > +    ip6.src = <var>A</var>;
> > +    nd.target = <var>A</var>;
> > +    nd.tll = <var>E</var>;
> > +    outport = inport;
> > +    flags.loopback = 1;
> > +    output;
> > +};
> > +        </pre>
> >        </li>
> >
> >        <li>
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 0a4340529..e9340131c 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -1793,9 +1793,8 @@
> >
> >      <group title="Health Checks">
> >        <p>
> > -        OVN supports health checks for load balancer endpoints, for IPv4 load
> > -        balancers only.  When health checks are enabled, the load balancer uses
> > -        only healthy endpoints.
> > +        OVN supports health checks for load balancer endpoints. When health
> > +        checks are enabled, the load balancer uses only healthy endpoints.
> >        </p>
> >
> >        <p>
> > @@ -1941,8 +1940,7 @@
> >
> >    <table name="Load_Balancer_Health_Check" title="load balancer">
> >      <p>
> > -      Each row represents one load balancer health check. Health checks
> > -      are supported for IPv4 load balancers only.
> > +      Each row represents one load balancer health check.
> >      </p>
> >
> 
> I'd also suggest updating the documentation on how to set the ip port
> mappings for  IPv6. with an example.
> 
> Thanks
> Numan

ack, I will add it in v2.

Regards,
Lorenzo

> 
> >      <column name="vip">
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index dde0f582b..73d7eeaa6 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -23625,7 +23625,7 @@ AT_CLEANUP
> >  ])
> >
> >  OVN_FOR_EACH_NORTHD([
> > -AT_SETUP([Load balancer health checks])
> > +AT_SETUP([Load balancer health checks - IPv4])
> >  AT_KEYWORDS([lb])
> >  ovn_start
> >
> > @@ -23823,6 +23823,205 @@ OVN_CLEANUP([hv1], [hv2])
> >  AT_CLEANUP
> >  ])
> >
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Load balancer health checks - IPv6])
> > +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
> > +check 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
> > +check 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
> > +check ovs-vsctl add-br br-phys
> > +ovn_attach n1 br-phys 192.168.0.2
> > +check 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
> > +
> > +check ovn-nbctl ls-add sw0
> > +
> > +check ovn-nbctl lsp-add sw0 sw0-p1
> > +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 2001::3"
> > +check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
> > +
> > +# Create port group and ACLs for sw0 ports.
> > +check ovn-nbctl pg-add pg0_drop sw0-p1
> > +check ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
> > +check ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
> > +
> > +# Create the second logical switch with one port
> > +check ovn-nbctl ls-add sw1
> > +check ovn-nbctl lsp-add sw1 sw1-p1
> > +check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 2002::3"
> > +check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
> > +
> > +# Create port group and ACLs for sw1 ports.
> > +check ovn-nbctl pg-add pg1_drop sw1-p1
> > +check ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
> > +check ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
> > +
> > +check ovn-nbctl pg-add pg1 sw1-p1
> > +check ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
> > +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> > +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> > +check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> > +
> > +# Create a logical router and attach both logical switches
> > +check ovn-nbctl lr-add lr0
> > +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001::1/64
> > +check ovn-nbctl lsp-add sw0 sw0-lr0
> > +check ovn-nbctl lsp-set-type sw0-lr0 router
> > +check ovn-nbctl lsp-set-addresses sw0-lr0 router
> > +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> > +
> > +check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2001::a/64
> > +check ovn-nbctl lsp-add sw1 sw1-lr0
> > +check ovn-nbctl lsp-set-type sw1-lr0 router
> > +check ovn-nbctl lsp-set-addresses sw1-lr0 router
> > +check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
> > +
> > +check ovn-nbctl lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
> > +OVN_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1)
> > +check ovn-nbctl set load_balancer ${OVN_LB_ID} selection_fields="ip_dst,ip_src,tp_dst,tp_src"
> > +#
> > +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
> > +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
> > +
> > +AT_CHECK([ovn-nbctl --wait=sb \
> > +          -- --id=@hc create Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\:80" \
> > +             options:failure_count=100 \
> > +          -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0>
> > +])
> > +
> > +check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> > +check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
> > +check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
> > +
> > +check ovn-nbctl ls-add public
> > +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 2003::1/64
> > +check ovn-nbctl lsp-add public public-lr0
> > +check ovn-nbctl lsp-set-type public-lr0 router
> > +check ovn-nbctl lsp-set-addresses public-lr0 router
> > +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
> > +
> > +# localnet port
> > +check ovn-nbctl lsp-add public ln-public
> > +check ovn-nbctl lsp-set-type ln-public localnet
> > +check ovn-nbctl lsp-set-addresses ln-public unknown
> > +check ovn-nbctl lsp-set-options ln-public network_name=public
> > +
> > +# schedule the gw router port to a chassis. Change the name of the chassis
> > +check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
> > +
> > +OVN_POPULATE_ARP
> > +wait_for_ports_up
> > +check ovn-nbctl --wait=hv sync
> > +
> > +wait_row_count Service_Monitor 2
> > +
> > +AT_CAPTURE_FILE([sbflows])
> > +OVS_WAIT_FOR_OUTPUT(
> > +  [ovn-sbctl dump-flows > sbflows
> > +   ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
> > +  [dnl
> > +  (ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
> > +  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(reg0[[1]] = 0; ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> > +])
> > +
> > +AT_CAPTURE_FILE([sbflows2])
> > +OVS_WAIT_FOR_OUTPUT(
> > +  [ovn-sbctl dump-flows > sbflows2
> > +   ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
> > +  [  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> > +])
> > +
> > +# 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`]
> > +)
> > +
> > +check ovn-nbctl set load_balancer_health_check [[2001::a]]:80 options:failure_count=1
> > +wait_row_count Service_Monitor 2 status=offline
> > +
> > +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`]
> > +)
> > +
> > +AT_CAPTURE_FILE([sbflows3])
> > +ovn-sbctl dump-flows sw0 > sbflows3
> > +AT_CHECK(
> > +  [grep "ip6.dst == 2001::a && tcp.dst == 80" sbflows3 | grep priority=120 |\
> > +   sed 's/table=../table=??/'], [0], [dnl
> > +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(drop;)
> > +])
> > +
> > +AT_CAPTURE_FILE([sbflows4])
> > +ovn-sbctl dump-flows lr0 > sbflows4
> > +AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
> > +  (lr_in_dnat         ), priority=120  , match=(ct.est && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
> > +  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
> > +])
> > +
> > +# Delete sw0-p1
> > +check ovn-nbctl lsp-del sw0-p1
> > +
> > +wait_row_count Service_Monitor 1
> > +
> > +# Add back sw0-p1 but without any IP address.
> > +check ovn-nbctl lsp-add sw0 sw0-p1
> > +check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \
> > +    lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
> > +
> > +wait_row_count Service_Monitor 2 status=offline
> > +
> > +check ovn-nbctl lsp-del sw0-p1
> > +check ovn-nbctl lsp-del sw1-p1
> > +wait_row_count Service_Monitor 0
> > +
> > +# Add back sw0-p1 but without any address set.
> > +check ovn-nbctl lsp-add sw0 sw0-p1
> > +
> > +wait_row_count Service_Monitor 1
> > +wait_row_count Service_Monitor 0 status=offline
> > +wait_row_count Service_Monitor 0 status=online
> > +
> > +OVN_CLEANUP([hv1], [hv2])
> > +AT_CLEANUP
> > +])
> > +
> >  OVN_FOR_EACH_NORTHD([
> >  AT_SETUP([SCTP Load balancer health checks])
> >  AT_KEYWORDS([lb sctp])
> > diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> > index cb3412717..358929522 100644
> > --- a/tests/system-ovn.at
> > +++ b/tests/system-ovn.at
> > @@ -4388,7 +4388,7 @@ AT_CLEANUP
> >  ])
> >
> >  OVN_FOR_EACH_NORTHD([
> > -AT_SETUP([Load balancer health checks])
> > +AT_SETUP([Load balancer health checks - IPv4])
> >  AT_KEYWORDS([lb])
> >  ovn_start
> >
> > @@ -4615,6 +4615,234 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> >  AT_CLEANUP
> >  ])
> >
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Load balancer health checks - IPv6])
> > +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 2001::3"
> > +ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
> > +
> > +ovn-nbctl lsp-add sw0 sw0-p2
> > +ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 2001::4"
> > +ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 2001::4"
> > +
> > +# Create port group and ACLs for sw0 ports.
> > +ovn-nbctl pg-add pg0_drop sw0-p1 sw0-p2
> > +ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
> > +ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
> > +
> > +ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
> > +ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip6" allow-related
> > +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> > +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> > +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> > +
> > +# 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 2002::3"
> > +ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
> > +
> > +# Create port group and ACLs for sw1 ports.
> > +ovn-nbctl pg-add pg1_drop sw1-p1
> > +ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
> > +ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
> > +
> > +ovn-nbctl pg-add pg1 sw1-p1
> > +ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
> > +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
> > +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
> > +ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
> > +
> > +# 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 2001::1/64
> > +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 2002::1/64
> > +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 --reject lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
> > +
> > +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
> > +check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
> > +
> > +ovn-nbctl --wait=sb -- --id=@hc create \
> > +Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\: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, "2001::3/64", "50:54:00:00:00:03", \
> > +         "2001::1")
> > +
> > +ADD_NAMESPACES(sw1-p1)
> > +ADD_VETH(sw1-p1, sw1-p1, br-int, "2002::3/64", "40:54:00:00:00:03", \
> > +         "2002::1")
> > +
> > +ADD_NAMESPACES(sw0-p2)
> > +ADD_VETH(sw0-p2, sw0-p2, br-int, "2001::4/64", "50:54:00:00:00:04", \
> > +         "2001::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], [http6])
> > +sw0_p1_pid_file=$(cat l7_pid_file)
> > +OVS_START_L7([sw1-p1], [http6])
> > +
> > +# 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
> > +     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2001::3\]]:80,[\[2002::3\]]:80)" | wc -l`]
> > +)
> > +
> > +# From sw0-p2 send traffic to vip - 2001::a
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    ovn-sbctl list service_monitor
> > +    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -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(2001::a) | grep -v fe80 | \
> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> > +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2001::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> > +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> > +])
> > +
> > +# Stop webserver 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
> > +     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2002::3\]]:80)" | wc -l`]
> > +)
> > +
> > +ovs-appctl dpctl/flush-conntrack
> > +# From sw0-p2 send traffic to vip - 2001::a
> > +for i in `seq 1 20`; do
> > +    echo Request $i
> > +    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
> > +done
> > +
> > +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(2001::a) | grep -v fe80 | \
> > +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
> > +tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
> > +])
> > +
> > +# trigger port binding release and check if status changed to offline
> > +ovs-vsctl remove interface ovs-sw1-p1 external_ids iface-id
> > +wait_row_count Service_Monitor 2
> > +wait_row_count Service_Monitor 2 status=offline
> > +
> > +ovs-vsctl set interface ovs-sw1-p1 external_ids:iface-id=sw1-p1
> > +wait_row_count Service_Monitor 2
> > +wait_row_count Service_Monitor 1 status=online
> > +
> > +# 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`])
> > +#
> > +## Stop webserver in sw1-p1
> > +#pid_file=$(cat l7_pid_file)
> > +#NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)])
> > +#
> > +#NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &])
> > +#OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
> > +#service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`])
> > +#NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4])
> > +#
> > +#OVS_WAIT_UNTIL([
> > +#    n_reset=$(cat rst.pcap | wc -l)
> > +#    test "${n_reset}" = "1"
> > +#])
> > +
> > +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([NORTHD_TYPE])
> > +
> > +as
> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> > +/connection dropped.*/d
> > +/Service monitor not found.*/d"])
> > +
> > +AT_CLEANUP
> > +])
> > +
> >  OVN_FOR_EACH_NORTHD([
> >  AT_SETUP([Load Balancer LS hairpin IPv4])
> >  AT_SKIP_IF([test $HAVE_NC = no])
> > --
> > 2.38.1
> >
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
>
diff mbox series

Patch

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index f44775c7e..89c2f207b 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -6676,7 +6676,7 @@  sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
         ovs_be32 ip4;
         if (ip_parse(sb_svc_mon->ip, &ip4)) {
             ip_addr = in6_addr_mapped_ipv4(ip4);
-        } else {
+        } else if (!ipv6_parse(sb_svc_mon->ip, &ip_addr)) {
             continue;
         }
 
@@ -6689,16 +6689,27 @@  sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
                 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 (IN6_IS_ADDR_V4MAPPED(&ip_addr)) {
+                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;
+                    }
+                }
+            } else {
+                for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) {
+                    if (!memcmp(&ip_addr, &laddrs.ipv6_addrs[j].addr,
+                        sizeof(ovs_be32[4]))) {
+                        ea = laddrs.ea;
+                        mac_found = true;
+                        break;
+                    }
                 }
             }
 
-            if (!mac_found && !laddrs.n_ipv4_addrs) {
-                /* IPv4 address(es) are not configured. Use the first mac. */
+            if (!mac_found && !laddrs.n_ipv4_addrs && !laddrs.n_ipv6_addrs) {
+                /* IP address(es) are not configured. Use the first mac. */
                 ea = laddrs.ea;
                 mac_found = true;
             }
@@ -6732,7 +6743,7 @@  sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
             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->is_ip6 = !IN6_IS_ADDR_V4MAPPED(&ip_addr);
             svc_mon->state = SVC_MON_S_INIT;
             svc_mon->status = SVC_MON_ST_UNKNOWN;
             svc_mon->protocol = protocol;
@@ -7500,26 +7511,30 @@  svc_monitor_send_tcp_health_check__(struct rconn *swconn,
                                     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;
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
 
     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);
+    if (svc_mon->is_ip6) {
+        struct in6_addr ip6_src;
+        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
+        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
+                             &ip6_src, &svc_mon->ip, IPPROTO_TCP,
+                             63, TCP_HEADER_LEN);
+    } else {
+        ovs_be32 ip4_src;
+        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+        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_l4(&packet);
     dp_packet_set_l4(&packet, th);
+    th->tcp_csum = 0;
     th->tcp_dst = htons(svc_mon->proto_port);
     th->tcp_src = tcp_src;
 
@@ -7530,7 +7545,11 @@  svc_monitor_send_tcp_health_check__(struct rconn *swconn,
     th->tcp_winsz = htons(65160);
 
     uint32_t csum;
-    csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
+    if (svc_mon->is_ip6) {
+        csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+    } else {
+        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)));
@@ -7565,21 +7584,26 @@  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);
+
+    if (svc_mon->is_ip6) {
+        struct in6_addr ip6_src;
+        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
+        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
+                             &ip6_src, &svc_mon->ip, IPPROTO_UDP,
+                             63, UDP_HEADER_LEN + 8);
+    } else {
+        ovs_be32 ip4_src;
+        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+        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_l4(&packet);
     dp_packet_set_l4(&packet, uh);
@@ -7587,6 +7611,16 @@  svc_monitor_send_udp_health_check(struct rconn *swconn,
     uh->udp_src = udp_src;
     uh->udp_len = htons(UDP_HEADER_LEN + 8);
     uh->udp_csum = 0;
+    if (svc_mon->is_ip6) {
+        uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+        csum = csum_continue(csum, uh, dp_packet_size(&packet) -
+                             ((const unsigned char *) uh -
+                              (const unsigned char *) dp_packet_eth(&packet)));
+        uh->udp_csum = csum_finish(csum);
+        if (!uh->udp_csum) {
+            uh->udp_csum = htons(0xffff);
+        }
+    }
 
     uint64_t ofpacts_stub[4096 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
@@ -7649,6 +7683,7 @@  svc_monitors_run(struct rconn *swconn,
         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);
@@ -7779,32 +7814,38 @@  pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
     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);
+    uint8_t ip_proto;
 
-    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;
+    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
+        struct ip_header *in_ip = dp_packet_l3(pkt_in);
+        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;
+        }
+
+        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
+        ip_proto = in_ip->ip_proto;
+    } else {
+        struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
+        ip_addr = ip_flow->ipv6_src;
+        ip_proto = in_ip->ip6_nxt;
     }
 
-    uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
-    if (in_ip_len < IP_HEADER_LEN) {
+    if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_ICMP &&
+        ip_proto != IPPROTO_ICMPV6) {
         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);
+                     "handle service check: Unsupported protocol - [%x]",
+                     ip_proto);
         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) {
+    if (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)));
@@ -7821,44 +7862,68 @@  pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
         }
         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;
-        }
-
+        struct udp_header *orig_uh;
         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) {
+        void *l4h = dp_packet_l4(pkt_in);
+        if (!l4h) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
-                         "ICMP packet");
+            VLOG_WARN_RL(&rl, "ICMP packet with invalid header");
             return;
         }
 
-        if (ntohs(orig_ip_hr->ip_tot_len) !=
-            (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
+        const void *in_ip = dp_packet_get_icmp_payload(pkt_in);
+        if (!in_ip) {
             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");
+            VLOG_WARN_RL(&rl, "Original IP datagram not 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;
+        if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
+            struct icmp_header *ih = l4h;
+            /* It's ICMP packet. */
+            if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
+                return;
+            }
+
+            const struct ip_header *orig_ip_hr = in_ip;
+            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;
+            }
+
+            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;
+            }
+        } else {
+            struct icmp6_header *ih6 = l4h;
+            if (ih6->icmp6_type != 1 || ih6->icmp6_code != 4) {
+                return;
+            }
+
+            const struct ovs_16aligned_ip6_hdr *ip6_hdr = in_ip;
+            if (ntohs(ip6_hdr->ip6_plen) != 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");
+            }
+
+            orig_uh = (struct udp_header *) (ip6_hdr + 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 =
diff --git a/northd/northd.c b/northd/northd.c
index 00ff8f933..68c7655ba 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3751,8 +3751,15 @@  ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
 
             struct ovn_port *op = NULL;
             char *svc_mon_src_ip = NULL;
+
+            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
+            struct ds key = DS_EMPTY_INITIALIZER;
+            ds_put_format(&key, "%s%s%s",
+                          ipv6 ? "[" : "", backend->ip_str,
+                          ipv6 ? "]" : "");
+
             const char *s = smap_get(&lb->nlb->ip_port_mappings,
-                                     backend->ip_str);
+                                     ds_cstr(&key));
             if (s) {
                 char *port_name = xstrdup(s);
                 char *p = strstr(port_name, ":");
@@ -3760,10 +3767,21 @@  ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, struct ovn_northd_lb *lb,
                     *p = 0;
                     p++;
                     op = ovn_port_find(ports, port_name);
+                    if (ipv6) {
+                        p = strstr(p, "[");
+                        if (p) {
+                            p++;
+                        }
+                        char *q = strstr(p, "]");
+                        if (q) {
+                            *q = 0;
+                        }
+                    }
                     svc_mon_src_ip = xstrdup(p);
                 }
                 free(port_name);
             }
+            ds_destroy(&key);
 
             backend_nb->op = op;
             backend_nb->svc_mon_src_ip = svc_mon_src_ip;
@@ -3841,8 +3859,10 @@  build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
             }
 
             n_active_backends++;
-            ds_put_format(action, "%s:%"PRIu16",",
-                          backend->ip_str, backend->port);
+            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip);
+            ds_put_format(action, "%s%s%s:%"PRIu16",",
+                          ipv6 ? "[" : "", backend->ip_str,
+                          ipv6 ? "]" : "", backend->port);
         }
 
         if (!n_active_backends) {
@@ -8435,6 +8455,7 @@  build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
             continue;
         }
 
+        struct ovn_lb_vip *lb_vip = &lb->vips[i];
         for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
             struct ovn_northd_lb_backend *backend_nb =
                 &lb_vip_nb->backends_nb[j];
@@ -8443,22 +8464,42 @@  build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
             }
 
             ds_clear(match);
-            ds_put_format(match, "arp.tpa == %s && arp.op == 1",
-                          backend_nb->svc_mon_src_ip);
             ds_clear(actions);
-            ds_put_format(actions,
-                "eth.dst = eth.src; "
-                "eth.src = %s; "
-                "arp.op = 2; /* ARP reply */ "
-                "arp.tha = arp.sha; "
-                "arp.sha = %s; "
-                "arp.tpa = arp.spa; "
-                "arp.spa = %s; "
-                "outport = inport; "
-                "flags.loopback = 1; "
-                "output;",
-                svc_monitor_mac, svc_monitor_mac,
-                backend_nb->svc_mon_src_ip);
+            if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+                ds_put_format(match, "arp.tpa == %s && arp.op == 1",
+                              backend_nb->svc_mon_src_ip);
+                ds_put_format(actions,
+                    "eth.dst = eth.src; "
+                    "eth.src = %s; "
+                    "arp.op = 2; /* ARP reply */ "
+                    "arp.tha = arp.sha; "
+                    "arp.sha = %s; "
+                    "arp.tpa = arp.spa; "
+                    "arp.spa = %s; "
+                    "outport = inport; "
+                    "flags.loopback = 1; "
+                    "output;",
+                    svc_monitor_mac, svc_monitor_mac,
+                    backend_nb->svc_mon_src_ip);
+            } else {
+                ds_put_format(match, "nd_ns && nd.target == %s",
+                              backend_nb->svc_mon_src_ip);
+                ds_put_format(actions,
+                        "nd_na { "
+                        "eth.dst = eth.src; "
+                        "eth.src = %s; "
+                        "ip6.src = %s; "
+                        "nd.target = %s; "
+                        "nd.tll = %s; "
+                        "outport = inport; "
+                        "flags.loopback = 1; "
+                        "output; "
+                        "};",
+                        svc_monitor_mac,
+                        backend_nb->svc_mon_src_ip,
+                        backend_nb->svc_mon_src_ip,
+                        svc_monitor_mac);
+            }
             ovn_lflow_add_with_hint(lflows,
                                     backend_nb->op->od,
                                     S_SWITCH_IN_ARP_ND_RSP, 110,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 4b712cec4..d9439ea78 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1418,6 +1418,23 @@  output;
           These flows are required if an ARP request is sent for the IP
           <var>SVC_MON_SRC_IP</var>.
         </p>
+
+        <p>
+          For IPv6 the similar flow is added with the following action
+        </p>
+
+        <pre>
+nd_na {
+    eth.dst = eth.src;
+    eth.src = <var>E</var>;
+    ip6.src = <var>A</var>;
+    nd.target = <var>A</var>;
+    nd.tll = <var>E</var>;
+    outport = inport;
+    flags.loopback = 1;
+    output;
+};
+        </pre>
       </li>
 
       <li>
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 0a4340529..e9340131c 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1793,9 +1793,8 @@ 
 
     <group title="Health Checks">
       <p>
-        OVN supports health checks for load balancer endpoints, for IPv4 load
-        balancers only.  When health checks are enabled, the load balancer uses
-        only healthy endpoints.
+        OVN supports health checks for load balancer endpoints. When health
+        checks are enabled, the load balancer uses only healthy endpoints.
       </p>
 
       <p>
@@ -1941,8 +1940,7 @@ 
 
   <table name="Load_Balancer_Health_Check" title="load balancer">
     <p>
-      Each row represents one load balancer health check. Health checks
-      are supported for IPv4 load balancers only.
+      Each row represents one load balancer health check.
     </p>
 
     <column name="vip">
diff --git a/tests/ovn.at b/tests/ovn.at
index dde0f582b..73d7eeaa6 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -23625,7 +23625,7 @@  AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([Load balancer health checks])
+AT_SETUP([Load balancer health checks - IPv4])
 AT_KEYWORDS([lb])
 ovn_start
 
@@ -23823,6 +23823,205 @@  OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - IPv6])
+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
+check 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
+check 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
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+check 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
+
+check ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 2001::3"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
+
+# Create port group and ACLs for sw0 ports.
+check ovn-nbctl pg-add pg0_drop sw0-p1
+check ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
+check ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
+
+# Create the second logical switch with one port
+check ovn-nbctl ls-add sw1
+check ovn-nbctl lsp-add sw1 sw1-p1
+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 2002::3"
+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
+
+# Create port group and ACLs for sw1 ports.
+check ovn-nbctl pg-add pg1_drop sw1-p1
+check ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
+check ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
+
+check ovn-nbctl pg-add pg1 sw1-p1
+check ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
+
+# Create a logical router and attach both logical switches
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001::1/64
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2001::a/64
+check ovn-nbctl lsp-add sw1 sw1-lr0
+check ovn-nbctl lsp-set-type sw1-lr0 router
+check ovn-nbctl lsp-set-addresses sw1-lr0 router
+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+check ovn-nbctl lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
+OVN_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1)
+check ovn-nbctl set load_balancer ${OVN_LB_ID} selection_fields="ip_dst,ip_src,tp_dst,tp_src"
+#
+check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
+check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
+
+AT_CHECK([ovn-nbctl --wait=sb \
+          -- --id=@hc create Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\:80" \
+             options:failure_count=100 \
+          -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0>
+])
+
+check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+
+check ovn-nbctl ls-add public
+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 2003::1/64
+check ovn-nbctl lsp-add public public-lr0
+check ovn-nbctl lsp-set-type public-lr0 router
+check ovn-nbctl lsp-set-addresses public-lr0 router
+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
+
+# localnet port
+check ovn-nbctl lsp-add public ln-public
+check ovn-nbctl lsp-set-type ln-public localnet
+check ovn-nbctl lsp-set-addresses ln-public unknown
+check ovn-nbctl lsp-set-options ln-public network_name=public
+
+# schedule the gw router port to a chassis. Change the name of the chassis
+check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+wait_row_count Service_Monitor 2
+
+AT_CAPTURE_FILE([sbflows])
+OVS_WAIT_FOR_OUTPUT(
+  [ovn-sbctl dump-flows > sbflows
+   ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
+  [dnl
+  (ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(reg0[[1]] = 0; ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+])
+
+AT_CAPTURE_FILE([sbflows2])
+OVS_WAIT_FOR_OUTPUT(
+  [ovn-sbctl dump-flows > sbflows2
+   ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
+  [  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+])
+
+# 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`]
+)
+
+check ovn-nbctl set load_balancer_health_check [[2001::a]]:80 options:failure_count=1
+wait_row_count Service_Monitor 2 status=offline
+
+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`]
+)
+
+AT_CAPTURE_FILE([sbflows3])
+ovn-sbctl dump-flows sw0 > sbflows3
+AT_CHECK(
+  [grep "ip6.dst == 2001::a && tcp.dst == 80" sbflows3 | grep priority=120 |\
+   sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; ct_lb_mark;)
+  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a && tcp.dst == 80), action=(drop;)
+])
+
+AT_CAPTURE_FILE([sbflows4])
+ovn-sbctl dump-flows lr0 > sbflows4
+AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
+  (lr_in_dnat         ), priority=120  , match=(ct.est && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+  (lr_in_dnat         ), priority=120  , match=(ct.new && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
+])
+
+# Delete sw0-p1
+check ovn-nbctl lsp-del sw0-p1
+
+wait_row_count Service_Monitor 1
+
+# Add back sw0-p1 but without any IP address.
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \
+    lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
+
+wait_row_count Service_Monitor 2 status=offline
+
+check ovn-nbctl lsp-del sw0-p1
+check ovn-nbctl lsp-del sw1-p1
+wait_row_count Service_Monitor 0
+
+# Add back sw0-p1 but without any address set.
+check ovn-nbctl lsp-add sw0 sw0-p1
+
+wait_row_count Service_Monitor 1
+wait_row_count Service_Monitor 0 status=offline
+wait_row_count Service_Monitor 0 status=online
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([SCTP Load balancer health checks])
 AT_KEYWORDS([lb sctp])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index cb3412717..358929522 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -4388,7 +4388,7 @@  AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([Load balancer health checks])
+AT_SETUP([Load balancer health checks - IPv4])
 AT_KEYWORDS([lb])
 ovn_start
 
@@ -4615,6 +4615,234 @@  OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - IPv6])
+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 2001::3"
+ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
+
+ovn-nbctl lsp-add sw0 sw0-p2
+ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 2001::4"
+ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 2001::4"
+
+# Create port group and ACLs for sw0 ports.
+ovn-nbctl pg-add pg0_drop sw0-p1 sw0-p2
+ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
+ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
+
+ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
+ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip6" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && icmp6" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
+
+# 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 2002::3"
+ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
+
+# Create port group and ACLs for sw1 ports.
+ovn-nbctl pg-add pg1_drop sw1-p1
+ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
+ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
+
+ovn-nbctl pg-add pg1 sw1-p1
+ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && icmp6" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && tcp && tcp.dst == 80" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 && udp && udp.dst == 80" allow-related
+
+# 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 2001::1/64
+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 2002::1/64
+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 --reject lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
+
+check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
+check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
+
+ovn-nbctl --wait=sb -- --id=@hc create \
+Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\: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, "2001::3/64", "50:54:00:00:00:03", \
+         "2001::1")
+
+ADD_NAMESPACES(sw1-p1)
+ADD_VETH(sw1-p1, sw1-p1, br-int, "2002::3/64", "40:54:00:00:00:03", \
+         "2002::1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "2001::4/64", "50:54:00:00:00:04", \
+         "2001::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], [http6])
+sw0_p1_pid_file=$(cat l7_pid_file)
+OVS_START_L7([sw1-p1], [http6])
+
+# 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
+     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2001::3\]]:80,[\[2002::3\]]:80)" | wc -l`]
+)
+
+# From sw0-p2 send traffic to vip - 2001::a
+for i in `seq 1 20`; do
+    echo Request $i
+    ovn-sbctl list service_monitor
+    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -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(2001::a) | grep -v fe80 | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2001::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+])
+
+# Stop webserver 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_mark | grep priority=120 | grep "ip6.dst == 2001::a" > lflows.txt
+     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2002::3\]]:80)" | wc -l`]
+)
+
+ovs-appctl dpctl/flush-conntrack
+# From sw0-p2 send traffic to vip - 2001::a
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(2001::a) | grep -v fe80 | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+])
+
+# trigger port binding release and check if status changed to offline
+ovs-vsctl remove interface ovs-sw1-p1 external_ids iface-id
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 2 status=offline
+
+ovs-vsctl set interface ovs-sw1-p1 external_ids:iface-id=sw1-p1
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 1 status=online
+
+# 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`])
+#
+## Stop webserver in sw1-p1
+#pid_file=$(cat l7_pid_file)
+#NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)])
+#
+#NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &])
+#OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+#service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`])
+#NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4])
+#
+#OVS_WAIT_UNTIL([
+#    n_reset=$(cat rst.pcap | wc -l)
+#    test "${n_reset}" = "1"
+#])
+
+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([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/Service monitor not found.*/d"])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Load Balancer LS hairpin IPv4])
 AT_SKIP_IF([test $HAVE_NC = no])