Hi Jacob, this looks good to me!

Acked-by: Mark Michelson <[email protected]>

On Mon, Nov 2, 2026 at 8:20 PM Jacob Tanenbaum via dev
<[email protected]> wrote:
>
> An ICMP echo request sent to a logical router port's own IP is
> answered by the in-pipeline echo responder in the logical router's
> IP input stage, and the generated reply re-enters the connected
> logical switch with inport set to the router port.  Router port
> traffic is exempt from conntrack (skip_port_from_conntrack()),
> so the reply arrives at the ACL eval stage untracked, never
> matches the built-in est-allow flow, and is dropped by a stateful
> ACL set that only allows egress traffic.  A VM with such a
> security group therefore cannot ping its router interface.
>
> The exemption exists for transit traffic, where the request and
> the reply are processed on different chassis and per-chassis
> conntrack zones cannot correlate them.  Router-terminated echo
> traffic is different: the reply is generated with
> flags.loopback = 1 in the logical router pipeline, so both the
> request and the reply are processed on the same chassis and the
> router port's zone is local.  Add per-router-port flows at
> priority 111 (above the priority-110 skip flows) in the pre-ACL
> stages that send echo requests destined to the router port's own
> address and echo replies sourced from it through conntrack in
> that zone.  The reply then matches the existing est-allow flow
> and is allowed, and pings to the router can still be blocked by
> dropping the echo request direction with a higher-priority ACL,
> like any other established connection.  Transit traffic is not
> matched and keeps the exemption.
>
> Router ports, however, had no conntrack zone allocated: the
> zone lookups in the physical pipeline (the egress zone load for
> the router port and the logical switch ingress zone load at the
> patch handoff) key off the switch-side patch port name, and
> ovn-controller only allocated zones for bound logical ports and
> per-datapath NAT zones.  Extend ct_zones_update() to also assign
> a zone for the switch-side port of every router port on a local
> logical switch (one additional zone per router port per chassis
> with a local datapath).  The ct_zones incremental processing
> handler cannot track those zones incrementally, so make it fall
> back to a full ct_zones recompute for patch ports instead of
> deleting their zones.
>
> Reported-at: https://redhat.atlassian.net/browse/FDP-4509
> Assisted-by: GLM 5.3, OpenCode
> Signed-off-by: Jacob Tanenbaum <[email protected]>
>
> diff --git a/NEWS b/NEWS
> index 4176f8d7f..11d79c0c2 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,14 @@
>  Post v26.09.0
>  --------------
> +   - ICMP echo replies generated by the logical router are now tracked
> +     by conntrack in the router port's zone, so they are established
> +     reply traffic instead of untracked packets.  Stateful ACLs with
> +     only egress allow-related rules no longer drop a VM's ping to its
> +     router port's IP address.  Pings to the router can still be
> +     blocked by dropping the echo request direction with a
> +     higher-priority ACL.  To support this, ovn-controller now
> +     allocates a conntrack zone for the router port of every local
> +     logical switch (one additional zone per router port per chassis).
>     - Learn ARP sender hardware address (arp.sha) and IPv6 Neighbor
>       Discovery link-layer addresses (nd.tll, nd.sll) into the FDB
>       table in addition to eth.src.  This allows VRRP virtual MACs to
> diff --git a/controller/ct-zone.c b/controller/ct-zone.c
> index a4f94375f..b54011d82 100644
> --- a/controller/ct-zone.c
> +++ b/controller/ct-zone.c
> @@ -210,6 +210,16 @@ ct_zones_update(const struct simap *local_lports,
>          }
>          free(dnat);
>          free(snat);
> +
> +        /* The router ports of a local logical switch need zones assigned
> +         * so that traffic terminated by the connected logical router can
> +         * be tracked in the router port's zone on this chassis. */
> +        if (ld->is_switch) {
> +            const struct peer_ports *peers;
> +            VECTOR_FOR_EACH_PTR (&ld->peer_ports, peers) {
> +                sset_add(&all_users, peers->local->logical_port);
> +            }
> +        }
>      }
>
>      ct_zones_parse_range(ovs_table, &min_ct_zone, &max_ct_zone);
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 7bc3139ab..56c917e64 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -2873,16 +2873,15 @@ ct_zones_runtime_data_handler(struct engine_node 
> *node, void *data)
>                  continue;
>              }
>
> -            /* For patch ports without ACL flag, delete the ct_zone. */
> +            /* The CT zone of a patch port without the ACL flag depends
> +             * on the local datapaths of the connected switches, which is
> +             * not tracked incrementally.  Fall back to a full recompute
> +             * of ct_zones so that zones for router ports are assigned
> +             * and released consistently with ct_zones_update(). */
>              if (!strcmp(t_lport->pb->type, "patch") &&
>                  !smap_get_bool(&t_lport->pb->options,
>                                 "enable_router_port_acl", false)) {
> -                updated |= ct_zone_handle_port_update(&ct_zones_data->ctx,
> -                                                      t_lport->pb,
> -                                                      false, &scan_start,
> -                                                      min_ct_zone,
> -                                                      max_ct_zone);
> -                continue;
> +                return EN_UNHANDLED;
>              }
>              bool port_updated =
>                      t_lport->tracked_type == TRACKED_RESOURCE_NEW ||
> diff --git a/northd/northd.c b/northd/northd.c
> index 3f59894bd..169da9c34 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -6928,6 +6928,75 @@ skip_port_from_conntrack(const struct ovn_datapath 
> *od, struct ovn_port *op,
>      free(egress_match);
>  }
>
> +static void op_put_v4_networks(struct ds *ds, const struct ovn_port *op,
> +                               bool add_bcast);
> +static void op_put_v6_networks(struct ds *ds, const struct ovn_port *op);
> +
> +/* Router-terminated ICMP echo request/reply pairs must be tracked by
> + * conntrack even though other traffic to and from a router port is
> + * exempt from it (see skip_port_from_conntrack()).
> + *
> + * The flows match only echo requests whose destination is the router
> + * port's own address and echo replies whose source is the router
> + * port's own address, so transit traffic through the router port is
> + * not affected.  They run at priority 111, above the priority-110
> + * skip flows installed by skip_port_from_conntrack(). */
> +static void
> +build_rport_echo_conntrack_flows(const struct ovn_datapath *od,
> +                                 struct ovn_port *op,
> +                                 struct lflow_table *lflows,
> +                                 struct lflow_ref *lflow_ref)
> +{
> +    ovs_assert(op->peer && op->peer->nbrp);
> +
> +    struct ds match = DS_EMPTY_INITIALIZER;
> +
> +    if (op->peer->lrp_networks.n_ipv4_addrs) {
> +        ds_put_format(&match, "inport == %s && ip4 && ip4.src == ",
> +                      op->json_key);
> +        op_put_v4_networks(&match, op->peer, false);
> +        ds_put_cstr(&match, " && icmp4.type == 0 && icmp4.code == 0");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 111, ds_cstr(&match),
> +                      REGBIT_CONNTRACK_DEFRAG" = 1; next;", lflow_ref,
> +                      WITH_IO_PORT(op->key),
> +                      WITH_HINT(&op->nbsp->header_));
> +
> +        ds_clear(&match);
> +        ds_put_format(&match, "outport == %s && ip4 && ip4.dst == ",
> +                      op->json_key);
> +        op_put_v4_networks(&match, op->peer, false);
> +        ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
> +        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 111, ds_cstr(&match),
> +                      REGBIT_CONNTRACK_DEFRAG" = 1; next;", lflow_ref,
> +                      WITH_IO_PORT(op->key),
> +                      WITH_HINT(&op->nbsp->header_));
> +    }
> +
> +    if (op->peer->lrp_networks.n_ipv6_addrs) {
> +        ds_clear(&match);
> +        ds_put_format(&match, "inport == %s && ip6 && ip6.src == ",
> +                      op->json_key);
> +        op_put_v6_networks(&match, op->peer);
> +        ds_put_cstr(&match, " && icmp6.type == 129 && icmp6.code == 0");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 111, ds_cstr(&match),
> +                      REGBIT_CONNTRACK_DEFRAG" = 1; next;", lflow_ref,
> +                      WITH_IO_PORT(op->key),
> +                      WITH_HINT(&op->nbsp->header_));
> +
> +        ds_clear(&match);
> +        ds_put_format(&match, "outport == %s && ip6 && ip6.dst == ",
> +                      op->json_key);
> +        op_put_v6_networks(&match, op->peer);
> +        ds_put_cstr(&match, " && icmp6.type == 128 && icmp6.code == 0");
> +        ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 111, ds_cstr(&match),
> +                      REGBIT_CONNTRACK_DEFRAG" = 1; next;", lflow_ref,
> +                      WITH_IO_PORT(op->key),
> +                      WITH_HINT(&op->nbsp->header_));
> +    }
> +
> +    ds_destroy(&match);
> +}
> +
>  /* Skip conntrack for traffic from/to EVPN remote VTEPs.
>   * Remote VTEPs do not have conntrack zones assigned, so
>   * conntrack lookups would return +trk+inv and cause drops. */
> @@ -7042,6 +7111,9 @@ build_ls_stateful_rec_pre_acls(
>              skip_port_from_conntrack(od, op, true,
>                                       S_SWITCH_IN_PRE_ACL, 
> S_SWITCH_OUT_PRE_ACL,
>                                       110, lflows, lflow_ref);
> +            if (op->peer && op->peer->nbrp) {
> +                build_rport_echo_conntrack_flows(od, op, lflows, lflow_ref);
> +            }
>          }
>          VECTOR_FOR_EACH (&od->switch_ports, op) {
>              skip_port_from_conntrack(od, op, true,
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 93af177ab..a8e870ffc 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -13947,12 +13947,16 @@ AT_CHECK([ovn-sbctl dump-flows S1 | grep pre_acl | 
> ovn_strip_lflows], [0], [dnl
>    table=??(ls_in_pre_acl      ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(ip && inport == 
> "S1-R1"), action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip4 && ip4.src == 172.16.1.1 && icmp4.type == 0 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip6 && ip6.src == fe80::ac:10ff:fe01:1 && icmp6.type == 129 && icmp6.code == 
> 0), action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=100  , match=(ip), 
> action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(ip && outport == 
> "S1-R1"), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip4 && ip4.dst == 172.16.1.1 && icmp4.type == 8 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip6 && ip6.dst == fe80::ac:10ff:fe01:1 && icmp6.type == 128 && icmp6.code 
> == 0), action=(reg0[[0]] = 1; next;)
>  ])
>
>  # Enable 'enable_router_port_acl' and check the flows
> @@ -13994,12 +13998,16 @@ AT_CHECK([ovn-sbctl dump-flows S1 | grep pre_acl | 
> ovn_strip_lflows], [0], [dnl
>    table=??(ls_in_pre_acl      ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(ip && inport == 
> "S1-R1"), action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip4 && ip4.src == 172.16.1.1 && icmp4.type == 0 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip6 && ip6.src == fe80::ac:10ff:fe01:1 && icmp6.type == 129 && icmp6.code == 
> 0), action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=100  , match=(ip), 
> action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(ip && outport == 
> "S1-R1"), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip4 && ip4.dst == 172.16.1.1 && icmp4.type == 8 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip6 && ip6.dst == fe80::ac:10ff:fe01:1 && icmp6.type == 128 && icmp6.code 
> == 0), action=(reg0[[0]] = 1; next;)
>  ])
>
>  # Clear the option 'enable_router_port_acl' and check the flows.  Before 
> that enable the option.
> @@ -14027,12 +14035,16 @@ AT_CHECK([ovn-sbctl dump-flows S1 | grep pre_acl | 
> ovn_strip_lflows], [0], [dnl
>    table=??(ls_in_pre_acl      ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(ip && inport == 
> "S1-R1"), action=(next;)
>    table=??(ls_in_pre_acl      ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip4 && ip4.src == 172.16.1.1 && icmp4.type == 0 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "S1-R1" && 
> ip6 && ip6.src == fe80::ac:10ff:fe01:1 && icmp6.type == 129 && icmp6.code == 
> 0), action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=100  , match=(ip), 
> action=(reg0[[0]] = 1; next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.mcast), 
> action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(ip && outport == 
> "S1-R1"), action=(next;)
>    table=??(ls_out_pre_acl     ), priority=110  , match=(nd || nd_rs || nd_ra 
> || mldv1 || mldv2 || (udp && udp.src == 546 && udp.dst == 547)), 
> action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip4 && ip4.dst == 172.16.1.1 && icmp4.type == 8 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "S1-R1" 
> && ip6 && ip6.dst == fe80::ac:10ff:fe01:1 && icmp6.type == 128 && icmp6.code 
> == 0), action=(reg0[[0]] = 1; next;)
>  ])
>
>  OVN_CLEANUP_NORTHD
> @@ -24122,6 +24134,54 @@ AT_CHECK([grep "ls1-to-spine" ls1flows | 
> ovn_strip_lflows | sort], [0], [dnl
>  OVN_CLEANUP_NORTHD
>  AT_CLEANUP
>
> +AT_SETUP([Conntrack flows for router port ICMP echo])
> +ovn_start
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lrp-add lr0 lr0-ls0 00:00:00:00:00:01 10.0.0.1/24 fd00::1/64
> +check ovn-nbctl ls-add ls0
> +check ovn-nbctl lsp-add-router-port ls0 ls0-lr0 lr0-ls0
> +check ovn-nbctl lsp-add ls0 vm1 -- \
> +    lsp-set-addresses vm1 "f0:00:00:00:00:01 10.0.0.10 fd00::10"
> +
> +# Stateful security group with only egress allow-all rules:
> +# egress is allowed, ingress is dropped.
> +check ovn-nbctl acl-add ls0 from-lport 100 "inport == \"vm1\" && ip" 
> allow-related
> +check ovn-nbctl acl-add ls0 to-lport 100 "outport == \"vm1\" && ip" drop
> +
> +check ovn-nbctl --wait=sb sync
> +
> +AT_CHECK([ovn-sbctl dump-flows ls0 > ls0flows])
> +AT_CAPTURE_FILE([ls0flows])
> +
> +AT_CHECK([grep "ls0-lr0" ls0flows | ovn_strip_lflows | sort], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=100  , match=(arp.tpa == 10.0.0.1 
> && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "ls0-lr0"), 
> action=(next;)
> +  table=??(ls_in_arp_rsp      ), priority=100  , match=(nd_ns_mcast && 
> ip6.dst == ff02::1:ff00:1 && nd.target == fd00::1 && inport == "ls0-lr0"), 
> action=(next;)
> +  table=??(ls_in_arp_rsp      ), priority=100  , match=(nd_ns_mcast && 
> ip6.dst == ff02::1:ff00:1 && nd.target == fe80::200:ff:fe00:1 && inport == 
> "ls0-lr0"), action=(next;)
> +  table=??(ls_in_check_port_sec), priority=70   , match=(inport == 
> "ls0-lr0"), action=(reg0[[18]] = 1; next;)
> +  table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 
> 00:00:00:00:00:01), action=(outport = "ls0-lr0"; output;)
> +  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && 
> eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.1), 
> action=(clone {outport = "ls0-lr0"; output; }; outport = "_MC_unknown"; 
> output;)
> +  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && 
> eth.mcast && nd_ns && nd.target == fd00::1), action=(clone {outport = 
> "ls0-lr0"; output; }; outport = "_MC_unknown"; output;)
> +  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && 
> eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:1), action=(clone 
> {outport = "ls0-lr0"; output; }; outport = "_MC_unknown"; output;)
> +  table=??(ls_in_pre_acl      ), priority=110  , match=(ip && inport == 
> "ls0-lr0"), action=(next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "ls0-lr0" 
> && ip4 && ip4.src == 10.0.0.1 && icmp4.type == 0 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_in_pre_acl      ), priority=111  , match=(inport == "ls0-lr0" 
> && ip6 && ip6.src == {fd00::1, fe80::200:ff:fe00:1} && icmp6.type == 129 && 
> icmp6.code == 0), action=(reg0[[0]] = 1; next;)
> +  table=??(ls_in_pre_lb       ), priority=110  , match=(ip && inport == 
> "ls0-lr0"), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=110  , match=(ip && outport == 
> "ls0-lr0"), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "ls0-lr0" 
> && ip4 && ip4.dst == 10.0.0.1 && icmp4.type == 8 && icmp4.code == 0), 
> action=(reg0[[0]] = 1; next;)
> +  table=??(ls_out_pre_acl     ), priority=111  , match=(outport == "ls0-lr0" 
> && ip6 && ip6.dst == {fd00::1, fe80::200:ff:fe00:1} && icmp6.type == 128 && 
> icmp6.code == 0), action=(reg0[[0]] = 1; next;)
> +  table=??(ls_out_pre_lb      ), priority=110  , match=(ip && outport == 
> "ls0-lr0"), action=(next;)
> +])
> +
> +# There must be no special-case allow flows for router echo replies in the
> +# ACL eval tables; the built-in est-allow flow must still be present.
> +AT_CHECK([grep 'ls_in_acl_eval' ls0flows | grep 'ls0-lr0' | grep -q icmp], 
> [1])
> +AT_CHECK([grep 'ls_out_acl_eval' ls0flows | grep 'ls0-lr0' | grep -q icmp], 
> [1])
> +AT_CHECK([grep 'ls_in_acl_eval' ls0flows | grep -q 'ct.est && !ct.rel && 
> ct.rpl && ct_mark.blocked == 0'])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +
>  AT_SETUP([Logical Switch Port Health Check - lflow/service monitor 
> synchronization])
>  ovn_start
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 780149467..4f80ecc1e 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -37204,9 +37204,11 @@ AT_CHECK([as hv1 ovn-appctl -t ovn-controller 
> ct-zone-list | \
>  grep sw0-port2 -c], [0], [1
>  ])
>
> -# There should be no ct-zone id allocated for sw0-lr0 and lr0-sw0
> +# There should be a ct zone for the switch-side patch port sw0-lr0, but
> +# not for the router-side patch port lr0-sw0.
>  AT_CHECK([as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
> -grep sw0-lr0], [1], [])
> +grep sw0-lr0 -c], [0], [1
> +])
>
>  AT_CHECK([as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
>  grep lr0-sw0], [1], [])
> @@ -37225,12 +37227,17 @@ grep sw1-port1 -c], [0], [1
>  # Attach sw1 to lr0
>  check ovn-nbctl lsp-add-router-port sw1 sw1-lr0 lr0-sw1
>
> -# There should be no ct-zone id allocated for sw1-lr0
> +# There is no ct-zone id allocated for the switch-side patch port
> +# sw1-lr0 while its router port does not exist yet.
>  AT_CHECK([as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
>  grep sw1-lr0], [1], [])
>
>  check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
>
> +# There should be a ct zone for the switch-side patch port sw1-lr0
> +OVS_WAIT_UNTIL([test `as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
> +grep sw1-lr0 -c` -eq 1])
> +
>  # There should be no ct-zone id allocated for lr0-sw1
>  AT_CHECK([as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
>  grep lr0-sw1], [1], [])
> @@ -43878,8 +43885,10 @@ check ovn-nbctl --wait=hv sync
>  AS_BOX([With patch ports])
>  zone_list_hv1=$(as hv1 ovn-appctl -t ovn-controller ct-zone-list | sort)
>  AT_CHECK([echo "$zone_list_hv1" | sed "s/ [[0-9]]*/ ??/" | sort], [0], [dnl
> +ls1-r1 ??
>  ls1_dnat ??
>  ls1_snat ??
> +ls2-r1 ??
>  ls2_dnat ??
>  ls2_snat ??
>  lsp1 ??
> @@ -43889,8 +43898,10 @@ r1_snat ??
>
>  zone_list_hv2=$(as hv2 ovn-appctl -t ovn-controller ct-zone-list | sort)
>  AT_CHECK([echo "$zone_list_hv2" | sed "s/ [[0-9]]*/ ??/" | sort], [0], [dnl
> +ls1-r1 ??
>  ls1_dnat ??
>  ls1_snat ??
> +ls2-r1 ??
>  ls2_dnat ??
>  ls2_snat ??
>  lsp2 ??
> @@ -43956,8 +43967,10 @@ AS_BOX([With patch ports again])
>  check ovn-nbctl --wait=hv remove Logical_Router r1 options chassis=hv2
>  zone_list_hv1=$(as hv1 ovn-appctl -t ovn-controller ct-zone-list | sort)
>  AT_CHECK([echo "$zone_list_hv1" | sed "s/ [[0-9]]*/ ??/" | sort], [0], [dnl
> +ls1-r1 ??
>  ls1_dnat ??
>  ls1_snat ??
> +ls2-r1 ??
>  ls2_dnat ??
>  ls2_snat ??
>  lsp1 ??
> @@ -43967,8 +43980,10 @@ r1_snat ??
>
>  zone_list_hv2=$(as hv2 ovn-appctl -t ovn-controller ct-zone-list | sort)
>  AT_CHECK([echo "$zone_list_hv2" | sed "s/ [[0-9]]*/ ??/" | sort], [0], [dnl
> +ls1-r1 ??
>  ls1_dnat ??
>  ls1_snat ??
> +ls2-r1 ??
>  ls2_dnat ??
>  ls2_snat ??
>  lsp2 ??
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 3cb4b858a..b041d6385 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -21242,8 +21242,11 @@ AT_CHECK([grep "ext_lr" stdout], [0], [dnl
>  ext_lr ??
>  ])
>
> -# Check no CT zone for ls_lr.
> -AT_CHECK([grep "ls_lr" stdout], [1], [])
> +# Check that the switch-side patch port ls_lr has a CT zone allocated.
> +AT_CHECK([ovn-appctl -t ovn-controller ct-zone-list | sed "s/ [[0-9]]*/ 
> ??/"], [0], [stdout])
> +AT_CHECK([grep "ls_lr" stdout], [0], [dnl
> +ls_lr ??
> +])
>
>  # Generate ICMP traffic to create conntrack entries.
>  NS_CHECK_EXEC([vm1], [ping -q -c 2 -W 1 172.16.0.100 | FORMAT_PING], \
> @@ -21261,10 +21264,12 @@ 
> icmp,orig=(src=192.168.100.11,dst=172.16.0.100,id=<cleared>,type=8,code=0),reply
>  NETNS_DAEMONIZE([external], [ncat -l -k 172.16.0.100 80], [external.pid])
>  NS_CHECK_EXEC([vm1], [ncat -z 172.16.0.100 80], [1])
>
> -# Remove enable_router_port_acl and verify CT zone release.
> +# Remove enable_router_port_acl.  The CT zone for ext_lr is kept: with
> +# router echo traffic tracked in the router port's zone, switch-side
> +# patch ports of local logical switches always have a CT zone.
>  check ovn-nbctl lsp-set-options ext_lr router-port=lr_ext
>  check ovn-nbctl --wait=sb sync
> -AT_CHECK([ovn-appctl -t ovn-controller ct-zone-list | grep "ext_lr"], [1], 
> [])
> +AT_CHECK([ovn-appctl -t ovn-controller ct-zone-list | grep "ext_lr"], [0], 
> [ignore])
>
>  # Re-enable enable_router_port_acl and verify CT zone reallocation.
>  check ovn-nbctl lsp-set-options ext_lr router-port=lr_ext 
> enable_router_port_acl=true
> @@ -24110,3 +24115,80 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
> patch-.*/d
>  /connection dropped.*/d"])
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ping to router port with stateful ACL])
> +AT_KEYWORDS([acl])
> +
> +CHECK_CONNTRACK()
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +
> +check 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_daemon ovn-controller
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lrp-add lr0 lr0-ls0 00:00:00:00:00:01 10.0.0.1/24 fd00::1/64
> +check ovn-nbctl ls-add ls0
> +check ovn-nbctl lsp-add-router-port ls0 ls0-lr0 lr0-ls0
> +check ovn-nbctl lsp-add ls0 vm1
> +check ovn-nbctl lsp-set-addresses vm1 "f0:00:00:00:00:01 10.0.0.10 fd00::10"
> +
> +dnl Stateful security group with only egress allow-all rules:
> +dnl egress is allowed, ingress is dropped.
> +check ovn-nbctl acl-add ls0 from-lport 100 "inport == \"vm1\" && ip" 
> allow-related
> +check ovn-nbctl acl-add ls0 to-lport 100 "outport == \"vm1\" && ip" drop
> +
> +ADD_NAMESPACES(vm1)
> +ADD_VETH(vm1, vm1, br-int, "10.0.0.10/24", "f0:00:00:00:00:01", "10.0.0.1")
> +NS_CHECK_EXEC([vm1], [ip -6 addr add fd00::10/64 dev vm1 nodad])
> +
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +# The router port must have a conntrack zone allocated on this chassis:
> +# the router echo request and reply are tracked in that zone.
> +OVS_WAIT_UNTIL([ovn-appctl -t ovn-controller ct-zone-list | grep -q ls0-lr0])
> +
> +dnl The router echo reply is conntrack-established in the router port's
> +dnl zone, so the stateful SG's built-in est-allow lets it through even
> +dnl though ingress has no allow rule.
> +NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.1 | FORMAT_PING], [0], 
> [dnl
> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
> +])
> +
> +NS_CHECK_EXEC([vm1], [ping -6 -q -c 3 -i 0.3 -w 2 fd00::1 | FORMAT_PING], 
> [0], [dnl
> +3 packets transmitted, 3 received, 0% packet loss, time 0ms
> +])
> +
> +dnl A higher-priority ACL that drops the echo request direction must
> +dnl block pings to the router, like any other established connection.
> +check ovn-nbctl --wait=hv acl-add ls0 from-lport 101 "inport == \"vm1\" && 
> ip4 && icmp4.type == 8 && ip4.dst == 10.0.0.1" drop
> +
> +NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.1 | FORMAT_PING], [0], 
> [dnl
> +7 packets transmitted, 0 received, 100% packet loss, time 0ms
> +])
> +
> +check ovn-nbctl --wait=hv acl-add ls0 from-lport 101 "inport == \"vm1\" && 
> ip6 && icmp6.type == 128 && ip6.dst == fd00::1" drop
> +
> +NS_CHECK_EXEC([vm1], [ping -6 -q -c 3 -i 0.3 -w 2 fd00::1 | FORMAT_PING], 
> [0], [dnl
> +7 packets transmitted, 0 received, 100% packet loss, time 0ms
> +])
> +
> +OVN_CLEANUP_CONTROLLER([hv1])
> +
> +OVN_CLEANUP_NORTHD
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> +/connection dropped.*/d"])
> +
> +AT_CLEANUP
> +])
> --
> 2.56.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

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

Reply via email to