From patchwork Tue Nov 17 01:46:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 1401281 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=whitealder.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=P/FjgXkQ; dkim-atps=neutral Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4CZpj16vr7z9sT6 for ; Tue, 17 Nov 2020 12:47:17 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id 26BC1867C6; Tue, 17 Nov 2020 01:47:16 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id khWs8SPEbFqy; Tue, 17 Nov 2020 01:47:06 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by whitealder.osuosl.org (Postfix) with ESMTP id 563AA867B2; Tue, 17 Nov 2020 01:47:06 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3BD58C0891; Tue, 17 Nov 2020 01:47:06 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from silver.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id D8A25C07FF for ; Tue, 17 Nov 2020 01:47:04 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by silver.osuosl.org (Postfix) with ESMTP id 94E7420462 for ; Tue, 17 Nov 2020 01:47:04 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from silver.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 1S8z0JLrU5Lu for ; Tue, 17 Nov 2020 01:47:02 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by silver.osuosl.org (Postfix) with ESMTPS id 89EFD203BF for ; Tue, 17 Nov 2020 01:47:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1605577621; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=IpA2qF7BbePHeeNKf1ngtc8DWohSoVCU9N4bJEBFxdY=; b=P/FjgXkQe5RdYJdLCXPttb1R+wzRf+sjgDtY5ONvzgct8BQYKB2xbCMEvxf+K+YAtQT/i4 9R84LwGyqS9ObK3e4QwgU0/sA4Xs6GQ5FelGR8ItHZknkLDhttiaqlMXN9/QDBcu5zTI5h F588UAyfkvBKkIQAnLaVFJo2RfxaEwQ= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-227-pKTsk8EBPlmizKrskrNkWA-1; Mon, 16 Nov 2020 20:46:59 -0500 X-MC-Unique: pKTsk8EBPlmizKrskrNkWA-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 3283E185697A for ; Tue, 17 Nov 2020 01:46:58 +0000 (UTC) Received: from monae.redhat.com (ovpn-113-146.rdu2.redhat.com [10.10.113.146]) by smtp.corp.redhat.com (Postfix) with ESMTP id 95ADC5C1C4 for ; Tue, 17 Nov 2020 01:46:57 +0000 (UTC) From: Mark Michelson To: dev@openvswitch.org Date: Mon, 16 Nov 2020 20:46:56 -0500 Message-Id: <20201117014656.15181-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=mmichels@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v3] Allow explicit setting of the SNAT zone on a gateway router. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" In certain situations, OVN may coexist with other applications on a host. Traffic from OVN and the other applications may then go out a shared gateway. If OVN traffic and the other application traffic use different conntrack zones for SNAT, then it is possible for the shared gateway to assign conflicting source IP:port combinations. By sharing the same conntrack zone, there will be no conflicting assignments. In this commit, we introduce options:snat-ct-zone for northbound logical routers. By setting this option, users can explicitly set the conntrack zone for the logical router so that it will match the zone used by non-OVN traffic on the host. The biggest side effects of this patch are: 1) southbound datapath changes now result in recalculating CT zones in ovn-controller. This can result in recomputing physical flows in more situations than previously. 2) The table 65 flow to transition between datapaths is no longer associated with a port binding. This is because the flow refers to the peer datapath's CT zones, which can now be updated due to changes on that datapath. The flow therefore may need to be updated either due to the port binding being changed or the peer datapath being changed. Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1892311 Signed-off-by: Mark Michelson --- controller/ovn-controller.c | 86 +++++++++++++++++++---- controller/physical.c | 2 +- lib/ovn-util.c | 7 ++ lib/ovn-util.h | 1 + northd/ovn-northd.c | 10 +++ ovn-nb.xml | 7 ++ tests/ovn.at | 135 ++++++++++++++++++++++++++++++++++++ 7 files changed, 234 insertions(+), 14 deletions(-) diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index a06cae3cc..25de0c72f 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -531,6 +531,21 @@ update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl, } } +static void +add_pending_ct_zone_entry(struct shash *pending_ct_zones, + enum ct_zone_pending_state state, + int zone, bool add, const char *name) +{ + VLOG_DBG("%s ct zone %"PRId32" for '%s'", + add ? "assigning" : "removing", zone, name); + + struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); + pending->state = state; /* Skip flushing zone. */ + pending->zone = zone; + pending->add = add; + shash_add(pending_ct_zones, name, pending); +} + static void update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, struct simap *ct_zones, unsigned long *ct_zone_bitmap, @@ -540,6 +555,8 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, int scan_start = 1; const char *user; struct sset all_users = SSET_INITIALIZER(&all_users); + struct simap req_snat_zones = SIMAP_INITIALIZER(&req_snat_zones); + unsigned long unreq_snat_zones[BITMAP_N_LONGS(MAX_CT_ZONES)]; SSET_FOR_EACH(user, lports) { sset_add(&all_users, user); @@ -554,6 +571,11 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, char *snat = alloc_nat_zone_key(&ld->datapath->header_.uuid, "snat"); sset_add(&all_users, dnat); sset_add(&all_users, snat); + + int req_snat_zone = datapath_snat_ct_zone(ld->datapath); + if (req_snat_zone >= 0) { + simap_put(&req_snat_zones, snat, req_snat_zone); + } free(dnat); free(snat); } @@ -564,15 +586,56 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, VLOG_DBG("removing ct zone %"PRId32" for '%s'", ct_zone->data, ct_zone->name); - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_DB_QUEUED; /* Skip flushing zone. */ - pending->zone = ct_zone->data; - pending->add = false; - shash_add(pending_ct_zones, ct_zone->name, pending); + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_DB_QUEUED, + ct_zone->data, false, ct_zone->name); bitmap_set0(ct_zone_bitmap, ct_zone->data); simap_delete(ct_zones, ct_zone); + } else if (!simap_find(&req_snat_zones, ct_zone->name)) { + bitmap_set1(unreq_snat_zones, ct_zone->data); + } + } + + /* Prioritize requested CT zones */ + struct simap_node *snat_req_node; + SIMAP_FOR_EACH (snat_req_node, &req_snat_zones) { + struct simap_node *node = simap_find(ct_zones, snat_req_node->name); + if (node) { + if (node->data == snat_req_node->data) { + /* No change to this request, so no action needed */ + continue; + } else { + /* Zone request has changed for this node. delete old entry */ + bitmap_set0(ct_zone_bitmap, node->data); + simap_delete(ct_zones, node); + } } + + /* Determine if someone already had this zone auto-assigned. + * If so, then they need to give up their assignment since + * that zone is being explicitly requested now. + */ + if (bitmap_is_set(unreq_snat_zones, snat_req_node->data)) { + struct simap_node *dup; + struct simap_node *next; + SIMAP_FOR_EACH_SAFE (dup, next, ct_zones) { + if (dup != snat_req_node && dup->data == snat_req_node->data) { + simap_delete(ct_zones, dup); + break; + } + } + /* Set this bit to 0 so that if multiple datapaths have requested + * this zone, we don't needlessly double-detect this condition. + */ + bitmap_set0(unreq_snat_zones, snat_req_node->data); + } + + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_OF_QUEUED, + snat_req_node->data, true, + snat_req_node->name); + + bitmap_set1(ct_zone_bitmap, snat_req_node->data); + simap_put(ct_zones, snat_req_node->name, snat_req_node->data); } /* xxx This is wasteful to assign a zone to each port--even if no @@ -592,22 +655,18 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths, if (zone == MAX_CT_ZONES + 1) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); VLOG_WARN_RL(&rl, "exhausted all ct zones"); - return; + break; } scan_start = zone + 1; - VLOG_DBG("assigning ct zone %"PRId32" to '%s'", zone, user); - - struct ct_zone_pending_entry *pending = xmalloc(sizeof *pending); - pending->state = CT_ZONE_OF_QUEUED; - pending->zone = zone; - pending->add = true; - shash_add(pending_ct_zones, user, pending); + add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_OF_QUEUED, + zone, true, user); bitmap_set1(ct_zone_bitmap, zone); simap_put(ct_zones, user, zone); } + simap_destroy(&req_snat_zones); sset_destroy(&all_users); } @@ -2330,6 +2389,7 @@ main(int argc, char *argv[]) engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL); engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL); + engine_add_input(&en_ct_zones, &en_sb_datapath_binding, NULL); engine_add_input(&en_ct_zones, &en_runtime_data, NULL); engine_add_input(&en_runtime_data, &en_ofctrl_is_connected, NULL); diff --git a/controller/physical.c b/controller/physical.c index 1bc2c389b..00c4ca4fd 100644 --- a/controller/physical.c +++ b/controller/physical.c @@ -926,7 +926,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, binding->header_.uuid.parts[0], - &match, ofpacts_p, &binding->header_.uuid); + &match, ofpacts_p, hc_uuid); return; } diff --git a/lib/ovn-util.c b/lib/ovn-util.c index 18aac8da3..f0fb796b4 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -532,6 +532,13 @@ datapath_is_switch(const struct sbrec_datapath_binding *ldp) { return smap_get(&ldp->external_ids, "logical-switch") != NULL; } + +int +datapath_snat_ct_zone(const struct sbrec_datapath_binding *dp) +{ + return smap_get_int(&dp->external_ids, "snat-ct-zone", -1); +} + struct tnlid_node { struct hmap_node hmap_node; diff --git a/lib/ovn-util.h b/lib/ovn-util.h index 3496673b2..ea8226571 100644 --- a/lib/ovn-util.h +++ b/lib/ovn-util.h @@ -107,6 +107,7 @@ uint32_t ovn_logical_flow_hash(const struct uuid *logical_datapath, uint16_t priority, const char *match, const char *actions); bool datapath_is_switch(const struct sbrec_datapath_binding *); +int datapath_snat_ct_zone(const struct sbrec_datapath_binding *ldp); void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *idl_); diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 4d4190cb9..8ce756c8a 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -1179,6 +1179,16 @@ ovn_datapath_update_external_ids(struct ovn_datapath *od) smap_add(&ids, "interconn-ts", ts); } } + + /* Set snat-ct-zone */ + if (od->nbr) { + int nat_default_ct = smap_get_int(&od->nbr->options, + "snat-ct-zone", -1); + if (nat_default_ct >= 0) { + smap_add_format(&ids, "snat-ct-zone", "%d", nat_default_ct); + } + } + sbrec_datapath_binding_set_external_ids(od->sb, &ids); smap_destroy(&ids); } diff --git a/ovn-nb.xml b/ovn-nb.xml index 5e6c6c7a3..619c0e299 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1961,6 +1961,13 @@ unique key for each datapath by itself. However, if it is configured, ovn-northd honors the configured value. + + Use the requested conntrack zone for SNAT with this router. This can be + useful if egress traffic from the host running OVN comes from both OVN + and other sources. This way, OVN and the other sources can make use of + the same conntrack zone. + diff --git a/tests/ovn.at b/tests/ovn.at index 378d11921..ca65dfff7 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -22686,3 +22686,138 @@ AT_CHECK([test "$encap_rec_mvtep" == "$encap_rec_mvtep1"], [0], []) OVN_CLEANUP([hv1]) AT_CLEANUP + +AT_SETUP([ovn -- snat default ct zone]) +ovn_start + +net_add n1 +sim_add hv1 +ovs-vsctl add-br br-phys +as hv1 +ovn_attach n1 br-phys 192.168.0.10 + +ovn-nbctl ls-add sw0 +ovn-nbctl lsp-add sw0 sw0-p1 +ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:02 10.0.0.2" + +ovn-nbctl lr-add gw_router +ovn-nbctl set Logical_Router gw_router options:chassis="hv1" + +ovn-nbctl lrp-add gw_router gw_router-sw0 00:00:00:00:00:01 10.0.0.1/24 +ovn-nbctl lsp-add sw0 sw0-gw_router +ovn-nbctl lsp-set-addresses sw0-gw_router router +ovn-nbctl set Logical_Switch_Port sw0-gw_router type=router \ + options:router-port=gw_router-sw0 \ + +ovn-nbctl lr-nat-add gw_router snat 192.168.0.1 10.0.0.0/24 + +as hv1 ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=sw0-p1 + +ovn-nbctl --wait=hv sync + +ro_nb_uuid=$(ovn-nbctl get Logical_Router gw_router _uuid) +sw_nb_uuid=$(ovn-nbctl get Logical_Switch sw0 _uuid) +ro_sb_uuid=$(ovn-sbctl --bare --columns=_uuid find Datapath_Binding external-ids:logical-router=${ro_nb_uuid}) +sw_sb_uuid=$(ovn-sbctl --bare --columns=_uuid find Datapath_Binding external-ids:logical-switch=${sw_nb_uuid}) +ro_meta=$(ovn-sbctl get Datapath_Binding ${ro_sb_uuid} tunnel_key) +ro_meta=$(printf %#x ${ro_meta}) +sw_meta=$(ovn-sbctl get Datapath_Binding ${sw_sb_uuid} tunnel_key) +sw_meta=$(printf %#x ${sw_meta}) + +echo "ro_nb_uuid: ${ro_nb_uuid}" +echo "sw_nb_uuid: ${sw_nb_uuid}" +echo "ro_sb_uuid: ${ro_sb_uuid}" +echo "sw_sb_uuid: ${sw_sb_uuid}" +echo "ro_meta: ${ro_meta}" +echo "sw_meta: ${sw_meta}" + +as hv1 +ovs-vsctl list bridge br-int +ro_snat_zone=$(printf %#x $(ovs-vsctl get bridge br-int external-ids:ct-zone-${ro_sb_uuid}_snat | tr -d \")) +sw_snat_zone=$(ovs-vsctl get bridge br-int external-ids:ct-zone-${sw_sb_uuid}_snat | tr -d \") + +echo "ro_snat_zone: ${ro_snat_zone}" +echo "sw_snat_zone: ${sw_snat_zone}" + +as hv1 ovs-ofctl dump-flows br-int > offlows_pre +AT_CAPTURE_FILE([offlows_pre]) +# We should have a flow in table 33 that transitions from the ingress pipeline +# to the egress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=33.*metadata=${ro_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_pre], [0], [dnl +1 +]) + +# We should have a flow in table 65 that transitions from the egress pipeline +# of sw0 to the ingress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=65.*metadata=${sw_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_pre], [0], [dnl +1 +]) + + +# Request a specific zone for the gateway router. This should then get reflected +# both in the OVS database and in the flow table. +ovn-nbctl --wait=hv set Logical_Router gw_router options:snat-ct-zone=666 + +ovs-vsctl list bridge br-int + +as hv1 +ro_snat_zone=$(ovs-vsctl get bridge br-int external-ids:ct-zone-${ro_sb_uuid}_snat | tr -d \") + +echo "ro_snat_zone: ${ro_snat_zone}" + +AT_CHECK([test "${ro_snat_zone}" = "666"], [0], []) + +ro_snat_zone=$(printf %#x "${ro_snat_zone}") + +as hv1 ovs-ofctl dump-flows br-int > offlows_post +AT_CAPTURE_FILE([offlows_post]) +# We should have a flow in table 33 that transitions from the ingress pipeline +# to the egress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=33.*metadata=${ro_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_post], [0], [dnl +1 +]) + +# We should have a flow in table 65 that transitions from the egress pipeline +# of sw0 to the ingress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=65.*metadata=${sw_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_post], [0], [dnl +1 +]) + +# Set router to use the zone that had been assigned to the switch. The router +# should claim the zone and the switch should change zones. +ovn-nbctl --wait=hv set Logical_Router gw_router options:snat-ct-zone=${sw_snat_zone} + +as hv1 +ro_snat_zone=$(ovs-vsctl get bridge br-int external-ids:ct-zone-${ro_sb_uuid}_snat | tr -d \") + +echo "ro_snat_zone: ${ro_snat_zone}" + +AT_CHECK([test "${ro_snat_zone}" = "${sw_snat_zone}"], [0], []) + +ro_snat_zone=$(printf %#x "${ro_snat_zone}") + +ovs-vsctl list bridge br-int + +sw_new_snat_zone=$(ovs-vsctl get bridge br-int external-ids:ct-zone-${sw_sb_uuid}_snat | tr -d \") + +echo "sw_new_snat_zone: ${sw_new_snat_zone}" + +AT_CHECK([test "${sw_new_snat_zone}" != "${sw_snat_zone}"], [0], []) + +as hv1 ovs-ofctl dump-flows br-int > offlows_stolen +AT_CAPTURE_FILE([offlows_stolen]) +# We should have a flow in table 33 that transitions from the ingress pipeline +# to the egress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=33.*metadata=${ro_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_stolen], [0], [dnl +1 +]) + +# We should have a flow in table 65 that transitions from the egress pipeline +# of sw0 to the ingress pipeline of gw_router. +AT_CHECK_UNQUOTED([grep -c "table=65.*metadata=${sw_meta}.*load:${ro_snat_zone}->NXM_NX_REG12[]" offlows_stolen], [0], [dnl +1 +]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP