On Tue, Sep 21, 2021 at 6:50 AM Dumitru Ceara <dce...@redhat.com> wrote: > > IP multicast relay didn't work properly if traffic had to be forwarded > across a distributed gateway port in the router pipeline. That is > because the multicast_group used as output logical port is expanded in > the egress pipeline, way after 'lr_in_gw_redirect' where unicast traffic > would normally be forwarded to the chassis-redirect port. > > In order to achieve the same behavior for IP multicast routed traffic we > now store the chassis-redirect port binding in the multicast_group on > which IP multicast is routed. On the remote hypervisor, to make sure > traffic is delivered to the correct destination switch pipeline, we make > sure that ovn-controller translates chassis-redirect ports from > multicast groups to the logical patch ports they were created from. > > This patch also adds a test to simulate the ovn-kubernetes IP multicast > use case (where this issue was first observed). > > Fixes: 5d1527b11e94 ("ovn-northd: Add IGMP Relay support") > Reported-by: Alexander Constantinescu <acons...@redhat.com> > Reported-at: https://bugzilla.redhat.com/2006306 > Signed-off-by: Dumitru Ceara <dce...@redhat.com> > --- > controller/physical.c | 28 ++++- > northd/lrouter.dl | 14 ++- > northd/multicast.dl | 23 +++- > northd/northd.c | 7 +- > northd/ovn_northd.dl | 12 +- > tests/ovn.at | 248 ++++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 314 insertions(+), 18 deletions(-) > > diff --git a/controller/physical.c b/controller/physical.c > index ffb9f9952..0cfb158c8 100644 > --- a/controller/physical.c > +++ b/controller/physical.c > @@ -1373,7 +1373,8 @@ out: > } > > static void > -consider_mc_group(enum mf_field_id mff_ovn_geneve, > +consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name, > + enum mf_field_id mff_ovn_geneve, > const struct simap *ct_zones, > const struct hmap *local_datapaths, > struct shash *local_bindings, > @@ -1406,6 +1407,10 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve, > * instead. (If we put them in 'ofpacts', then the output > * would happen on every hypervisor in the multicast group, > * effectively duplicating the packet.) > + * > + * - For chassisredirect ports, add actions to 'ofpacts' to > + * set the output port to be the router patch port for which > + * the redirect port was added. > */ > struct ofpbuf ofpacts; > ofpbuf_init(&ofpacts, 0); > @@ -1440,6 +1445,21 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve, > && port->chassis == chassis)) { > put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts); > put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts); > + } else if (!strcmp(port->type, "chassisredirect") > + && port->chassis == chassis) { > + const char *distributed_port = smap_get(&port->options, > + "distributed-port"); > + if (distributed_port) { > + const struct sbrec_port_binding *distributed_binding > + = lport_lookup_by_name(sbrec_port_binding_by_name, > + distributed_port); > + if (distributed_binding > + && port->datapath == distributed_binding->datapath) { > + put_load(distributed_binding->tunnel_key, MFF_LOG_OUTPORT, > + 0, 32, &ofpacts); > + put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts); > + } > + } > } else if (port->chassis && !get_localnet_port( > local_datapaths, mc->datapath->tunnel_key)) { > /* Add remote chassis only when localnet port not exist, > @@ -1574,7 +1594,8 @@ physical_handle_mc_group_changes(struct physical_ctx *p_ctx, > if (!sbrec_multicast_group_is_new(mc)) { > ofctrl_remove_flows(flow_table, &mc->header_.uuid); > } > - consider_mc_group(p_ctx->mff_ovn_geneve, p_ctx->ct_zones, > + consider_mc_group(p_ctx->sbrec_port_binding_by_name, > + p_ctx->mff_ovn_geneve, p_ctx->ct_zones, > p_ctx->local_datapaths, p_ctx->local_bindings, > p_ctx->patch_ofports, > p_ctx->chassis, mc, > @@ -1617,7 +1638,8 @@ physical_run(struct physical_ctx *p_ctx, > /* Handle output to multicast groups, in tables 32 and 33. */ > const struct sbrec_multicast_group *mc; > SBREC_MULTICAST_GROUP_TABLE_FOR_EACH (mc, p_ctx->mc_group_table) { > - consider_mc_group(p_ctx->mff_ovn_geneve, p_ctx->ct_zones, > + consider_mc_group(p_ctx->sbrec_port_binding_by_name, > + p_ctx->mff_ovn_geneve, p_ctx->ct_zones, > p_ctx->local_datapaths, p_ctx->local_bindings, > p_ctx->patch_ofports, p_ctx->chassis, > mc, p_ctx->chassis_tunnels, > diff --git a/northd/lrouter.dl b/northd/lrouter.dl > index 3029ba67d..0e4308eb5 100644 > --- a/northd/lrouter.dl > +++ b/northd/lrouter.dl > @@ -145,8 +145,10 @@ Warning[message] :- > * > * A logical router can have multiple distributed gateway ports. */ > relation DistributedGatewayPort(lrp: Intern<nb::Logical_Router_Port>, > - lr_uuid: uuid) > -DistributedGatewayPort(lrp, lr_uuid) :- > + lr_uuid: uuid, cr_lrp_uuid: uuid) > + > +// lrp._uuid is already in use; generate a new UUID by hashing it. > +DistributedGatewayPort(lrp, lr_uuid, hash128(lrp_uuid)) :- > lr in nb::Logical_Router(._uuid = lr_uuid), > LogicalRouterPort(lrp_uuid, lr._uuid), > lrp in &nb::Logical_Router_Port(._uuid = lrp_uuid), > @@ -237,10 +239,10 @@ DistributedGatewayPortHAChassisGroup(lrp, > > /* For each router port, tracks whether it's a redirect port of its router */ > relation RouterPortIsRedirect(lrp: uuid, is_redirect: bool) > -RouterPortIsRedirect(lrp, true) :- DistributedGatewayPort(&nb::Logical_Router_Port{._uuid = lrp}, _). > +RouterPortIsRedirect(lrp, true) :- DistributedGatewayPort(&nb::Logical_Router_Port{._uuid = lrp}, _, _). > RouterPortIsRedirect(lrp, false) :- > &nb::Logical_Router_Port(._uuid = lrp), > - not DistributedGatewayPort(&nb::Logical_Router_Port{._uuid = lrp}, _). > + not DistributedGatewayPort(&nb::Logical_Router_Port{._uuid = lrp}, _, _). > > /* > * LogicalRouterDGWPorts maps from each logical router UUID > @@ -249,12 +251,12 @@ relation LogicalRouterDGWPorts( > lr_uuid: uuid, > l3dgw_ports: Vec<Intern<nb::Logical_Router_Port>>) > LogicalRouterDGWPorts(lr_uuid, l3dgw_ports) :- > - DistributedGatewayPort(lrp, lr_uuid), > + DistributedGatewayPort(lrp, lr_uuid, _), > var l3dgw_ports = lrp.group_by(lr_uuid).to_vec(). > LogicalRouterDGWPorts(lr_uuid, vec_empty()) :- > lr in nb::Logical_Router(), > var lr_uuid = lr._uuid, > - not DistributedGatewayPort(_, lr_uuid). > + not DistributedGatewayPort(_, lr_uuid, _). > > typedef ExceptionalExtIps = AllowedExtIps{ips: Intern<nb::Address_Set>} > | ExemptedExtIps{ips: Intern<nb::Address_Set>} > diff --git a/northd/multicast.dl b/northd/multicast.dl > index 074caf654..56bfa0c63 100644 > --- a/northd/multicast.dl > +++ b/northd/multicast.dl > @@ -236,7 +236,28 @@ IgmpRouterGroupPort(address, rtr_port.router, rtr_port.lrp._uuid) :- > }, > var flood_port = FlatMap(sw_flood_ports), > &SwitchPort(.lsp = &nb::Logical_Switch_Port{._uuid = flood_port}, > - .peer = Some{rtr_port}). > + .peer = Some{rtr_port}), > + RouterPortIsRedirect(rtr_port.lrp._uuid, false). > + > +/* Store the chassis redirect port uuid for redirect ports, otherwise traffic > + * will not be tunneled properly. This will be translated back to the patch > + * port on the remote hypervisor. > + */ > +IgmpRouterGroupPort(address, rtr_port.router, cr_lrp_uuid) :- > + SwitchMcastFloodRelayPorts(switch, sw_flood_ports), > + IgmpSwitchMulticastGroup(address, switch, _), > + /* For IPv6 only relay routable multicast groups > + * (RFC 4291 2.7). > + */ > + match (ipv6_parse(address.ival())) { > + Some{ipv6} -> ipv6.is_routable_multicast(), > + None -> true > + }, > + var flood_port = FlatMap(sw_flood_ports), > + &SwitchPort(.lsp = &nb::Logical_Switch_Port{._uuid = flood_port}, > + .peer = Some{rtr_port}), > + RouterPortIsRedirect(rtr_port.lrp._uuid, true), > + DistributedGatewayPort(rtr_port.lrp, _, cr_lrp_uuid). > > /* Aggregated IGMP group for routers: merges all IgmpRouterGroupPort for > * a given address-router tuple from all connected switches. > diff --git a/northd/northd.c b/northd/northd.c > index d1b87891c..e021e146b 100644 > --- a/northd/northd.c > +++ b/northd/northd.c > @@ -14169,7 +14169,12 @@ build_mcast_groups(struct northd_context *ctx, > address, igmp_group->mcgroup.name ); > struct ovn_port **router_igmp_ports = > xmalloc(sizeof *router_igmp_ports); > - router_igmp_ports[0] = router_port; > + /* Store the chassis redirect port otherwise traffic will not > + * be tunneled properly. > + */ > + router_igmp_ports[0] = router_port->cr_port > + ? router_port->cr_port > + : router_port; > ovn_igmp_group_add_entry(igmp_group_rtr, router_igmp_ports, 1); > } > } > diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl > index 0202af5dc..22913f05a 100644 > --- a/northd/ovn_northd.dl > +++ b/northd/ovn_northd.dl > @@ -464,9 +464,7 @@ function has_distributed_nat(nats: Vec<NAT>): bool { > */ > > /* Create derived ports */ > -OutProxy_Port_Binding(// lrp._uuid is already in use; generate a new UUID by > - // hashing it. > - ._uuid = hash128(lrp._uuid), > +OutProxy_Port_Binding(._uuid = cr_lrp_uuid, > .logical_port = chassis_redirect_name( lrp.name).intern(), > .__type = i"chassisredirect", > .gateway_chassis = set_empty(), > @@ -478,7 +476,7 @@ OutProxy_Port_Binding(// lrp._uuid is already in use; generate a new UUID by > .mac = set_singleton(i"${lrp.mac} ${lrp.networks.map(ival).to_vec().join(\" \")}"), > .nat_addresses = set_empty(), > .external_ids = lrp.external_ids) :- > - DistributedGatewayPort(lrp, lr_uuid), > + DistributedGatewayPort(lrp, lr_uuid, cr_lrp_uuid), > DistributedGatewayPortHAChassisGroup(lrp, hacg_uuid), > var redirect_type = match (lrp.options.get(i"redirect-type")) { > Some{var value} -> [i"redirect-type" -> value], > @@ -549,7 +547,7 @@ RefChassis(lr_uuid, chassis_uuid) :- > HAChassis(.hacg_uuid = hacg_uuid, .hac_uuid = hac_uuid), > var hacg_size = hac_uuid.group_by(hacg_uuid).to_set().size(), > hacg_size > 1, > - DistributedGatewayPort(lrp, lr_uuid), > + DistributedGatewayPort(lrp, lr_uuid, _), > ConnectedLogicalRouter[(lr_uuid, set_uuid)], > ConnectedLogicalRouter[(lr2_uuid, set_uuid)], > FirstHopLogicalRouter(lr2_uuid, ls_uuid), > @@ -577,7 +575,7 @@ relation HAChassisGroupRefChassisSet(hacg_uuid: uuid, > chassis_uuids: Set<uuid>) > HAChassisGroupRefChassisSet(hacg_uuid, chassis_uuids) :- > DistributedGatewayPortHAChassisGroup(lrp, hacg_uuid), > - DistributedGatewayPort(lrp, lr_uuid), > + DistributedGatewayPort(lrp, lr_uuid, _), > RefChassisSet(lr_uuid, chassis_uuids), > var chassis_uuids = chassis_uuids.group_by(hacg_uuid).union(). > > @@ -8198,7 +8196,7 @@ for (&Router(._uuid = lr_uuid)) > * packet did not match any higher priority redirect > * rule, then the traffic is redirected to the central > * instance of the l3dgw_port. */ > - for (DistributedGatewayPort(lrp, lr_uuid)) { > + for (DistributedGatewayPort(lrp, lr_uuid, _)) { > Flow(.logical_datapath = lr_uuid, > .stage = s_ROUTER_IN_GW_REDIRECT(), > .priority = 50, > diff --git a/tests/ovn.at b/tests/ovn.at > index d76f4ba86..172b5c713 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -19247,6 +19247,254 @@ OVN_CLEANUP([hv1], [hv2], [hv3]) > AT_CLEANUP > ]) > > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([IGMP relay - distributed gateway port]) > +AT_KEYWORDS([snoop relay]) > + > +ovn_start > + > +# Logical network: > +# Two logical switches (sw1-sw2) connected to a logical router (rtr). > +# sw1: > +# - subnet 10.0.0.0/8 > +# - 1 ports bound on hv1 (sw1-p1) > +# sw2: > +# - subnet 20.0.0.0/8 > +# - 1 port bound on hv2 (sw2-p2) > +# - IGMP Querier from 20.0.0.254 > + > +reset_pcap_file() { > + local iface=$1 > + local pcap_file=$2 > + check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ > +options:rxq_pcap=dummy-rx.pcap > + rm -f ${pcap_file}*.pcap > + check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ > +options:rxq_pcap=${pcap_file}-rx.pcap > +} > + > +# > +# send_igmp_v3_report INPORT HV ETH_SRC IP_SRC IP_CSUM GROUP REC_TYPE > +# IGMP_CSUM OUTFILE > +# > +# This shell function causes an IGMPv3 report to be received on INPORT of HV. > +# The packet's content has Ethernet destination 01:00:5E:00:00:22 and source > +# ETH_SRC (exactly 12 hex digits). Ethernet type is set to IP. > +# GROUP is the IP multicast group to be joined/to leave (based on REC_TYPE). > +# REC_TYPE == 04: join GROUP > +# REC_TYPE == 03: leave GROUP > +# The packet hexdump is also stored in OUTFILE. > +# > +send_igmp_v3_report() { > + local inport=$1 hv=$2 eth_src=$3 ip_src=$4 ip_chksum=$5 group=$6 > + local rec_type=$7 igmp_chksum=$8 outfile=$9 > + > + local eth_dst=01005e000016 > + local ip_dst=$(ip_to_hex 224 0 0 22) > + local ip_ttl=01 > + local ip_ra_opt=94040000 > + > + local igmp_type=2200 > + local num_rec=00000001 > + local aux_dlen=00 > + local num_src=0000 > + > + local eth=${eth_dst}${eth_src}0800 > + local ip=46c0002800004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst}${ip_ra_opt} > + local igmp=${igmp_type}${igmp_chksum}${num_rec}${rec_type}${aux_dlen}${num_src}${group} > + local packet=${eth}${ip}${igmp} > + > + echo ${packet} >> ${outfile} > + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} > +} > + > +# > +# store_igmp_v3_query ETH_SRC IP_SRC IP_CSUM OUTFILE > +# > +# This shell function builds an IGMPv3 general query from ETH_SRC and IP_SRC > +# and stores the hexdump of the packet in OUTFILE. > +# > +store_igmp_v3_query() { > + local eth_src=$1 ip_src=$2 ip_chksum=$3 outfile=$4 > + > + local eth_dst=01005e000001 > + local ip_dst=$(ip_to_hex 224 0 0 1) > + local ip_ttl=01 > + local igmp_type=11 > + local max_resp=0a > + local igmp_chksum=eeeb > + local addr=00000000 > + > + local eth=${eth_dst}${eth_src}0800 > + local ip=4500002000004000${ip_ttl}02${ip_chksum}${ip_src}${ip_dst} > + local igmp=${igmp_type}${max_resp}${igmp_chksum}${addr}000a0000 > + local packet=${eth}${ip}${igmp} > + > + echo ${packet} >> ${outfile} > +} > + > +# > +# send_ip_multicast_pkt INPORT HV ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN TTL > +# IP_CHKSUM IP_PROTO DATA > +# > +# This shell function causes an IP multicast packet to be received on INPORT > +# of HV. > +# The hexdump of the packet is stored in OUTFILE. > +# > +send_ip_multicast_pkt() { > + local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 > + local ip_src=$5 ip_dst=$6 ip_len=$7 ip_ttl=$8 ip_chksum=$9 proto=${10} > + local data=${11} > + > + local eth=${eth_dst}${eth_src}0800 > + local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst} > + local packet=${eth}${ip}${data} > + > + check as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet} > +} > + > +# > +# store_ip_multicast_pkt ETH_SRC ETH_DST IP_SRC IP_DST IP_LEN TTL > +# IP_CHKSUM IP_PROTO DATA OUTFILE > +# > +# This shell function builds an IP multicast packet and stores the hexdump of > +# the packet in OUTFILE. > +# > +store_ip_multicast_pkt() { > + local eth_src=$1 eth_dst=$2 > + local ip_src=$3 ip_dst=$4 ip_len=$5 ip_ttl=$6 ip_chksum=$7 proto=$8 > + local data=$9 outfile=${10} > + > + local eth=${eth_dst}${eth_src}0800 > + local ip=450000${ip_len}95f14000${ip_ttl}${proto}${ip_chksum}${ip_src}${ip_dst} > + local packet=${eth}${ip}${data} > + > + echo ${packet} >> ${outfile} > +} > + > +check ovn-nbctl ls-add sw1 \ > + -- lsp-add sw1 sw1-p1 > + > +check ovn-nbctl ls-add sw2 \ > + -- lsp-add sw2 sw2-p2 > + > +check ovn-nbctl lr-add rtr \ > + -- lrp-add rtr rtr-sw1 00:00:00:00:01:00 10.0.0.254/24 \ > + -- lrp-add rtr rtr-sw2 00:00:00:00:02:00 20.0.0.254/24 > + > +ovn-nbctl lsp-add sw1 sw1-rtr \ > + -- lsp-set-type sw1-rtr router \ > + -- lsp-set-addresses sw1-rtr 00:00:00:00:01:00 \ > + -- lsp-set-options sw1-rtr router-port=rtr-sw1 > +check ovn-nbctl lsp-add sw2 sw2-rtr \ > + -- lsp-set-type sw2-rtr router \ > + -- lsp-set-addresses sw2-rtr 00:00:00:00:02:00 \ > + -- lsp-set-options sw2-rtr router-port=rtr-sw2 > +check ovn-nbctl lrp-set-gateway-chassis rtr-sw1 hv1 10 > +check ovn-nbctl lrp-set-gateway-chassis rtr-sw2 hv2 10 > + > +net_add n1 > +sim_add hv1 > +as hv1 > +check 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=sw1-p1 \ > + options:tx_pcap=hv1/vif1-tx.pcap \ > + options:rxq_pcap=hv1/vif1-rx.pcap > + > +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-vif2 -- \ > + set interface hv2-vif2 external-ids:iface-id=sw2-p2 \ > + options:tx_pcap=hv2/vif2-tx.pcap \ > + options:rxq_pcap=hv2/vif2-rx.pcap > + > +wait_for_ports_up > + > +AS_BOX([Wait until cr-rtr-sw1 is claimed by hv1]) > +hv1_chassis=$(fetch_column Chassis _uuid name=hv1) > +AS_BOX([check that the chassis redirect port has been claimed by the hv1 chassis]) > +wait_row_count Port_Binding 1 logical_port=cr-rtr-sw1 chassis=$hv1_chassis > + > +AS_BOX([Wait until cr-rtr-sw2 is claimed by hv2]) > +hv2_chassis=$(fetch_column Chassis _uuid name=hv2) > +AS_BOX([check that the chassis redirect port has been claimed by the hv2 chassis]) > +wait_row_count Port_Binding 1 logical_port=cr-rtr-sw2 chassis=$hv2_chassis > + > +check ovn-nbctl --wait=hv sync > + > +AT_CAPTURE_FILE([exp]) > +AT_CAPTURE_FILE([rcv]) > +check_packets() { > + > exp > + > rcv > + if test "$1" = --uniq; then > + sort="sort -u"; shift > + else > + sort=sort > + fi > + for tuple in "$@"; do > + set $tuple; pcap=$1 type=$2 > + echo "--- $pcap" | tee -a exp >> rcv > + $sort "$type" >> exp > + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv > + echo | tee -a exp >> rcv > + done > + > + $at_diff exp rcv >/dev/null > +} > + > +OVN_POPULATE_ARP > + > +# Enable IGMP snooping on sw1 and sw2 and relay on rtr. > +check ovn-nbctl --wait=hv set Logical_Switch sw1 \ > + other_config:mcast_querier="false" \ > + other_config:mcast_snoop="true" > +check ovn-nbctl --wait=hv set Logical_Switch sw2 \ > + other_config:mcast_querier="false" \ > + other_config:mcast_snoop="true" > +check ovn-nbctl set logical_router rtr \ > + options:mcast_relay="true" > +check ovn-nbctl --wait=hv sync > + > +# Inject IGMP Join for 239.0.1.68 on sw2-p2. > +send_igmp_v3_report hv2-vif2 hv2 \ > + 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \ > + $(ip_to_hex 239 0 1 68) 04 e9b9 \ > + /dev/null > + > +# Check that the IGMP Group is learned by all switches. > +wait_row_count IGMP_Group 1 address=239.0.1.68 > +check ovn-nbctl --wait=hv sync > + > +# Send traffic from sw1 and make sure it is relayed by rtr. > +> expected_routed > +> expected_empty > + > +as hv1 reset_pcap_file hv1-vif1 hv1/vif1 > +as hv2 reset_pcap_file hv2-vif2 hv2/vif2 > + > +send_ip_multicast_pkt hv1-vif1 hv1 \ > + 000000000001 01005e000144 \ > + $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ > + e518e518000a3b3a0000 > +store_ip_multicast_pkt \ > + 000000000200 01005e000144 \ > + $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \ > + e518e518000a3b3a0000 expected_routed > + > +OVS_WAIT_UNTIL( > + [check_packets 'hv1/vif1-tx.pcap expected_empty' \ > + 'hv2/vif2-tx.pcap expected_routed'], > + [$at_diff -F'^---' exp rcv]) > + > +OVN_CLEANUP([hv1], [hv2]) > +AT_CLEANUP > +]) > + > OVN_FOR_EACH_NORTHD([ > AT_SETUP([MLD snoop/querier/relay]) > AT_KEYWORDS([snoop query querier relay]) > -- > 2.27.0 > > _______________________________________________ > dev mailing list > d...@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Thanks for the fix. I haven't tested, but it looks good. Acked-by: Han Zhou <hz...@ovn.org> _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev