build_lb_health_check_response_lflows() builds a per-backend
service_monitor reply punt lflow with the match:

    inport == "<backend_lsp>" && ip4.dst == <svc_mon_src_ip> &&
    ip4.src == <backend_ip>   && eth.dst == <svc_mon_lrp_mac> &&
    tcp.src == <backend_port>

When the backend's LSP is type=external on a Logical_Switch that has
a localnet port (the typical configuration produced by Neutron /
ovn-octavia-provider for baremetal pool members on a provider VLAN),
the response packet re-enters br-int through the localnet LSP, so
MFF_LOG_INPORT at S_SWITCH_IN_L2_LKUP is set to the localnet LSP's
tunnel_key, not the backend LSP's.  The existing inport == "..."
clause therefore never matches, the response is never punted to
ovn-controller via ACTION_OPCODE_HANDLE_SVC_CHECK, and the
Service_Monitor.status of those backends stays "offline"
indefinitely -- even though the backend physically replies on the
wire.

Fix this by emitting an additional reply punt lflow on every
peer_switch_od that has a localnet port AND a type=external backend
LSP.  The new lflow drops the inport == "..." constraint, replaces
it with eth.src == <backend_mac>, and uses

    outport = "<backend_lsp>";
    handle_svc_check(outport);
    outport = "";

so MFF_LOG_INPORT is loaded with the backend LSP's tunnel_key for
the controller dispatch (handle_svc_check(outport) borrows reg15
into reg14 via push/pop), and outport is then cleared to prevent
the cleared MFF_LOG_OUTPORT from leaking into egress and reflecting
the reply back to the backend.

The patch does not introduce or change any OVN action, so
OVN_INTERNAL_MINOR_VER is not bumped.

Signed-off-by: JayGue Lee <[email protected]>
---
v2:
- Fixed SIGSEGV reported by the ovsrobot CI on v1.  v1 used
  ovn_port_find(&peer_switch_od->ports, ...) to look up the backend
  ovn_port by its LSP name.  ovn_port_find() walks the hmap with
  HMAP_FOR_EACH_WITH_HASH using the 'key_node' hmap_node, but
  ovn_datapath::ports is keyed on 'dp_node' (per ovn_port_alloc()
  paths in northd.c that hmap_insert() with &op->dp_node), so the
  iteration container_of()'s a wrong field offset and produces a
  garbage struct ovn_port * that crashes on the first deref.  Switch
  to an inline HMAP_FOR_EACH (op, dp_node, &peer_switch_od->ports)
  loop matching by op->key, which is the established pattern used
  elsewhere in northd.c (e.g. ovn_port_find_port_or_child()) for
  per-datapath port lookups.
- No functional change in the lflow that is generated; only the way
  the corresponding ovn_port is located.

 NEWS                |  7 ++++
 northd/northd.c     | 83 +++++++++++++++++++++++++++++++++++++++++++
 tests/ovn-northd.at | 86 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 176 insertions(+)

diff --git a/NEWS b/NEWS
index 8633ba8..b2a956c 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,13 @@ Post v26.03.0
    - Dynamic Routing:
      * Add support for hub-and-spoke propagation via the "hub-spoke" option
        in dynamic-routing-redistribute settings.
+   - Fixed Load_Balancer health checks for backends referenced via a
+     Logical_Switch_Port whose type is "external" on a Logical_Switch that
+     also has a localnet port (typical Neutron / ovn-octavia-provider
+     baremetal pool member configuration).  Previously such backends
+     remained permanently offline in Service_Monitor.status because the
+     per-backend reply punt lflow's "inport == <backend_lsp>" match did
+     not fit traffic that re-enters br-int through the localnet port.
 
 OVN v26.03.0 - xxx xx xxxx
 --------------------------
diff --git a/northd/northd.c b/northd/northd.c
index 0b52db6..879bfd1 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -8962,6 +8962,89 @@ build_lb_health_check_response_lflows(
             ovn_lflow_add(lflows, peer_switch_od, S_SWITCH_IN_L2_LKUP, 110,
                           ds_cstr(match), "handle_svc_check(inport);",
                           lb_dps->lflow_ref, WITH_CTRL_METER(meter));
+
+            /* The lflow above only matches replies whose ingress logical
+             * inport is the backend LSP itself.  This holds for backends
+             * behind tunnels or local OVS ports.  For backends whose LSP
+             * is type=external on a Logical_Switch that has a localnet
+             * port (typical for Neutron / ovn-octavia-provider baremetal
+             * pool members on a provider VLAN), the reply re-enters
+             * br-int through the localnet port; MFF_LOG_INPORT therefore
+             * holds the localnet LSP's tunnel_key and the lflow above
+             * never matches.  pinctrl_handle_svc_check() then fails its
+             * (dp_key, port_key) lookup and Service_Monitor.status stays
+             * 'offline' indefinitely, even though the backend is fully
+             * reachable on the wire.
+             *
+             * Emit an additional reply punt lflow that:
+             *  - identifies the backend by eth.src instead of inport,
+             *  - loads the backend LSP's tunnel_key into MFF_LOG_INPORT
+             *    via 'outport = "<lsp>"; handle_svc_check(outport);'
+             *    (the latter borrows MFF_LOG_OUTPORT's value into
+             *    MFF_LOG_INPORT for the controller dispatch),
+             *  - resets outport to "" so the cleared MFF_LOG_OUTPORT
+             *    does not leak into egress and reflect the reply back
+             *    to the backend.
+             */
+            if (vector_is_empty(&peer_switch_od->localnet_ports)) {
+                continue;
+            }
+            /* peer_switch_od->ports is an hmap keyed by 'dp_node' (not the
+             * 'key_node' that ovn_port_find() expects), so iterate it with
+             * HMAP_FOR_EACH directly to find the backend LSP by name. */
+            struct ovn_port *backend_op = NULL;
+            struct ovn_port *op;
+            HMAP_FOR_EACH (op, dp_node, &peer_switch_od->ports) {
+                if (op->nbsp && op->key
+                    && !strcmp(op->key, backend_nb->logical_port)) {
+                    backend_op = op;
+                    break;
+                }
+            }
+            if (!backend_op || !backend_op->nbsp || !backend_op->nbsp->type
+                || strcmp(backend_op->nbsp->type, "external")
+                || !backend_op->n_lsp_addrs) {
+                continue;
+            }
+
+            ds_clear(match);
+            ds_clear(action);
+
+            const char *bm_mac = backend_op->lsp_addrs[0].ea_s;
+            if (addr_is_ipv6(backend_nb->svc_mon_src_ip)) {
+                ds_put_format(match, "eth.src == %s && ip6.dst == %s && "
+                              "ip6.src == %s && eth.dst == %s && ",
+                              bm_mac,
+                              backend_nb->svc_mon_src_ip,
+                              backend->ip_str,
+                              backend_nb->svc_mon_lrp->lrp_networks.ea_s);
+                if (!strcmp(protocol, "tcp")) {
+                    ds_put_format(match, "tcp.src == %s",
+                                  backend->port_str);
+                } else {
+                    ds_put_cstr(match, "icmp6.type == 1");
+                }
+            } else {
+                ds_put_format(match, "eth.src == %s && ip4.dst == %s && "
+                              "ip4.src == %s && eth.dst == %s && ",
+                              bm_mac,
+                              backend_nb->svc_mon_src_ip,
+                              backend->ip_str,
+                              backend_nb->svc_mon_lrp->lrp_networks.ea_s);
+                if (!strcmp(protocol, "tcp")) {
+                    ds_put_format(match, "tcp.src == %s",
+                                  backend->port_str);
+                } else {
+                    ds_put_cstr(match, "icmp4.type == 3");
+                }
+            }
+            ds_put_format(action,
+                          "outport = \"%s\"; handle_svc_check(outport); "
+                          "outport = \"\";",
+                          backend_nb->logical_port);
+            ovn_lflow_add(lflows, peer_switch_od, S_SWITCH_IN_L2_LKUP, 110,
+                          ds_cstr(match), ds_cstr(action),
+                          lb_dps->lflow_ref, WITH_CTRL_METER(meter));
         }
     }
 }
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 1d7bd6c..c95ce39 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -20672,3 +20672,89 @@ check_column "$global_svc_mon_mac" sb:Service_Monitor 
src_mac port=2
 OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION([
+AT_SETUP([Load balancer health checks - external LSP backend on localnet LS])
+ovn_start
+
+dnl Topology:
+dnl   lr0  --  ls-tenant (vm-port: type="", 10.0.0.10)
+dnl     \---  ls-prov   (bm-port: type=external, 192.168.0.10
+dnl                       MAC 02:bb:bb:00:00:01;
+dnl                      provnet-physnet1: type=localnet)
+dnl
+dnl LB lb0 attached to lr0:
+dnl   vip 10.10.10.10:80 -> 10.0.0.10:80,192.168.0.10:80 (tcp)
+dnl   ip_port_mappings: 10.0.0.10  = vm-port:10.0.0.1
+dnl                     192.168.0.10 = bm-port:192.168.0.1
+dnl
+dnl Expectation:
+dnl   1) On ls-tenant the existing per-backend reply lflow is emitted
+dnl      with `inport == "vm-port"`.
+dnl   2) On ls-prov  the existing per-backend reply lflow is emitted
+dnl      with `inport == "bm-port"` AND, additionally, the new lflow
+dnl      added by this commit is emitted with `eth.src == <bm-mac>`
+dnl      and action `outport = "bm-port"; handle_svc_check(outport);
+dnl      outport = "";`.
+
+check ovn-nbctl                                                 \
+    -- lr-add lr0                                               \
+      -- lrp-add lr0 lr0-tenant 02:00:00:00:01:01 10.0.0.1/24   \
+      -- lrp-add lr0 lr0-prov   02:00:00:00:02:01 192.168.0.1/24
+
+check ovn-nbctl                                                 \
+    -- ls-add ls-tenant                                         \
+      -- lsp-add ls-tenant ls-tenant-lr0                        \
+      -- lsp-set-type      ls-tenant-lr0 router                 \
+      -- lsp-set-options   ls-tenant-lr0 router-port=lr0-tenant \
+      -- lsp-set-addresses ls-tenant-lr0 router                 \
+      -- lsp-add ls-tenant vm-port                              \
+      -- lsp-set-addresses vm-port "02:aa:aa:00:00:01 10.0.0.10"
+
+check ovn-nbctl                                                 \
+    -- ls-add ls-prov                                           \
+      -- lsp-add ls-prov ls-prov-lr0                            \
+      -- lsp-set-type      ls-prov-lr0 router                   \
+      -- lsp-set-options   ls-prov-lr0 router-port=lr0-prov     \
+      -- lsp-set-addresses ls-prov-lr0 router                   \
+      -- lsp-add ls-prov bm-port                                \
+      -- lsp-set-type      bm-port external                     \
+      -- lsp-set-addresses bm-port "02:bb:bb:00:00:01 192.168.0.10" \
+      -- lsp-add ls-prov provnet-physnet1                       \
+      -- lsp-set-type      provnet-physnet1 localnet            \
+      -- lsp-set-addresses provnet-physnet1 unknown             \
+      -- lsp-set-options   provnet-physnet1 network_name=physnet1
+
+check ovn-nbctl --wait=sb lb-add lb0 \
+    10.10.10.10:80 10.0.0.10:80,192.168.0.10:80 tcp
+lb0=$(fetch_column nb:Load_Balancer _uuid name=lb0)
+check ovn-nbctl                                                          \
+    -- set load_balancer $lb0 ip_port_mappings:10.0.0.10=vm-port:10.0.0.1 \
+    -- set load_balancer $lb0 ip_port_mappings:192.168.0.10=bm-port:192.168.0.1
+
+check_uuid ovn-nbctl --id=@hc create Load_Balancer_Health_Check    \
+    vip="10.10.10.10\:80" -- add Load_Balancer $lb0 health_check @hc
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb0
+
+dnl Existing per-backend reply lflow on the tenant (VM) datapath.
+AT_CHECK([ovn-sbctl lflow-list ls-tenant | grep handle_svc_check | 
ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == 
$svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+  table=??(ls_in_l2_lkup      ), priority=110  , match=(inport == "vm-port" && 
ip4.dst == 10.0.0.1 && ip4.src == 10.0.0.10 && eth.dst == 02:00:00:00:01:01 && 
tcp.src == 80), action=(handle_svc_check(inport);)
+])
+
+dnl On the provider datapath, BOTH the existing per-backend reply lflow
+dnl AND the new external-LSP reply lflow added by this commit must be
+dnl present.  The new lflow uses eth.src to identify the backend (so it
+dnl matches replies that re-enter via the localnet port) and forces
+dnl MFF_LOG_INPORT to the backend LSP's tunnel_key by going through
+dnl outport, then resets outport to "" to avoid reflecting the reply
+dnl back to the backend.
+AT_CHECK([ovn-sbctl lflow-list ls-prov | grep handle_svc_check | 
ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == 
$svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.src == 
02:bb:bb:00:00:01 && ip4.dst == 192.168.0.1 && ip4.src == 192.168.0.10 && 
eth.dst == 02:00:00:00:02:01 && tcp.src == 80), action=(outport = "bm-port"; 
handle_svc_check(outport); outport = "";)
+  table=??(ls_in_l2_lkup      ), priority=110  , match=(inport == "bm-port" && 
ip4.dst == 192.168.0.1 && ip4.src == 192.168.0.10 && eth.dst == 
02:00:00:00:02:01 && tcp.src == 80), action=(handle_svc_check(inport);)
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
-- 
2.49.0

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

Reply via email to