From patchwork Fri Dec 16 12:30:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ales Musil X-Patchwork-Id: 1716495 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: legolas.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=TTQP0HtE; dkim-atps=neutral Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4NYT3g4G1pz2403 for ; Fri, 16 Dec 2022 23:31:11 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 91E316114E; Fri, 16 Dec 2022 12:31:08 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 91E316114E Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TTQP0HtE X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 42LsVlt4T5Mn; Fri, 16 Dec 2022 12:31:04 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp3.osuosl.org (Postfix) with ESMTPS id 7D65460806; Fri, 16 Dec 2022 12:31:03 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 7D65460806 Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 7D100C007F; Fri, 16 Dec 2022 12:31:01 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 68DD1C0033 for ; Fri, 16 Dec 2022 12:30:59 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 3528660806 for ; Fri, 16 Dec 2022 12:30:59 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 3528660806 X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id wy68PAzmP9ZA for ; Fri, 16 Dec 2022 12:30:57 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 27FBE607F7 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id 27FBE607F7 for ; Fri, 16 Dec 2022 12:30:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1671193855; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=pVZpT2Q3bPSwAZDSRubrhBM4L+wvPAIGop4deDuyc8o=; b=TTQP0HtE+tftIHUiTBSGCy3O5TEw/2xjaP6lLFQ0JyJXt/bZ2tHvo5ZpxR+x06AS4owfnf 7I6EQa7bOuW21hPJ9y8GR4FyCJHlS74Er0cnzOfpsnVF3THjfu3YFViozCOGAQ/Tjv/Klt e7MY4EtXf3vPwCj4hDHHQRmMBpn0EyA= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-609-Yv9zJ09JP0Ok2327lxklBw-1; Fri, 16 Dec 2022 07:30:52 -0500 X-MC-Unique: Yv9zJ09JP0Ok2327lxklBw-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 6B563886064; Fri, 16 Dec 2022 12:30:52 +0000 (UTC) Received: from amusil.redhat.com (ovpn-192-198.brq.redhat.com [10.40.192.198]) by smtp.corp.redhat.com (Postfix) with ESMTP id EBB541121315; Fri, 16 Dec 2022 12:30:50 +0000 (UTC) From: Ales Musil To: dev@openvswitch.org Date: Fri, 16 Dec 2022 13:30:47 +0100 Message-Id: <20221216123048.973406-2-amusil@redhat.com> In-Reply-To: <20221216123048.973406-1-amusil@redhat.com> References: <20221216123048.973406-1-amusil@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Cc: i.maximets@ovn.org Subject: [ovs-dev] [PATCH v5 1/2] ofp, dpif: Allow CT flush based on partial match 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" Currently, the CT can be flushed by dpctl only be specifying the whole 5-tuple. This is not very convenient when there are only some fields known to the user of CT flush. Add new struct ofputil_ct_match which represents the generic filtering that can be done for CT flush. The match is done only on fields that are non-zero with exception to the icmp fields. This allows the filtering just within dpctl, however it is a preparation for OpenFlow extension. Reported-at: https://bugzilla.redhat.com/2120546 Signed-off-by: Ales Musil Acked-by: Paolo Valerio --- v5: Add ack from Paolo. v4: Fix a flush all scenario. v3: Rebase on top of master. Address the C99 comment and missing dpif_close call. v2: Rebase on top of master. Address comments from Paolo. --- NEWS | 2 + include/openvswitch/ofp-util.h | 28 +++ lib/automake.mk | 2 + lib/ct-dpif.c | 208 +++++++++------------ lib/ct-dpif.h | 4 +- lib/dpctl.c | 42 ++--- lib/dpctl.man | 3 +- lib/ofp-ct-util.c | 326 +++++++++++++++++++++++++++++++++ lib/ofp-ct-util.h | 36 ++++ tests/system-traffic.at | 82 ++++++++- 10 files changed, 586 insertions(+), 147 deletions(-) create mode 100644 lib/ofp-ct-util.c create mode 100644 lib/ofp-ct-util.h diff --git a/NEWS b/NEWS index 265375e1c..ff8904b02 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,8 @@ Post-v3.0.0 10 Gbps link speed by default in case the actual link speed cannot be determined. Previously it was 10 Mbps. Values can still be overridden by specifying 'max-rate' or '[r]stp-path-cost' accordingly. + - ovs-dpctl and related ovs-appctl commands: + * "flush-conntrack" is capable of handling partial 5-tuple. v3.0.0 - 15 Aug 2022 diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h index 091a09cad..84937ae26 100644 --- a/include/openvswitch/ofp-util.h +++ b/include/openvswitch/ofp-util.h @@ -19,6 +19,9 @@ #include #include +#include +#include + #include "openvswitch/ofp-protocol.h" struct ofp_header; @@ -27,6 +30,31 @@ struct ofp_header; extern "C" { #endif +struct ofputil_ct_tuple { + struct in6_addr src; + struct in6_addr dst; + + union { + ovs_be16 src_port; + ovs_be16 icmp_id; + }; + union { + ovs_be16 dst_port; + struct { + uint8_t icmp_code; + uint8_t icmp_type; + }; + }; +}; + +struct ofputil_ct_match { + uint8_t ip_proto; + uint16_t l3_type; + + struct ofputil_ct_tuple tuple_orig; + struct ofputil_ct_tuple tuple_reply; +}; + bool ofputil_decode_hello(const struct ofp_header *, uint32_t *allowed_versions); struct ofpbuf *ofputil_encode_hello(uint32_t version_bitmap); diff --git a/lib/automake.mk b/lib/automake.mk index a0fabe38f..37135f118 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -227,6 +227,8 @@ lib_libopenvswitch_la_SOURCES = \ lib/ofp-actions.c \ lib/ofp-bundle.c \ lib/ofp-connection.c \ + lib/ofp-ct-util.c \ + lib/ofp-ct-util.h \ lib/ofp-ed-props.c \ lib/ofp-errors.c \ lib/ofp-flow.c \ diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c index 6f17a26b5..9b324ff79 100644 --- a/lib/ct-dpif.c +++ b/lib/ct-dpif.c @@ -20,6 +20,7 @@ #include #include "ct-dpif.h" +#include "ofp-ct-util.h" #include "openvswitch/ofp-parse.h" #include "openvswitch/vlog.h" @@ -71,6 +72,31 @@ ct_dpif_dump_start(struct dpif *dpif, struct ct_dpif_dump_state **dump, return err; } +static void +ct_dpif_tuple_from_ofputil_ct_tuple(const struct ofputil_ct_tuple *ofp_tuple, + struct ct_dpif_tuple *tuple, + uint16_t l3_type, uint8_t ip_proto) +{ + if (l3_type == AF_INET) { + tuple->src.ip = in6_addr_get_mapped_ipv4(&ofp_tuple->src); + tuple->dst.ip = in6_addr_get_mapped_ipv4(&ofp_tuple->dst); + } else { + tuple->src.in6 = ofp_tuple->src; + tuple->dst.in6 = ofp_tuple->dst; + } + + tuple->l3_type = l3_type; + tuple->ip_proto = ip_proto; + tuple->src_port = ofp_tuple->src_port; + + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + tuple->icmp_code = ofp_tuple->icmp_code; + tuple->icmp_type = ofp_tuple->icmp_type; + } else { + tuple->dst_port = ofp_tuple->dst_port; + } +} + /* Dump one connection from a tracker, and put it in 'entry'. * * 'dump' should have been initialized by ct_dpif_dump_start(). @@ -100,25 +126,77 @@ ct_dpif_dump_done(struct ct_dpif_dump_state *dump) ? dpif->dpif_class->ct_dump_done(dpif, dump) : EOPNOTSUPP); } - + +static int +ct_dpif_flush_tuple(struct dpif *dpif, const uint16_t *zone, + const struct ofputil_ct_match *match) { + struct ct_dpif_dump_state *dump; + struct ct_dpif_entry cte; + int error; + int tot_bkts; + + if (!dpif->dpif_class->ct_flush) { + return EOPNOTSUPP; + } + + if (VLOG_IS_DBG_ENABLED()) { + struct ds ds = DS_EMPTY_INITIALIZER; + ofputil_ct_match_format(&ds, match); + VLOG_DBG("%s: ct_flush:%s in zone %d", dpif_name(dpif), ds_cstr(&ds), + zone ? *zone : 0); + ds_destroy(&ds); + } + + /* If we have full five tuple in orig just do the flush over that + * tuple directly. */ + if (ofputil_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto)) { + struct ct_dpif_tuple tuple; + ct_dpif_tuple_from_ofputil_ct_tuple(&match->tuple_orig, &tuple, + match->l3_type, match->ip_proto); + return dpif->dpif_class->ct_flush(dpif, zone, &tuple); + } + + error = ct_dpif_dump_start(dpif, &dump, zone, &tot_bkts); + if (error) { + return error; + } + + while (!(error = ct_dpif_dump_next(dump, &cte))) { + if (zone && *zone != cte.zone) { + continue; + } + + if (ofputil_ct_match_cmp(match, &cte)) { + error = dpif->dpif_class->ct_flush(dpif, &cte.zone, + &cte.tuple_orig); + if (error) { + break; + } + } + } + if (error == EOF) { + error = 0; + } + + ct_dpif_dump_done(dump); + return error; +} + /* Flush the entries in the connection tracker used by 'dpif'. The * arguments have the following behavior: * - * - If both 'zone' and 'tuple' are NULL, flush all the conntrack entries. - * - If 'zone' is not NULL, and 'tuple' is NULL, flush all the conntrack + * - If both 'zone' and 'match' are NULL, or match is zero, + * flush all the conntrack entries. + * - If 'zone' is not NULL, and 'match' is NULL, flush all the conntrack * entries in '*zone'. - * - If 'tuple' is not NULL, flush the conntrack entry specified by 'tuple' + * - If 'tuple' is not NULL, flush the conntrack entry specified by 'match' * in '*zone'. If 'zone' is NULL, use the default zone (zone 0). */ int ct_dpif_flush(struct dpif *dpif, const uint16_t *zone, - const struct ct_dpif_tuple *tuple) + const struct ofputil_ct_match *match) { - if (tuple) { - struct ds ds = DS_EMPTY_INITIALIZER; - ct_dpif_format_tuple(&ds, tuple); - VLOG_DBG("%s: ct_flush: %s in zone %d", dpif_name(dpif), ds_cstr(&ds), - zone ? *zone : 0); - ds_destroy(&ds); + if (match && !ofputil_is_ct_match_zero(match)) { + return ct_dpif_flush_tuple(dpif, zone, match); } else if (zone) { VLOG_DBG("%s: ct_flush: zone %"PRIu16, dpif_name(dpif), *zone); } else { @@ -126,7 +204,7 @@ ct_dpif_flush(struct dpif *dpif, const uint16_t *zone, } return (dpif->dpif_class->ct_flush - ? dpif->dpif_class->ct_flush(dpif, zone, tuple) + ? dpif->dpif_class->ct_flush(dpif, zone, NULL) : EOPNOTSUPP); } @@ -583,112 +661,6 @@ ct_dpif_format_tcp_stat(struct ds * ds, int tcp_state, int conn_per_state) ds_put_format(ds, "=%u", conn_per_state); } -/* Parses a specification of a conntrack 5-tuple from 's' into 'tuple'. - * Returns true on success. Otherwise, returns false and puts the error - * message in 'ds'. */ -bool -ct_dpif_parse_tuple(struct ct_dpif_tuple *tuple, const char *s, struct ds *ds) -{ - char *pos, *key, *value, *copy; - memset(tuple, 0, sizeof *tuple); - - pos = copy = xstrdup(s); - while (ofputil_parse_key_value(&pos, &key, &value)) { - if (!*value) { - ds_put_format(ds, "field %s missing value", key); - goto error; - } - - if (!strcmp(key, "ct_nw_src") || !strcmp(key, "ct_nw_dst")) { - if (tuple->l3_type && tuple->l3_type != AF_INET) { - ds_put_cstr(ds, "L3 type set multiple times"); - goto error; - } else { - tuple->l3_type = AF_INET; - } - if (!ip_parse(value, key[6] == 's' ? &tuple->src.ip : - &tuple->dst.ip)) { - goto error_with_msg; - } - } else if (!strcmp(key, "ct_ipv6_src") || - !strcmp(key, "ct_ipv6_dst")) { - if (tuple->l3_type && tuple->l3_type != AF_INET6) { - ds_put_cstr(ds, "L3 type set multiple times"); - goto error; - } else { - tuple->l3_type = AF_INET6; - } - if (!ipv6_parse(value, key[8] == 's' ? &tuple->src.in6 : - &tuple->dst.in6)) { - goto error_with_msg; - } - } else if (!strcmp(key, "ct_nw_proto")) { - char *err = str_to_u8(value, key, &tuple->ip_proto); - if (err) { - free(err); - goto error_with_msg; - } - } else if (!strcmp(key, "ct_tp_src") || !strcmp(key,"ct_tp_dst")) { - uint16_t port; - char *err = str_to_u16(value, key, &port); - if (err) { - free(err); - goto error_with_msg; - } - if (key[6] == 's') { - tuple->src_port = htons(port); - } else { - tuple->dst_port = htons(port); - } - } else if (!strcmp(key, "icmp_type") || !strcmp(key, "icmp_code") || - !strcmp(key, "icmp_id") ) { - if (tuple->ip_proto != IPPROTO_ICMP && - tuple->ip_proto != IPPROTO_ICMPV6) { - ds_put_cstr(ds, "invalid L4 fields"); - goto error; - } - uint16_t icmp_id; - char *err; - if (key[5] == 't') { - err = str_to_u8(value, key, &tuple->icmp_type); - } else if (key[5] == 'c') { - err = str_to_u8(value, key, &tuple->icmp_code); - } else { - err = str_to_u16(value, key, &icmp_id); - tuple->icmp_id = htons(icmp_id); - } - if (err) { - free(err); - goto error_with_msg; - } - } else { - ds_put_format(ds, "invalid conntrack tuple field: %s", key); - goto error; - } - } - - if (ipv6_is_zero(&tuple->src.in6) || ipv6_is_zero(&tuple->dst.in6) || - !tuple->ip_proto) { - /* icmp_type, icmp_code, and icmp_id can be 0. */ - if (tuple->ip_proto != IPPROTO_ICMP && - tuple->ip_proto != IPPROTO_ICMPV6) { - if (!tuple->src_port || !tuple->dst_port) { - ds_put_cstr(ds, "at least one of the conntrack 5-tuple fields " - "is missing."); - goto error; - } - } - } - - free(copy); - return true; - -error_with_msg: - ds_put_format(ds, "failed to parse field %s", key); -error: - free(copy); - return false; -} void ct_dpif_push_zone_limit(struct ovs_list *zone_limits, uint16_t zone, diff --git a/lib/ct-dpif.h b/lib/ct-dpif.h index 2848549b0..337c99dd4 100644 --- a/lib/ct-dpif.h +++ b/lib/ct-dpif.h @@ -17,6 +17,7 @@ #ifndef CT_DPIF_H #define CT_DPIF_H +#include "openvswitch/ofp-util.h" #include "openvswitch/types.h" #include "packets.h" @@ -285,7 +286,7 @@ int ct_dpif_dump_start(struct dpif *, struct ct_dpif_dump_state **, int ct_dpif_dump_next(struct ct_dpif_dump_state *, struct ct_dpif_entry *); int ct_dpif_dump_done(struct ct_dpif_dump_state *); int ct_dpif_flush(struct dpif *, const uint16_t *zone, - const struct ct_dpif_tuple *); + const struct ofputil_ct_match *); int ct_dpif_set_maxconns(struct dpif *dpif, uint32_t maxconns); int ct_dpif_get_maxconns(struct dpif *dpif, uint32_t *maxconns); int ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns); @@ -311,7 +312,6 @@ void ct_dpif_format_ipproto(struct ds *ds, uint16_t ipproto); void ct_dpif_format_tuple(struct ds *, const struct ct_dpif_tuple *); uint8_t ct_dpif_coalesce_tcp_state(uint8_t state); void ct_dpif_format_tcp_stat(struct ds *, int, int); -bool ct_dpif_parse_tuple(struct ct_dpif_tuple *, const char *s, struct ds *); void ct_dpif_push_zone_limit(struct ovs_list *, uint16_t zone, uint32_t limit, uint32_t count); void ct_dpif_free_zone_limits(struct ovs_list *); diff --git a/lib/dpctl.c b/lib/dpctl.c index 29041fa3e..e6e350aba 100644 --- a/lib/dpctl.c +++ b/lib/dpctl.c @@ -40,6 +40,7 @@ #include "netdev.h" #include "netlink.h" #include "odp-util.h" +#include "ofp-ct-util.h" #include "openvswitch/ofpbuf.h" #include "packets.h" #include "openvswitch/shash.h" @@ -1707,47 +1708,38 @@ dpctl_flush_conntrack(int argc, const char *argv[], struct dpctl_params *dpctl_p) { struct dpif *dpif = NULL; - struct ct_dpif_tuple tuple, *ptuple = NULL; - struct ds ds = DS_EMPTY_INITIALIZER; - uint16_t zone, *pzone = NULL; - int error; + struct ofputil_ct_match match = {0}; + uint16_t zone; + bool with_zone = false; int args = argc - 1; /* Parse ct tuple */ - if (args && ct_dpif_parse_tuple(&tuple, argv[args], &ds)) { - ptuple = &tuple; + if (args) { + struct ds ds = DS_EMPTY_INITIALIZER; + if (!ofputil_ct_match_parse(&match, argv[args], &ds)) { + dpctl_error(dpctl_p, EINVAL, "%s", ds_cstr(&ds)); + ds_destroy(&ds); + return EINVAL; + } args--; } /* Parse zone */ if (args && ovs_scan(argv[args], "zone=%"SCNu16, &zone)) { - pzone = &zone; - args--; - } - - /* Report error if there are more than one unparsed argument. */ - if (args > 1) { - ds_put_cstr(&ds, "invalid arguments"); - error = EINVAL; - goto error; + with_zone = true; } - error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif); + int error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif); if (error) { + dpctl_error(dpctl_p, error, "Cannot open dpif"); return error; } - error = ct_dpif_flush(dpif, pzone, ptuple); - if (!error) { - dpif_close(dpif); - return 0; - } else { - ds_put_cstr(&ds, "failed to flush conntrack"); + error = ct_dpif_flush(dpif, with_zone ? &zone : NULL, &match); + if (error) { + dpctl_error(dpctl_p, error, "Failed to flush conntrack"); } -error: - dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds)); - ds_destroy(&ds); dpif_close(dpif); return error; } diff --git a/lib/dpctl.man b/lib/dpctl.man index 87ea8087b..b0cabe05d 100644 --- a/lib/dpctl.man +++ b/lib/dpctl.man @@ -312,7 +312,8 @@ If \fBzone\fR=\fIzone\fR is specified, only flushes the connections in If \fIct-tuple\fR is provided, flushes the connection entry specified by \fIct-tuple\fR in \fIzone\fR. The zone defaults to 0 if it is not provided. The userspace connection tracker requires flushing with the original pre-NATed -tuple and a warning log will be otherwise generated. +tuple and a warning log will be otherwise generated. The tuple can be partial +and will remove all connections that are matching on the specified fields. An example of an IPv4 ICMP \fIct-tuple\fR: .IP "ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=1,icmp_type=8,icmp_code=0,icmp_id=10" diff --git a/lib/ofp-ct-util.c b/lib/ofp-ct-util.c new file mode 100644 index 000000000..a10607590 --- /dev/null +++ b/lib/ofp-ct-util.c @@ -0,0 +1,326 @@ + +/* Copyright (c) 2022, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "ct-dpif.h" +#include "ofp-ct-util.h" +#include "openvswitch/dynamic-string.h" +#include "openvswitch/ofp-parse.h" +#include "openvswitch/ofp-util.h" +#include "openvswitch/packets.h" + +static inline bool +ofputil_ct_inet_addr_cmp_partial(const struct in6_addr *partial, + const union ct_dpif_inet_addr *addr, + const uint16_t l3_type) +{ + if (ipv6_is_zero(partial)) { + return true; + } + + if (l3_type == AF_INET && in6_addr_get_mapped_ipv4(partial) != addr->ip) { + return false; + } + + if (l3_type == AF_INET6 && !ipv6_addr_equals(partial, &addr->in6)) { + return false; + } + + return true; +} + +static inline bool +ofputil_ct_tuple_ip_cmp_partial(const struct ofputil_ct_tuple *partial, + const struct ct_dpif_tuple *tuple, + const uint16_t l3_type, const uint8_t ip_proto) +{ + if (!ofputil_ct_inet_addr_cmp_partial(&partial->src, + &tuple->src, l3_type)) { + return false; + } + + if (!ofputil_ct_inet_addr_cmp_partial(&partial->dst, + &tuple->dst, l3_type)) { + return false; + } + + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + if (partial->icmp_id != tuple->icmp_id) { + return false; + } + + if (partial->icmp_type != tuple->icmp_type) { + return false; + } + + if (partial->icmp_code != tuple->icmp_code) { + return false; + } + } else { + if (partial->src_port && partial->src_port != tuple->src_port) { + return false; + } + + if (partial->dst_port && partial->dst_port != tuple->dst_port) { + return false; + } + } + + return true; +} + +/* Compares the non-zero members if they match. This is useful for clearing + * up all connections specified by a partial tuples for orig/reply. */ +bool +ofputil_ct_match_cmp(const struct ofputil_ct_match *match, + const struct ct_dpif_entry *entry) +{ + if (match->l3_type && match->l3_type != entry->tuple_orig.l3_type) { + return false; + } + + if (match->ip_proto && match->ip_proto != entry->tuple_orig.ip_proto) { + return false; + } + + if (!ofputil_ct_tuple_ip_cmp_partial(&match->tuple_orig, + &entry->tuple_orig, + match->l3_type, match->ip_proto)) { + return false; + } + + if (!ofputil_ct_tuple_ip_cmp_partial(&match->tuple_reply, + &entry->tuple_reply, + match->l3_type, match->ip_proto)) { + return false; + } + + return true; +} + +static void +ofputil_ct_tuple_format(struct ds *ds, const struct ofputil_ct_tuple *tuple, + uint8_t ip_proto) +{ + ds_put_cstr(ds, "src="); + ipv6_format_mapped(&tuple->src, ds); + ds_put_cstr(ds, ",dst="); + ipv6_format_mapped(&tuple->dst, ds); + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + ds_put_format(ds, ",icmp_id=%u,icmp_type=%u,icmp_code=%u", + ntohs(tuple->icmp_id), tuple->icmp_type, + tuple->icmp_code); + + } else { + ds_put_format(ds, ",src_port=%u,dst_port=%u", ntohs(tuple->src_port), + ntohs(tuple->dst_port)); + } +} + +static bool +ofputil_is_tuple_zero(const struct ofputil_ct_tuple *tuple) +{ + return ipv6_is_zero(&tuple->src) && ipv6_is_zero(&tuple->dst) && + !tuple->src_port && !tuple->dst_port; +} + +bool +ofputil_ct_tuple_is_five_tuple(const struct ofputil_ct_tuple *tuple, + uint8_t ip_proto) +{ + /* First check if we have address. */ + bool five_tuple = !ipv6_is_zero(&tuple->src) && !ipv6_is_zero(&tuple->dst); + + if (!(ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6)) { + five_tuple = five_tuple && tuple->src_port && tuple->dst_port; + } + + return five_tuple; +} + +bool +ofputil_is_ct_match_zero(const struct ofputil_ct_match *match) +{ + return !match->ip_proto && !match->l3_type && + ofputil_is_tuple_zero(&match->tuple_orig) && + ofputil_is_tuple_zero(&match->tuple_reply); +} + +void +ofputil_ct_match_format(struct ds *ds, const struct ofputil_ct_match *match) +{ + ds_put_format(ds, " l3_type=%u,ip_proto=%u", match->l3_type, + match->ip_proto); + ds_put_cstr(ds, ",orig=("); + ofputil_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto); + ds_put_cstr(ds, "),reply=("); + ofputil_ct_tuple_format(ds, &match->tuple_reply, match->ip_proto); + ds_put_cstr(ds, ")"); +} + +static bool +ofputil_ct_tuple_ip_parse(struct in6_addr *addr, char *value, uint16_t l3_type) +{ + if (!ipv6_is_zero(addr)) { + return false; + } + + if (l3_type == AF_INET) { + ovs_be32 ip = 0; + + ip_parse(value, &ip); + *addr = in6_addr_mapped_ipv4(ip); + } else { + ipv6_parse(value, addr); + } + + return true; +} + +/* Parses a specification of a conntrack 5-tuple from 's' into 'tuple'. + * Returns true on success. Otherwise, returns false and puts the error + * message in 'ds'. */ +bool +ofputil_ct_match_parse(struct ofputil_ct_match *match, const char *s, + struct ds *ds) +{ + char *pos, *key, *value, *copy; + + memset(match, 0, sizeof *match); + struct ofputil_ct_tuple *tuple = &match->tuple_orig; + + pos = copy = xstrdup(s); + while (ofputil_parse_key_value(&pos, &key, &value)) { + if (!*value) { + ds_put_format(ds, "field %s missing value", key); + goto error_with_msg; + } + + if (!strcmp(key, "ct_nw_src") || !strcmp(key, "ct_nw_dst") + || !strcmp(key, "ct_ipv6_src") || !strcmp(key, "ct_ipv6_dst")) { + match->l3_type = key[6] == '6' ? AF_INET6 : AF_INET; + uint8_t index = key[6] == '6' ? 8 : 6; + struct in6_addr *addr = key[index] == 's' + ? &tuple->src : &tuple->dst; + + if (!ofputil_ct_tuple_ip_parse(addr, value, match->l3_type)) { + ds_put_format(ds, "%s is set multiple times", key); + goto error; + } + } else if (!strcmp(key, "ct_nw_proto")) { + char *err = str_to_u8(value, key, &match->ip_proto); + + if (err) { + free(err); + goto error_with_msg; + } + } else if (!strcmp(key, "ct_tp_src") || !strcmp(key, "ct_tp_dst")) { + uint16_t port; + char *err = str_to_u16(value, key, &port); + + if (err) { + free(err); + goto error_with_msg; + } + if (key[6] == 's') { + tuple->src_port = htons(port); + } else { + tuple->dst_port = htons(port); + } + } else if (!strcmp(key, "icmp_type") || !strcmp(key, "icmp_code") || + !strcmp(key, "icmp_id")) { + if (match->ip_proto != IPPROTO_ICMP && + match->ip_proto != IPPROTO_ICMPV6) { + ds_put_cstr(ds, "invalid L4 fields"); + goto error; + } + uint16_t icmp_id; + char *err; + + if (key[5] == 't') { + err = str_to_u8(value, key, &tuple->icmp_type); + } else if (key[5] == 'c') { + err = str_to_u8(value, key, &tuple->icmp_code); + } else { + err = str_to_u16(value, key, &icmp_id); + tuple->icmp_id = htons(icmp_id); + } + if (err) { + free(err); + goto error_with_msg; + } + } else { + ds_put_format(ds, "invalid conntrack tuple field: %s", key); + goto error; + } + } + + if (!match->ip_proto && (tuple->src_port || tuple->dst_port)) { + ds_put_cstr(ds, "port is set without protocol"); + goto error; + } + + /* For the filtering to work with icmp we need to fill the reply direction + * with correct information. */ + if (match->ip_proto == IPPROTO_ICMP) { + switch (match->tuple_orig.icmp_type) { + case ICMP4_ECHO_REQUEST: + match->tuple_reply.icmp_type = ICMP4_ECHO_REPLY; + break; + case ICMP4_ECHO_REPLY: + match->tuple_reply.icmp_type = ICMP4_ECHO_REQUEST; + break; + case ICMP4_TIMESTAMP: + match->tuple_reply.icmp_type = ICMP4_TIMESTAMPREPLY; + break; + case ICMP4_TIMESTAMPREPLY: + match->tuple_reply.icmp_type = ICMP4_TIMESTAMP; + break; + case ICMP4_INFOREQUEST: + match->tuple_reply.icmp_type = ICMP4_INFOREPLY; + break; + case ICMP4_INFOREPLY: + match->tuple_reply.icmp_type = ICMP4_INFOREQUEST; + break; + } + match->tuple_reply.icmp_id = match->tuple_orig.icmp_id; + } else if (match->ip_proto == IPPROTO_ICMPV6) { + switch (match->tuple_orig.icmp_type) { + case ICMP6_ECHO_REQUEST: + match->tuple_reply.icmp_type = ICMP6_ECHO_REPLY; + break; + case ICMP6_ECHO_REPLY: + match->tuple_reply.icmp_type = ICMP6_ECHO_REQUEST; + break; + } + match->tuple_reply.icmp_id = match->tuple_orig.icmp_id; + } + + free(copy); + return true; + +error_with_msg: + ds_put_format(ds, "failed to parse field %s", key); +error: + free(copy); + return false; +} diff --git a/lib/ofp-ct-util.h b/lib/ofp-ct-util.h new file mode 100644 index 000000000..5a5eaf920 --- /dev/null +++ b/lib/ofp-ct-util.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2022, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVS_OFP_CT_UTIL_H +#define OVS_OFP_CT_UTIL_H + +#include "ct-dpif.h" +#include "openvswitch/ofp-util.h" + +bool ofputil_ct_match_cmp(const struct ofputil_ct_match *match, + const struct ct_dpif_entry *entry); + +bool ofputil_ct_tuple_is_five_tuple(const struct ofputil_ct_tuple *tuple, + uint8_t ip_proto); + +void ofputil_ct_match_format(struct ds *ds, + const struct ofputil_ct_match *match); + +bool ofputil_ct_match_parse(struct ofputil_ct_match *match, const char *s, + struct ds *ds); + +bool ofputil_is_ct_match_zero(const struct ofputil_ct_match *match); + +#endif /* lib/ofp-ct-util.h */ diff --git a/tests/system-traffic.at b/tests/system-traffic.at index e5403519f..51903a658 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -2230,7 +2230,7 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP -AT_SETUP([conntrack - ct flush by 5-tuple]) +AT_SETUP([conntrack - ct flush]) CHECK_CONNTRACK() OVS_TRAFFIC_VSWITCHD_START() @@ -2291,6 +2291,86 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 $ICMP_TUPLE]) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [1], [dnl ]) +dnl Test UDP from port 1 and 2, partial flush by src port +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl +udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=1']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=2']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + +dnl Test UDP from port 1 and 2, partial flush by dst port +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl +udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=2']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=1']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + +dnl Test UDP from port 1 and 2, partial flush by src address +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl +udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.1']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + +dnl Test UDP from port 1 and 2, partial flush by dst address +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl +udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.2']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl +udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 +]) + +AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.1']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP From patchwork Fri Dec 16 12:30:48 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ales Musil X-Patchwork-Id: 1716496 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: legolas.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=aEpXF7Gm; dkim-atps=neutral Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4NYT3m0BLWz2403 for ; Fri, 16 Dec 2022 23:31:16 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 0BAD841A41; Fri, 16 Dec 2022 12:31:13 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 0BAD841A41 Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=aEpXF7Gm X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 0hpYKOVKFX7w; Fri, 16 Dec 2022 12:31:07 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 3564441BE7; Fri, 16 Dec 2022 12:31:05 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 3564441BE7 Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id C99BBC0083; Fri, 16 Dec 2022 12:31:02 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id D690CC002D for ; Fri, 16 Dec 2022 12:30:59 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id BCBA641A3C for ; Fri, 16 Dec 2022 12:30:59 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org BCBA641A3C X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id rKQyLL2q4O4Y for ; Fri, 16 Dec 2022 12:30:57 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org B49AD41A3A Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp4.osuosl.org (Postfix) with ESMTPS id B49AD41A3A for ; Fri, 16 Dec 2022 12:30:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1671193855; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TkrxvgqhtXQE7JoQ2efN/zOJ3TL92O51l75+VjwNiwo=; b=aEpXF7GmnznTe1Z3mVmSjZ65sVyh/bwNVA4+vYq8bDPD8em/fz62OUUYoWMJ+l5ppLA4kC J/E1E1l99vbIMqnuuFtvejEr115eU3+t15Ez+nf0Rqa9ke4/VQrwGk8n95SfEVRHoiamte 8RCehxfGAenuTKiz7JJh1qe9IqSKMl0= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-554-ZcKqeGnNNwS_WuiMduADhw-1; Fri, 16 Dec 2022 07:30:54 -0500 X-MC-Unique: ZcKqeGnNNwS_WuiMduADhw-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id E287888606B; Fri, 16 Dec 2022 12:30:53 +0000 (UTC) Received: from amusil.redhat.com (ovpn-192-198.brq.redhat.com [10.40.192.198]) by smtp.corp.redhat.com (Postfix) with ESMTP id B3D531121314; Fri, 16 Dec 2022 12:30:52 +0000 (UTC) From: Ales Musil To: dev@openvswitch.org Date: Fri, 16 Dec 2022 13:30:48 +0100 Message-Id: <20221216123048.973406-3-amusil@redhat.com> In-Reply-To: <20221216123048.973406-1-amusil@redhat.com> References: <20221216123048.973406-1-amusil@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Cc: i.maximets@ovn.org Subject: [ovs-dev] [PATCH v5 2/2] openflow: Add extension to flush CT by generic match 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" Add extension that allows to flush connections from CT by specifying fields that the connections should be matched against. This allows to match only some fields of the connection e.g. source address for orig direrction. Reported-at: https://bugzilla.redhat.com/2120546 Signed-off-by: Ales Musil Acked-by: Paolo Valerio --- v5: Add missing usage and man for ovs-ofctl command. v4: Allow ovs-ofctl flush/conntrack without any zone/tuple. v3: Rebase on top of master. v2: Rebase on top of master. Use suggestion from Ilya. --- NEWS | 6 ++ include/openflow/nicira-ext.h | 30 +++++++ include/openvswitch/ofp-msgs.h | 4 + include/openvswitch/ofp-util.h | 4 + lib/ofp-bundle.c | 1 + lib/ofp-ct-util.c | 146 +++++++++++++++++++++++++++++++++ lib/ofp-ct-util.h | 9 ++ lib/ofp-print.c | 20 +++++ lib/ofp-util.c | 25 ++++++ lib/rconn.c | 1 + ofproto/ofproto-dpif.c | 8 +- ofproto/ofproto-provider.h | 7 +- ofproto/ofproto.c | 30 ++++++- tests/ofp-print.at | 93 +++++++++++++++++++++ tests/ovs-ofctl.at | 26 ++++++ tests/system-traffic.at | 116 ++++++++++++++------------ utilities/ovs-ofctl.8.in | 20 +++++ utilities/ovs-ofctl.c | 40 +++++++++ 18 files changed, 528 insertions(+), 58 deletions(-) diff --git a/NEWS b/NEWS index ff8904b02..ac73d6293 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,12 @@ Post-v3.0.0 by specifying 'max-rate' or '[r]stp-path-cost' accordingly. - ovs-dpctl and related ovs-appctl commands: * "flush-conntrack" is capable of handling partial 5-tuple. + - OpenFlow: + * New OpenFlow extension NXT_CT_FLUSH to flush connections matching + the specified fields. + - ovs-ofctl: + * New command "flush-conntrack" that accepts zone and 5-tuple or partial + 5-tuple. v3.0.0 - 15 Aug 2022 diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index b68804991..32ce56d31 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -1064,4 +1064,34 @@ struct nx_zone_id { }; OFP_ASSERT(sizeof(struct nx_zone_id) == 8); +/* CT flush available TLVs. */ +enum nx_ct_flush_tlv_type { + /* Outer types. */ + NXT_CT_ORIG_DIRECTION, /* CT orig direction outer type. */ + NXT_CT_REPLY_DIRECTION, /* CT reply direction outer type. */ + + /* Nested types. */ + NXT_CT_SRC, /* CT source IPv6 or mapped IPv4 address. */ + NXT_CT_DST, /* CT destination IPv6 or mapped IPv4 address. */ + NXT_CT_SRC_PORT, /* CT source port. */ + NXT_CT_DST_PORT, /* CT destination port. */ + NXT_CT_ICMP_ID, /* CT ICMP id. */ + NXT_CT_ICMP_TYPE, /* CT ICMP type. */ + NXT_CT_ICMP_CODE, /* CT ICMP code. */ + + /* Primitive types. */ + NXT_CT_ZONE_ID, /* CT zone id. */ +}; + +/* NXT_CT_FLUSH. + * + * Flushes the connection tracking specified by 5-tuple. + * The struct should be followed by TLVs specifying the matching parameters. */ +struct nx_ct_flush { + uint8_t ip_proto; /* IP protocol. */ + uint8_t family; /* L3 address family. */ + uint8_t zero[6]; /* Must be zero. */ +}; +OFP_ASSERT(sizeof(struct nx_ct_flush) == 8); + #endif /* openflow/nicira-ext.h */ diff --git a/include/openvswitch/ofp-msgs.h b/include/openvswitch/ofp-msgs.h index 921a937e5..659b0a3e7 100644 --- a/include/openvswitch/ofp-msgs.h +++ b/include/openvswitch/ofp-msgs.h @@ -526,6 +526,9 @@ enum ofpraw { /* NXST 1.0+ (4): struct nx_ipfix_stats_reply[]. */ OFPRAW_NXST_IPFIX_FLOW_REPLY, + + /* NXT 1.0+ (32): struct nx_ct_flush, uint8_t[8][]. */ + OFPRAW_NXT_CT_FLUSH, }; /* Decoding messages into OFPRAW_* values. */ @@ -772,6 +775,7 @@ enum ofptype { OFPTYPE_IPFIX_FLOW_STATS_REQUEST, /* OFPRAW_NXST_IPFIX_FLOW_REQUEST */ OFPTYPE_IPFIX_FLOW_STATS_REPLY, /* OFPRAW_NXST_IPFIX_FLOW_REPLY */ OFPTYPE_CT_FLUSH_ZONE, /* OFPRAW_NXT_CT_FLUSH_ZONE. */ + OFPTYPE_CT_FLUSH, /* OFPRAW_NXT_CT_FLUSH. */ /* Flow monitor extension. */ OFPTYPE_FLOW_MONITOR_CANCEL, /* OFPRAW_NXT_FLOW_MONITOR_CANCEL. diff --git a/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h index 84937ae26..e10d90b9f 100644 --- a/include/openvswitch/ofp-util.h +++ b/include/openvswitch/ofp-util.h @@ -65,6 +65,10 @@ struct ofpbuf *ofputil_encode_echo_reply(const struct ofp_header *); struct ofpbuf *ofputil_encode_barrier_request(enum ofp_version); +struct ofpbuf *ofputil_ct_match_encode(const struct ofputil_ct_match *match, + uint16_t *zone_id, + enum ofp_version version); + #ifdef __cplusplus } #endif diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c index 0161c2bc6..941a8370e 100644 --- a/lib/ofp-bundle.c +++ b/lib/ofp-bundle.c @@ -292,6 +292,7 @@ ofputil_is_bundlable(enum ofptype type) case OFPTYPE_IPFIX_FLOW_STATS_REQUEST: case OFPTYPE_IPFIX_FLOW_STATS_REPLY: case OFPTYPE_CT_FLUSH_ZONE: + case OFPTYPE_CT_FLUSH: break; } diff --git a/lib/ofp-ct-util.c b/lib/ofp-ct-util.c index a10607590..a46798582 100644 --- a/lib/ofp-ct-util.c +++ b/lib/ofp-ct-util.c @@ -23,8 +23,12 @@ #include "ct-dpif.h" #include "ofp-ct-util.h" +#include "openflow/nicira-ext.h" #include "openvswitch/dynamic-string.h" +#include "openvswitch/ofp-msgs.h" #include "openvswitch/ofp-parse.h" +#include "openvswitch/ofp-errors.h" +#include "openvswitch/ofp-prop.h" #include "openvswitch/ofp-util.h" #include "openvswitch/packets.h" @@ -324,3 +328,145 @@ error: free(copy); return false; } + +static enum ofperr +ofpprop_pull_value(struct ofpbuf *property, void *value, size_t len) +{ + if (ofpbuf_msgsize(property) < len) { + return OFPERR_OFPBPC_BAD_LEN; + } + + memcpy(value, property->msg, len); + + return 0; +} + +static enum ofperr +ofputil_ct_tuple_decode_nested(struct ofpbuf *property, + struct ofputil_ct_tuple *tuple) +{ + struct ofpbuf nested; + enum ofperr error = ofpprop_parse_nested(property, &nested); + if (error) { + return error; + } + + while (nested.size) { + struct ofpbuf inner; + uint64_t type; + + error = ofpprop_pull(&nested, &inner, &type); + if (error) { + return error; + } + switch (type) { + case NXT_CT_SRC: + ofpprop_pull_value(&inner, &tuple->src, sizeof tuple->src); + break; + case NXT_CT_DST: + ofpprop_pull_value(&inner, &tuple->dst, sizeof tuple->dst); + break; + case NXT_CT_SRC_PORT: + ofpprop_parse_be16(&inner, &tuple->src_port); + break; + case NXT_CT_DST_PORT: + ofpprop_parse_be16(&inner, &tuple->dst_port); + break; + case NXT_CT_ICMP_ID: + ofpprop_parse_be16(&inner, &tuple->icmp_id); + break; + case NXT_CT_ICMP_TYPE: + ofpprop_parse_u8(&inner, &tuple->icmp_type); + break; + case NXT_CT_ICMP_CODE: + ofpprop_parse_u8(&inner, &tuple->icmp_code); + } + } + + return 0; +} + +enum ofperr +ofputil_ct_match_decode(struct ofputil_ct_match *match, bool *with_zone, + uint16_t *zone_id, const struct ofp_header *oh) +{ + struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length)); + ofpraw_pull_assert(&msg); + + const struct nx_ct_flush *nx_flush = ofpbuf_pull(&msg, sizeof *nx_flush); + + if (!is_all_zeros(nx_flush->zero, sizeof nx_flush->zero)) { + return OFPERR_NXBRC_MUST_BE_ZERO; + } + + match->l3_type = nx_flush->family; + match->ip_proto = nx_flush->ip_proto; + + struct ofputil_ct_tuple *orig = &match->tuple_orig; + struct ofputil_ct_tuple *reply = &match->tuple_reply; + + while (msg.size) { + struct ofpbuf property; + uint64_t type; + + enum ofperr error = ofpprop_pull(&msg, &property, &type); + if (error) { + return error; + } + + switch (type) { + case NXT_CT_ORIG_DIRECTION: + ofputil_ct_tuple_decode_nested(&property, orig); + break; + case NXT_CT_REPLY_DIRECTION: + ofputil_ct_tuple_decode_nested(&property, reply); + break; + case NXT_CT_ZONE_ID: + if (with_zone) { + *with_zone = true; + } + ofpprop_parse_u16(&property, zone_id); + break; + } + } + + return 0; +} + +void +ofputil_ct_tuple_encode(const struct ofputil_ct_tuple *tuple, + struct ofpbuf *buf, enum nx_ct_flush_tlv_type type, + uint8_t ip_proto) +{ + /* 128 B is enough to hold the whole tuple. */ + uint8_t stub[128]; + struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub); + + if (!ipv6_is_zero(&tuple->src)) { + ofpprop_put(&nested, NXT_CT_SRC, &tuple->src, sizeof tuple->src); + } + + if (!ipv6_is_zero(&tuple->dst)) { + ofpprop_put(&nested, NXT_CT_DST, &tuple->dst, sizeof tuple->dst); + } + + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + ofpprop_put_be16(&nested, NXT_CT_ICMP_ID, tuple->icmp_id); + ofpprop_put_u8(&nested, NXT_CT_ICMP_TYPE, tuple->icmp_type); + ofpprop_put_u8(&nested, NXT_CT_ICMP_CODE, tuple->icmp_code); + } else { + if (tuple->src_port) { + ofpprop_put_be16(&nested, NXT_CT_SRC_PORT, tuple->src_port); + } + + if (tuple->dst_port) { + ofpprop_put_be16(&nested, NXT_CT_DST_PORT, tuple->dst_port); + } + } + + if (nested.size) { + ofpprop_put_nested(buf, type, &nested); + } + + ofpbuf_uninit(&nested); +} diff --git a/lib/ofp-ct-util.h b/lib/ofp-ct-util.h index 5a5eaf920..f4e4f812e 100644 --- a/lib/ofp-ct-util.h +++ b/lib/ofp-ct-util.h @@ -17,6 +17,7 @@ #define OVS_OFP_CT_UTIL_H #include "ct-dpif.h" +#include "openflow/nicira-ext.h" #include "openvswitch/ofp-util.h" bool ofputil_ct_match_cmp(const struct ofputil_ct_match *match, @@ -33,4 +34,12 @@ bool ofputil_ct_match_parse(struct ofputil_ct_match *match, const char *s, bool ofputil_is_ct_match_zero(const struct ofputil_ct_match *match); +enum ofperr ofputil_ct_match_decode(struct ofputil_ct_match *match, + bool *with_zone, uint16_t *zone_id, + const struct ofp_header *oh); + +void ofputil_ct_tuple_encode(const struct ofputil_ct_tuple *tuple, + struct ofpbuf *buf, + enum nx_ct_flush_tlv_type type, uint8_t ip_proto); + #endif /* lib/ofp-ct-util.h */ diff --git a/lib/ofp-print.c b/lib/ofp-print.c index bd37fa17a..94bfa7070 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -36,6 +36,7 @@ #include "learn.h" #include "multipath.h" #include "netdev.h" +#include "ofp-ct-util.h" #include "nx-match.h" #include "odp-util.h" #include "openflow/nicira-ext.h" @@ -949,6 +950,23 @@ ofp_print_nxt_ct_flush_zone(struct ds *string, const struct nx_zone_id *nzi) return 0; } +static enum ofperr +ofp_print_nxt_ct_flush(struct ds *string, const struct ofp_header *oh) +{ + uint16_t zone_id = 0; + struct ofputil_ct_match match = {0}; + + enum ofperr error = ofputil_ct_match_decode(&match, NULL, &zone_id, oh); + if (error) { + return error; + } + + ofputil_ct_match_format(string, &match); + ds_put_format(string, ",zone_id=%"PRIu16, zone_id); + + return 0; +} + static enum ofperr ofp_to_string__(const struct ofp_header *oh, const struct ofputil_port_map *port_map, @@ -1184,6 +1202,8 @@ ofp_to_string__(const struct ofp_header *oh, case OFPTYPE_CT_FLUSH_ZONE: return ofp_print_nxt_ct_flush_zone(string, ofpmsg_body(oh)); + case OFPTYPE_CT_FLUSH: + return ofp_print_nxt_ct_flush(string, oh); } return 0; diff --git a/lib/ofp-util.c b/lib/ofp-util.c index a324ceeea..a14cb6860 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -31,6 +31,7 @@ #include "multipath.h" #include "netdev.h" #include "nx-match.h" +#include "ofp-ct-util.h" #include "id-pool.h" #include "openflow/netronome-ext.h" #include "openvswitch/dynamic-string.h" @@ -237,3 +238,27 @@ ofputil_encode_barrier_request(enum ofp_version ofp_version) return ofpraw_alloc(type, ofp_version, 0); } + +struct ofpbuf * +ofputil_ct_match_encode(const struct ofputil_ct_match *match, + uint16_t *zone_id, enum ofp_version version) +{ + struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH, version, 0); + struct nx_ct_flush *nx_flush = ofpbuf_put_zeros(msg, sizeof *nx_flush); + const struct ofputil_ct_tuple *orig = &match->tuple_orig; + const struct ofputil_ct_tuple *reply = &match->tuple_reply; + + nx_flush->ip_proto = match->ip_proto; + nx_flush->family = match->l3_type; + + ofputil_ct_tuple_encode(orig, msg, NXT_CT_ORIG_DIRECTION, + match->ip_proto); + ofputil_ct_tuple_encode(reply, msg, NXT_CT_REPLY_DIRECTION, + match->ip_proto); + + if (zone_id) { + ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id); + } + + return msg; +} diff --git a/lib/rconn.c b/lib/rconn.c index a96b2eb8b..4afa21515 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -1426,6 +1426,7 @@ is_admitted_msg(const struct ofpbuf *b) case OFPTYPE_IPFIX_FLOW_STATS_REQUEST: case OFPTYPE_IPFIX_FLOW_STATS_REPLY: case OFPTYPE_CT_FLUSH_ZONE: + case OFPTYPE_CT_FLUSH: default: return true; } diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index f9562dee8..29174a585 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -5358,11 +5358,12 @@ type_set_config(const char *type, const struct smap *other_config) } static void -ct_flush(const struct ofproto *ofproto_, const uint16_t *zone) +ct_flush(const struct ofproto *ofproto_, const uint16_t *zone, + const struct ofputil_ct_match *match) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); - ct_dpif_flush(ofproto->backer->dpif, zone, NULL); + ct_dpif_flush(ofproto->backer->dpif, zone, match); } static struct ct_timeout_policy * @@ -5674,6 +5675,9 @@ get_datapath_cap(const char *datapath_type, struct smap *cap) smap_add(cap, "lb_output_action", s.lb_output_action ? "true" : "false"); smap_add(cap, "ct_zero_snat", s.ct_zero_snat ? "true" : "false"); smap_add(cap, "add_mpls", s.add_mpls ? "true" : "false"); + /* The ct_tuple_flush is implemented on dpif level, so it is supported + * for all backers. */ + smap_add(cap, "ct_flush", "true"); } /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index 7e3fb6698..5e39234f9 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -49,6 +49,7 @@ #include "openvswitch/ofp-port.h" #include "openvswitch/ofp-switch.h" #include "openvswitch/ofp-table.h" +#include "openvswitch/ofp-util.h" #include "ovs-atomic.h" #include "ovs-rcu.h" #include "ovs-thread.h" @@ -1902,8 +1903,10 @@ struct ofproto_class { /* ## Connection tracking ## */ /* ## ------------------- ## */ /* Flushes the connection tracking tables. If 'zone' is not NULL, - * only deletes connections in '*zone'. */ - void (*ct_flush)(const struct ofproto *, const uint16_t *zone); + * only deletes connections in '*zone'. If 'match' is not NULL, + * deletes connections specified by the match. */ + void (*ct_flush)(const struct ofproto *, const uint16_t *zone, + const struct ofputil_ct_match *match); /* Sets conntrack timeout policy specified by 'timeout_policy' to 'zone' * in datapath type 'dp_type'. */ diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 3a527683c..c9b222994 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -34,6 +34,7 @@ #include "openvswitch/hmap.h" #include "netdev.h" #include "nx-match.h" +#include "ofp-ct-util.h" #include "ofproto.h" #include "ofproto-provider.h" #include "openflow/nicira-ext.h" @@ -934,7 +935,31 @@ handle_nxt_ct_flush_zone(struct ofconn *ofconn, const struct ofp_header *oh) uint16_t zone = ntohs(nzi->zone_id); if (ofproto->ofproto_class->ct_flush) { - ofproto->ofproto_class->ct_flush(ofproto, &zone); + ofproto->ofproto_class->ct_flush(ofproto, &zone, NULL); + } else { + return EOPNOTSUPP; + } + + return 0; +} + +static enum ofperr +handle_nxt_ct_flush(struct ofconn *ofconn, const struct ofp_header *oh) +{ + struct ofproto *ofproto = ofconn_get_ofproto(ofconn); + struct ofputil_ct_match match = {0}; + bool with_zone = false; + uint16_t zone_id = 0; + + enum ofperr error = + ofputil_ct_match_decode(&match, &with_zone, &zone_id, oh); + if (error) { + return error; + } + + if (ofproto->ofproto_class->ct_flush) { + ofproto->ofproto_class->ct_flush(ofproto, with_zone ? &zone_id : NULL, + &match); } else { return EOPNOTSUPP; } @@ -8787,6 +8812,9 @@ handle_single_part_openflow(struct ofconn *ofconn, const struct ofp_header *oh, case OFPTYPE_CT_FLUSH_ZONE: return handle_nxt_ct_flush_zone(ofconn, oh); + case OFPTYPE_CT_FLUSH: + return handle_nxt_ct_flush(ofconn, oh); + case OFPTYPE_HELLO: case OFPTYPE_ERROR: case OFPTYPE_FEATURES_REPLY: diff --git a/tests/ofp-print.at b/tests/ofp-print.at index fe41cc42c..7c6a86133 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -4073,3 +4073,96 @@ AT_CHECK([ovs-ofctl ofp-print "\ NXT_CT_FLUSH_ZONE (xid=0x3): zone_id=13 ]) AT_CLEANUP + +AT_SETUP([NXT_CT_FLUSH]) +AT_KEYWORDS([ofp-print]) +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 18 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +02 \ +00 00 00 00 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=2,ip_proto=6,orig=(src=::,dst=::,src_port=0,dst_port=0),reply=(src=::,dst=::,src_port=0,dst_port=0),zone_id=0 +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 20 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +02 \ +00 00 00 00 00 00 \ +00 09 00 08 00 0d 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=2,ip_proto=6,orig=(src=::,dst=::,src_port=0,dst_port=0),reply=(src=::,dst=::,src_port=0,dst_port=0),zone_id=13 +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +02 \ +00 00 00 00 00 00 \ +00 09 00 08 00 0d 00 00 \ +00 00 00 48 00 00 00 00 \ +00 02 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 03 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 04 00 08 00 50 00 00 \ +00 05 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=2,ip_proto=6,orig=(src=10.10.0.1,dst=10.10.0.2,src_port=80,dst_port=8080),reply=(src=::,dst=::,src_port=0,dst_port=0),zone_id=13 +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +02 \ +00 00 00 00 00 00 \ +00 09 00 08 00 0d 00 00 \ +00 01 00 48 00 00 00 00 \ +00 03 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 02 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 05 00 08 00 50 00 00 \ +00 04 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=2,ip_proto=6,orig=(src=::,dst=::,src_port=0,dst_port=0),reply=(src=10.10.0.2,dst=10.10.0.1,src_port=8080,dst_port=80),zone_id=13 +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 b0 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +02 \ +00 00 00 00 00 00 \ +00 09 00 08 00 0d 00 00 \ +00 00 00 48 00 00 00 00 \ +00 02 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 03 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 04 00 08 00 50 00 00 \ +00 05 00 08 1f 90 00 00 \ +00 01 00 48 00 00 00 00 \ +00 03 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 02 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 05 00 08 00 50 00 00 \ +00 04 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=2,ip_proto=6,orig=(src=10.10.0.1,dst=10.10.0.2,src_port=80,dst_port=8080),reply=(src=10.10.0.2,dst=10.10.0.1,src_port=8080,dst_port=80),zone_id=13 +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 b8 00 00 00 03 00 00 23 20 00 00 00 20 \ +01 \ +0a \ +00 00 00 00 00 00 \ +00 00 00 50 00 00 00 00 \ +00 02 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \ +00 03 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \ +00 06 00 08 00 0a 00 00 \ +00 07 00 05 01 00 00 00 \ +00 08 00 05 02 00 00 00 \ +00 01 00 50 00 00 00 00 \ +00 02 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \ +00 02 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \ +00 06 00 08 00 0a 00 00 \ +00 07 00 05 03 00 00 00 \ +00 08 00 05 04 00 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): l3_type=10,ip_proto=1,orig=(src=fd18::ffff:abcd:1,dst=fd18::ffff:abcd:2,icmp_id=10,icmp_type=1,icmp_code=2),reply=(src=fd18::ffff:abcd:1,dst=::,icmp_id=10,icmp_type=3,icmp_code=4),zone_id=0 +]) +AT_CLEANUP diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index a8934051e..a14912768 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -3271,3 +3271,29 @@ AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip | sed '/OFPST_FLO OVS_VSWITCHD_STOP(["/Flow exceeded the maximum flow statistics reply size and was excluded from the response set/d"]) AT_CLEANUP + +AT_SETUP([ovs-ofctl ct - flush-conntrack]) +OVS_VSWITCHD_START + +AT_CHECK([ovs-appctl vlog/set ct_dpif:dbg]) + +# Check flush conntrack with both zone and tuple +AT_CHECK([ovs-ofctl flush-conntrack br0 zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 1]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: l3_type=2,ip_proto=17,orig=(src=10.1.1.1,dst=10.1.1.2,src_port=1,dst_port=0),reply=(src=::,dst=::,src_port=0,dst_port=0) in zone 5" ovs-vswitchd.log]) + +# Check flush-conntrack just with tuple +AT_CHECK([ovs-ofctl flush-conntrack br0 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 2]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: l3_type=2,ip_proto=17,orig=(src=10.1.1.3,dst=10.1.1.4,src_port=1,dst_port=0),reply=(src=::,dst=::,src_port=0,dst_port=0) in zone 0" ovs-vswitchd.log]) + +# Check flush-conntrack without any tuple and zone +AT_CHECK([ovs-ofctl flush-conntrack br0]) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 3]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: " ovs-vswitchd.log]) + +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/system-traffic.at b/tests/system-traffic.at index 51903a658..396d81ad9 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -2250,126 +2250,136 @@ priority=100,in_port=2,icmp,action=ct(zone=5,commit),1 AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt]) -dnl Test UDP from port 1 -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) +flush_conntrack() { + if [[ "$1" == "dpctl" ]]; then + AT_CHECK([ovs-appctl dpctl/flush-conntrack ${@:2}]) + else + AT_CHECK([ovs-ofctl flush-conntrack br0 ${@:2}]) + fi +} -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [], [dnl +for type in dpctl ofctl; do + AS_BOX([Testing with $type command]) + + dnl Test UDP from port 1 + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) + + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [], [dnl udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1']) + AT_CHECK([flush_conntrack $type 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [1], [dnl -]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [1]) -dnl Test UDP from port 2 -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [0], [dnl + dnl Test UDP from port 2 + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2']) + AT_CHECK([flush_conntrack $type zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl -]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0]) -dnl Test ICMP traffic -NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 | FORMAT_PING], [0], [dnl + dnl Test ICMP traffic + NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 | FORMAT_PING], [0], [dnl 3 packets transmitted, 3 received, 0% packet loss, time 0ms ]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [0], [stdout]) -AT_CHECK([cat stdout | FORMAT_CT(10.1.1.1)], [0],[dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [0], [stdout]) + AT_CHECK([cat stdout | FORMAT_CT(10.1.1.1)], [0],[dnl icmp,orig=(src=10.1.1.2,dst=10.1.1.1,id=,type=8,code=0),reply=(src=10.1.1.1,dst=10.1.1.2,id=,type=0,code=0),zone=5 ]) -ICMP_ID=`cat stdout | cut -d ',' -f4 | cut -d '=' -f2` -ICMP_TUPLE=ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=1,icmp_id=$ICMP_ID,icmp_type=8,icmp_code=0 -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 $ICMP_TUPLE]) + ICMP_ID=`cat stdout | cut -d ',' -f4 | cut -d '=' -f2` + ICMP_TUPLE=ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=1,icmp_id=$ICMP_ID,icmp_type=8,icmp_code=0 + AT_CHECK([flush_conntrack $type zone=5 $ICMP_TUPLE]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [1], [dnl -]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [1]) -dnl Test UDP from port 1 and 2, partial flush by src port -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + dnl Test UDP from port 1 and 2, partial flush by src port + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=1']) + AT_CHECK([flush_conntrack $type 'ct_nw_proto=17,ct_tp_src=1']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=2']) + AT_CHECK([flush_conntrack $type 'ct_nw_proto=17,ct_tp_src=2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) -dnl Test UDP from port 1 and 2, partial flush by dst port -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + dnl Test UDP from port 1 and 2, partial flush by dst port + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=2']) + AT_CHECK([flush_conntrack $type 'ct_nw_proto=17,ct_tp_dst=2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=1']) + AT_CHECK([flush_conntrack $type 'ct_nw_proto=17,ct_tp_dst=1']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) -dnl Test UDP from port 1 and 2, partial flush by src address -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + dnl Test UDP from port 1 and 2, partial flush by src address + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.1']) + AT_CHECK([flush_conntrack $type 'ct_nw_src=10.1.1.1']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2']) + AT_CHECK([flush_conntrack $type 'ct_nw_src=10.1.1.2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) -dnl Test UDP from port 1 and 2, partial flush by dst address -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) + dnl Test UDP from port 1 and 2, partial flush by dst address + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) + AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], [dnl udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.2']) + AT_CHECK([flush_conntrack $type 'ct_nw_dst=10.1.1.2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.1']) + AT_CHECK([flush_conntrack $type 'ct_nw_dst=10.1.1.1']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) + AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) +done OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 10a6a64de..35dd5c8b0 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -296,6 +296,26 @@ Flushes the connection tracking entries in \fIzone\fR on \fIswitch\fR. This command uses an Open vSwitch extension that is only in Open vSwitch 2.6 and later. . +.IP "\fBflush\-conntrack \fIswitch [[zone=N] ct-tuple]\fR +Flushes the connection entries in the tracker \fIzone\fR and +connection tracking tuple \fIct-tuple\fR. +If \fIct-tuple\fR is not provided, flushes all the connection entries. +If \fIzone\fR is specified, only flushes the connections in +\fIzone\fR. +.IP +If \fIct-tuple\fR is provided, flushes the connection entry specified by +\fIct-tuple\fR in \fIzone\fR. The zone defaults to 0 if it is not provided. +The userspace connection tracker requires flushing with the original pre-NATed +tuple and a warning log will be otherwise generated. The tuple can be partial +and will remove all connections that are matching on the specified fields. +An example of an IPv4 ICMP \fIct-tuple\fR: +.IP +"ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=1,icmp_type=8,icmp_code=0,icmp_id=10" +.IP +An example of an IPv6 TCP \fIct-tuple\fR: +.IP +"ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2,ct_nw_proto=6,ct_tp_src=1,ct_tp_dst=2" +. .SS "OpenFlow Switch Flow Table Commands" . These commands manage the flow table in an OpenFlow switch. In each diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index fe9114580..b0fac6e03 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -40,6 +40,7 @@ #include "fatal-signal.h" #include "nx-match.h" #include "odp-util.h" +#include "ofp-ct-util.h" #include "ofp-version-opt.h" #include "ofproto/ofproto.h" #include "openflow/nicira-ext.h" @@ -485,6 +486,8 @@ usage(void) " dump-ipfix-bridge SWITCH print ipfix stats of bridge\n" " dump-ipfix-flow SWITCH print flow ipfix of a bridge\n" " ct-flush-zone SWITCH ZONE flush conntrack entries in ZONE\n" + " flush-conntrack SWITCH [[ZONE=N] CT_TUPLE] flush conntrack" + " entries specified by CT_TUPLE and ZONE\n" "\nFor OpenFlow switches and controllers:\n" " probe TARGET probe whether TARGET is up\n" " ping TARGET [N] latency of N-byte echos\n" @@ -3050,6 +3053,40 @@ ofctl_ct_flush_zone(struct ovs_cmdl_context *ctx) vconn_close(vconn); } +static void +ofctl_ct_flush_conntrack(struct ovs_cmdl_context *ctx) +{ + struct vconn *vconn; + struct ofputil_ct_match match = {0}; + struct ds ds = DS_EMPTY_INITIALIZER; + uint16_t zone; + bool with_zone = false; + int args = ctx->argc - 2; + + /* Parse ct tuple */ + if (args) { + if (!ofputil_ct_match_parse(&match, ctx->argv[ctx->argc - 1], &ds)) { + ovs_fatal(0, "Failed to parse ct-tuple: %s", ds_cstr(&ds)); + } + args--; + } + + /* Parse zone */ + if (args && ovs_scan(ctx->argv[ctx->argc - 2], "zone=%"SCNu16, &zone)) { + with_zone = true; + } + + open_vconn(ctx->argv[1], &vconn); + enum ofp_version version = vconn_get_version(vconn); + + struct ofpbuf *msg = + ofputil_ct_match_encode(&match, with_zone ? &zone : NULL, version); + + ds_destroy(&ds); + transact_noreply(vconn, msg); + vconn_close(vconn); +} + static void ofctl_dump_ipfix_flow(struct ovs_cmdl_context *ctx) { @@ -5063,6 +5100,9 @@ static const struct ovs_cmdl_command all_commands[] = { { "ct-flush-zone", "switch zone", 2, 2, ofctl_ct_flush_zone, OVS_RO }, + { "flush-conntrack", "switch [[zone=N] ct-tuple]", + 1, 3, ofctl_ct_flush_conntrack, OVS_RO }, + { "ofp-parse", "file", 1, 1, ofctl_ofp_parse, OVS_RW }, { "ofp-parse-pcap", "pcap",