2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-31 14:25:26 +00:00

ofp, dpif: Allow CT flush based on partial match.

Currently, the CT can be flushed by dpctl only by 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
ofp_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 <amusil@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
This commit is contained in:
Ales Musil
2023-01-16 12:45:07 +01:00
committed by Ilya Maximets
parent de3bbdc479
commit a9ae73b916
10 changed files with 617 additions and 143 deletions

3
NEWS
View File

@@ -25,6 +25,9 @@ Post-v3.0.0
* New option '--dump-hugepages' to include hugepages in core dumps. This * New option '--dump-hugepages' to include hugepages in core dumps. This
can assist with postmortem analysis involving DPDK, but may also produce can assist with postmortem analysis involving DPDK, but may also produce
significantly larger core dump files. significantly larger core dump files.
- ovs-dpctl and 'ovs-appctl dpctl/' commands:
* 'flush-conntrack' is now capable of handling partial 5-tuple,
with additional optional parameter to specify the reply direction.
- Support for travis-ci.org based continuous integration builds has been - Support for travis-ci.org based continuous integration builds has been
dropped. dropped.
- Userspace datapath: - Userspace datapath:

View File

@@ -15,6 +15,7 @@ openvswitchinclude_HEADERS = \
include/openvswitch/ofp-actions.h \ include/openvswitch/ofp-actions.h \
include/openvswitch/ofp-bundle.h \ include/openvswitch/ofp-bundle.h \
include/openvswitch/ofp-connection.h \ include/openvswitch/ofp-connection.h \
include/openvswitch/ofp-ct.h \
include/openvswitch/ofp-ed-props.h \ include/openvswitch/ofp-ed-props.h \
include/openvswitch/ofp-errors.h \ include/openvswitch/ofp-errors.h \
include/openvswitch/ofp-flow.h \ include/openvswitch/ofp-flow.h \

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2023, 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 OPENVSWITCH_OFP_CT_H
#define OPENVSWITCH_OFP_CT_H 1
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
struct ofp_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 ofp_ct_match {
uint8_t ip_proto;
uint16_t l3_type;
struct ofp_ct_tuple tuple_orig;
struct ofp_ct_tuple tuple_reply;
};
bool ofp_ct_match_is_zero(const struct ofp_ct_match *);
bool ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *, uint8_t ip_proto);
bool ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *, uint8_t ip_proto);
void ofp_ct_match_format(struct ds *, const struct ofp_ct_match *);
bool ofp_ct_tuple_parse(struct ofp_ct_tuple *, const char *,
struct ds *, uint8_t *ip_proto, uint16_t *l3_type);
#ifdef __cplusplus
}
#endif
#endif /* ofp-ct.h */

View File

@@ -226,6 +226,7 @@ lib_libopenvswitch_la_SOURCES = \
lib/ofp-actions.c \ lib/ofp-actions.c \
lib/ofp-bundle.c \ lib/ofp-bundle.c \
lib/ofp-connection.c \ lib/ofp-connection.c \
lib/ofp-ct.c \
lib/ofp-ed-props.c \ lib/ofp-ed-props.c \
lib/ofp-errors.c \ lib/ofp-errors.c \
lib/ofp-flow.c \ lib/ofp-flow.c \

View File

@@ -20,6 +20,7 @@
#include <errno.h> #include <errno.h>
#include "ct-dpif.h" #include "ct-dpif.h"
#include "openvswitch/ofp-ct.h"
#include "openvswitch/ofp-parse.h" #include "openvswitch/ofp-parse.h"
#include "openvswitch/vlog.h" #include "openvswitch/vlog.h"
@@ -101,24 +102,191 @@ ct_dpif_dump_done(struct ct_dpif_dump_state *dump)
: EOPNOTSUPP); : EOPNOTSUPP);
} }
/* Flushing. */
static void
ct_dpif_tuple_from_ofp_ct_tuple(const struct ofp_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;
}
}
static inline bool
ct_dpif_inet_addr_cmp_partial(const union ct_dpif_inet_addr *addr,
const struct in6_addr *partial, 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
ct_dpif_tuple_ip_cmp_partial(const struct ct_dpif_tuple *tuple,
const struct ofp_ct_tuple *partial,
uint16_t l3_type, uint8_t ip_proto)
{
if (!ct_dpif_inet_addr_cmp_partial(&tuple->src, &partial->src, l3_type)) {
return false;
}
if (!ct_dpif_inet_addr_cmp_partial(&tuple->dst, &partial->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;
}
/* Returns 'true' if all non-zero members of 'match' equal to corresponding
* members of 'entry'. */
static bool
ct_dpif_entry_cmp(const struct ct_dpif_entry *entry,
const struct ofp_ct_match *match)
{
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 (!ct_dpif_tuple_ip_cmp_partial(&entry->tuple_orig, &match->tuple_orig,
match->l3_type, match->ip_proto)) {
return false;
}
if (!ct_dpif_tuple_ip_cmp_partial(&entry->tuple_reply, &match->tuple_reply,
match->l3_type, match->ip_proto)) {
return false;
}
return true;
}
static int
ct_dpif_flush_tuple(struct dpif *dpif, const uint16_t *zone,
const struct ofp_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;
ofp_ct_match_format(&ds, match);
VLOG_DBG("%s: ct_flush: zone=%d %s", dpif_name(dpif), zone ? *zone : 0,
ds_cstr(&ds));
ds_destroy(&ds);
}
/* If we have full five tuple in original and empty reply tuple just
* do the flush over original tuple directly. */
if (ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto)) {
struct ct_dpif_tuple tuple;
ct_dpif_tuple_from_ofp_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 (ct_dpif_entry_cmp(&cte, match)) {
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 /* Flush the entries in the connection tracker used by 'dpif'. The
* arguments have the following behavior: * arguments have the following behavior:
* *
* - If both 'zone' and 'tuple' are NULL, flush all the conntrack entries. * - If both 'zone' is NULL and 'match' is NULL or zero, flush all the
* - If 'zone' is not NULL, and 'tuple' is NULL, flush all the conntrack * conntrack entries.
* - If 'zone' is not NULL, and 'match' is NULL, flush all the conntrack
* entries in '*zone'. * entries in '*zone'.
* - If 'tuple' is not NULL, flush the conntrack entry specified by 'tuple' * - If 'match' is not NULL or zero, flush the conntrack entry specified
* in '*zone'. If 'zone' is NULL, use the default zone (zone 0). */ * by 'match' in '*zone'. If 'zone' is NULL, use the default zone
* (zone 0). */
int int
ct_dpif_flush(struct dpif *dpif, const uint16_t *zone, ct_dpif_flush(struct dpif *dpif, const uint16_t *zone,
const struct ct_dpif_tuple *tuple) const struct ofp_ct_match *match)
{ {
if (tuple) { if (match && !ofp_ct_match_is_zero(match)) {
struct ds ds = DS_EMPTY_INITIALIZER; return ct_dpif_flush_tuple(dpif, zone, match);
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);
} else if (zone) { } else if (zone) {
VLOG_DBG("%s: ct_flush: zone %"PRIu16, dpif_name(dpif), *zone); VLOG_DBG("%s: ct_flush: zone %"PRIu16, dpif_name(dpif), *zone);
} else { } else {
@@ -126,7 +294,7 @@ ct_dpif_flush(struct dpif *dpif, const uint16_t *zone,
} }
return (dpif->dpif_class->ct_flush return (dpif->dpif_class->ct_flush
? dpif->dpif_class->ct_flush(dpif, zone, tuple) ? dpif->dpif_class->ct_flush(dpif, zone, NULL)
: EOPNOTSUPP); : EOPNOTSUPP);
} }
@@ -583,112 +751,6 @@ ct_dpif_format_tcp_stat(struct ds * ds, int tcp_state, int conn_per_state)
ds_put_format(ds, "=%u", 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 void
ct_dpif_push_zone_limit(struct ovs_list *zone_limits, uint16_t zone, ct_dpif_push_zone_limit(struct ovs_list *zone_limits, uint16_t zone,

View File

@@ -20,6 +20,8 @@
#include "openvswitch/types.h" #include "openvswitch/types.h"
#include "packets.h" #include "packets.h"
struct ofp_ct_match;
union ct_dpif_inet_addr { union ct_dpif_inet_addr {
ovs_be32 ip; ovs_be32 ip;
ovs_be32 ip6[4]; ovs_be32 ip6[4];
@@ -285,7 +287,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_next(struct ct_dpif_dump_state *, struct ct_dpif_entry *);
int ct_dpif_dump_done(struct ct_dpif_dump_state *); int ct_dpif_dump_done(struct ct_dpif_dump_state *);
int ct_dpif_flush(struct dpif *, const uint16_t *zone, int ct_dpif_flush(struct dpif *, const uint16_t *zone,
const struct ct_dpif_tuple *); const struct ofp_ct_match *);
int ct_dpif_set_maxconns(struct dpif *dpif, uint32_t maxconns); 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_maxconns(struct dpif *dpif, uint32_t *maxconns);
int ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns); int ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns);
@@ -311,7 +313,6 @@ void ct_dpif_format_ipproto(struct ds *ds, uint16_t ipproto);
void ct_dpif_format_tuple(struct ds *, const struct ct_dpif_tuple *); void ct_dpif_format_tuple(struct ds *, const struct ct_dpif_tuple *);
uint8_t ct_dpif_coalesce_tcp_state(uint8_t state); uint8_t ct_dpif_coalesce_tcp_state(uint8_t state);
void ct_dpif_format_tcp_stat(struct ds *, int, int); 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, void ct_dpif_push_zone_limit(struct ovs_list *, uint16_t zone, uint32_t limit,
uint32_t count); uint32_t count);
void ct_dpif_free_zone_limits(struct ovs_list *); void ct_dpif_free_zone_limits(struct ovs_list *);

View File

@@ -41,6 +41,7 @@
#include "netlink.h" #include "netlink.h"
#include "odp-util.h" #include "odp-util.h"
#include "openvswitch/ofpbuf.h" #include "openvswitch/ofpbuf.h"
#include "openvswitch/ofp-ct.h"
#include "packets.h" #include "packets.h"
#include "openvswitch/shash.h" #include "openvswitch/shash.h"
#include "simap.h" #include "simap.h"
@@ -1707,37 +1708,55 @@ dpctl_flush_conntrack(int argc, const char *argv[],
struct dpctl_params *dpctl_p) struct dpctl_params *dpctl_p)
{ {
struct dpif *dpif = NULL; struct dpif *dpif = NULL;
struct ct_dpif_tuple tuple, *ptuple = NULL; struct ofp_ct_match match = {0};
struct ds ds = DS_EMPTY_INITIALIZER; struct ds ds = DS_EMPTY_INITIALIZER;
uint16_t zone, *pzone = NULL; uint16_t zone, *pzone = NULL;
int error; int error;
int args = argc - 1; int args = argc - 1;
/* Parse ct tuple */ /* Parse zone. */
if (args && ct_dpif_parse_tuple(&tuple, argv[args], &ds)) { if (args && !strncmp(argv[1], "zone=", 5)) {
ptuple = &tuple; if (!ovs_scan(argv[1], "zone=%"SCNu16, &zone)) {
args--; ds_put_cstr(&ds, "failed to parse zone");
} error = EINVAL;
goto error;
/* Parse zone */ }
if (args && ovs_scan(argv[args], "zone=%"SCNu16, &zone)) {
pzone = &zone; pzone = &zone;
args--; args--;
} }
/* Report error if there are more than one unparsed argument. */ /* Parse ct tuples. */
for (int i = 0; i < 2; i++) {
if (!args) {
break;
}
struct ofp_ct_tuple *tuple =
i ? &match.tuple_reply : &match.tuple_orig;
const char *arg = argv[argc - args];
if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, &ds, &match.ip_proto,
&match.l3_type)) {
error = EINVAL;
goto error;
}
args--;
}
/* Report error if there is more than one unparsed argument. */
if (args > 1) { if (args > 1) {
ds_put_cstr(&ds, "invalid arguments"); ds_put_cstr(&ds, "invalid arguments");
error = EINVAL; error = EINVAL;
goto error; goto error;
} }
error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif); error = opt_dpif_open(argc, argv, dpctl_p, 5, &dpif);
if (error) { if (error) {
dpctl_error(dpctl_p, error, "Cannot open dpif");
return error; return error;
} }
error = ct_dpif_flush(dpif, pzone, ptuple); error = ct_dpif_flush(dpif, pzone, &match);
if (!error) { if (!error) {
dpif_close(dpif); dpif_close(dpif);
return 0; return 0;
@@ -2862,8 +2881,8 @@ static const struct dpctl_command all_commands[] = {
0, 1, dpctl_offload_stats_show, DP_RO }, 0, 1, dpctl_offload_stats_show, DP_RO },
{ "dump-conntrack", "[-m] [-s] [dp] [zone=N]", { "dump-conntrack", "[-m] [-s] [dp] [zone=N]",
0, 4, dpctl_dump_conntrack, DP_RO }, 0, 4, dpctl_dump_conntrack, DP_RO },
{ "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3, { "flush-conntrack", "[dp] [zone=N] [ct-orig-tuple] [ct-reply-tuple]",
dpctl_flush_conntrack, DP_RW }, 0, 4, dpctl_flush_conntrack, DP_RW },
{ "cache-get-size", "[dp]", 0, 1, dpctl_cache_get_size, DP_RO }, { "cache-get-size", "[dp]", 0, 1, dpctl_cache_get_size, DP_RO },
{ "cache-set-size", "dp cache <size>", 3, 3, dpctl_cache_set_size, DP_RW }, { "cache-set-size", "dp cache <size>", 3, 3, dpctl_cache_set_size, DP_RW },
{ "ct-stats-show", "[dp] [zone=N]", { "ct-stats-show", "[dp] [zone=N]",

View File

@@ -302,22 +302,30 @@ are included. With \fB\-\-statistics\fR timeouts and timestamps are
added to the output. added to the output.
. .
.TP .TP
\*(DX\fBflush\-conntrack\fR [\fIdp\fR] [\fBzone=\fIzone\fR] [\fIct-tuple\fR] \*(DX\fBflush\-conntrack\fR [\fIdp\fR] [\fBzone=\fIzone\fR] [\fIct-origin-tuple\fR [\fIct-reply-tuple\fR]]
Flushes the connection entries in the tracker used by \fIdp\fR based on Flushes the connection entries in the tracker used by \fIdp\fR based on
\fIzone\fR and connection tracking tuple \fIct-tuple\fR. \fIzone\fR and connection tracking tuple \fIct-origin-tuple\fR.
If \fIct-tuple\fR is not provided, flushes all the connection entries. If \fIct-tuple\fR is not provided, flushes all the connection entries.
If \fBzone\fR=\fIzone\fR is specified, only flushes the connections in If \fBzone\fR=\fIzone\fR is specified, only flushes the connections in
\fIzone\fR. \fIzone\fR.
.IP .IP
If \fIct-tuple\fR is provided, flushes the connection entry specified by If \fIct-[orig|reply]-tuple\fR is provided, flushes the connection entry
\fIct-tuple\fR in \fIzone\fR. The zone defaults to 0 if it is not provided. specified by \fIct-[orig|reply]-tuple\fR in \fIzone\fR. The zone defaults
The userspace connection tracker requires flushing with the original pre-NATed to 0 if it is not provided. The userspace connection tracker requires flushing
tuple and a warning log will be otherwise generated. with the original pre-NATed tuple and a warning log will be otherwise
An example of an IPv4 ICMP \fIct-tuple\fR: generated. The tuple can be partial and will remove all connections that are
matching on the specified fields. In order to specify only
\fIct-reply-tuple\fR, provide empty string as \fIct-origin-tuple\fR.
.IP
Note: Currently there is a limitation for matching on ICMP, in order to
partially match on ICMP parameters the \fIct-[orig|reply]-tuple\fR has
to include either source or destination IP.
.IP
An example of an IPv4 ICMP \fIct-[orig|reply]-tuple\fR:
.IP .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" "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 .IP
An example of an IPv6 TCP \fIct-tuple\fR: An example of an IPv6 TCP \fIct-[orig|reply]-tuple\fR:
.IP .IP
"ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2,ct_nw_proto=6,ct_tp_src=1,ct_tp_dst=2" "ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2,ct_nw_proto=6,ct_tp_src=1,ct_tp_dst=2"
. .

213
lib/ofp-ct.c Normal file
View File

@@ -0,0 +1,213 @@
/*
* Copyright (c) 2023, 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 <config.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
#include "ct-dpif.h"
#include "openvswitch/ofp-ct.h"
#include "openvswitch/dynamic-string.h"
#include "openvswitch/ofp-parse.h"
#include "openvswitch/ofp-util.h"
#include "openvswitch/packets.h"
static void
ofp_ct_tuple_format(struct ds *ds, const struct ofp_ct_tuple *tuple,
uint8_t ip_proto, uint16_t l3_type)
{
ds_put_cstr(ds, l3_type == AF_INET ? "ct_nw_src=": "ct_ipv6_src=");
ipv6_format_mapped(&tuple->src, ds);
ds_put_cstr(ds, l3_type == AF_INET ? ",ct_nw_dst=": ",ct_ipv6_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, ",ct_tp_src=%u,ct_tp_dst=%u", ntohs(tuple->src_port),
ntohs(tuple->dst_port));
}
}
bool
ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
{
bool is_zero = ipv6_is_zero(&tuple->src) && ipv6_is_zero(&tuple->dst);
if (!(ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6)) {
is_zero = is_zero && !tuple->src_port && !tuple->dst_port;
}
return is_zero;
}
bool
ofp_ct_tuple_is_five_tuple(const struct ofp_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
ofp_ct_match_is_zero(const struct ofp_ct_match *match)
{
return !match->ip_proto && !match->l3_type &&
ofp_ct_tuple_is_zero(&match->tuple_orig, match->ip_proto) &&
ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto);
}
void
ofp_ct_match_format(struct ds *ds, const struct ofp_ct_match *match)
{
ds_put_cstr(ds, "'");
ofp_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto,
match->l3_type);
ds_put_format(ds, ",ct_nw_proto=%u' '", match->ip_proto);
ofp_ct_tuple_format(ds, &match->tuple_reply, match->ip_proto,
match->l3_type);
ds_put_cstr(ds, "'");
}
/* 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
ofp_ct_tuple_parse(struct ofp_ct_tuple *tuple, const char *s,
struct ds *ds, uint8_t *ip_proto, uint16_t *l3_type)
{
char *pos, *key, *value, *copy;
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")) {
struct in6_addr *addr = key[6] == 's' ? &tuple->src : &tuple->dst;
if (*l3_type && *l3_type != AF_INET) {
ds_put_format(ds ,"the L3 protocol does not match %s", value);
goto error;
}
if (!ipv6_is_zero(addr)) {
ds_put_format(ds, "%s is set multiple times", key);
goto error;
}
ovs_be32 ip = 0;
if (!ip_parse(value, &ip)) {
goto error_with_msg;
}
*l3_type = AF_INET;
*addr = in6_addr_mapped_ipv4(ip);
} else if (!strcmp(key, "ct_ipv6_src") ||
!strcmp(key, "ct_ipv6_dst")) {
struct in6_addr *addr = key[8] == 's' ? &tuple->src : &tuple->dst;
if (*l3_type && *l3_type != AF_INET6) {
ds_put_format(ds, "the L3 protocol does not match %s", value);
goto error;
}
if (!ipv6_is_zero(addr)) {
ds_put_format(ds, "%s is set multiple times", key);
goto error;
}
if (!ipv6_parse(value, addr)) {
goto error_with_msg;
}
*l3_type = AF_INET6;
} else if (!strcmp(key, "ct_nw_proto")) {
if (*ip_proto) {
ds_put_format(ds, "%s is set multiple times", key);
}
char *err = str_to_u8(value, key, 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 (*ip_proto != IPPROTO_ICMP && *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 (!*ip_proto && (tuple->src_port || tuple->dst_port)) {
ds_put_cstr(ds, "port is set without protocol");
goto error;
}
free(copy);
return true;
error_with_msg:
ds_put_format(ds, "failed to parse field %s", key);
error:
free(copy);
return false;
}

View File

@@ -2278,7 +2278,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 OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP AT_CLEANUP
AT_SETUP([conntrack - ct flush by 5-tuple]) AT_SETUP([conntrack - ct flush])
CHECK_CONNTRACK() CHECK_CONNTRACK()
OVS_TRAFFIC_VSWITCHD_START() OVS_TRAFFIC_VSWITCHD_START()
@@ -2339,6 +2339,106 @@ 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 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])
dnl Test UDP from port 1 and 2, partial flush by src address in reply direction
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.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 zone=5 '' 'ct_nw_src=10.1.1.1'])
AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
OVS_TRAFFIC_VSWITCHD_STOP OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP AT_CLEANUP