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
