This adds the basic primitives, initialization, and operations that conntrack offload providers will need to implement in order to offer a path to offloading.
Assisted-by: Claude Sonnet 4.6 <[email protected]> Signed-off-by: Aaron Conole <[email protected]> --- lib/automake.mk | 2 + lib/conntrack-private.h | 27 ----- lib/conntrack.h | 29 +++++ lib/ct-offload.c | 255 ++++++++++++++++++++++++++++++++++++++++ lib/ct-offload.h | 81 +++++++++++++ 5 files changed, 367 insertions(+), 27 deletions(-) create mode 100644 lib/ct-offload.c create mode 100644 lib/ct-offload.h diff --git a/lib/automake.mk b/lib/automake.mk index 8051362354..dc165f4153 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -57,6 +57,8 @@ lib_libopenvswitch_la_SOURCES = \ lib/conntrack-other.c \ lib/conntrack.c \ lib/conntrack.h \ + lib/ct-offload.c \ + lib/ct-offload.h \ lib/cooperative-multitasking.c \ lib/cooperative-multitasking.h \ lib/cooperative-multitasking-private.h \ diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h index 6dd6c0ab1c..d011de3557 100644 --- a/lib/conntrack-private.h +++ b/lib/conntrack-private.h @@ -33,39 +33,12 @@ #include "unaligned.h" #include "dp-packet.h" -struct ct_endpoint { - union ct_addr addr; - union { - ovs_be16 port; - struct { - ovs_be16 icmp_id; - uint8_t icmp_type; - uint8_t icmp_code; - }; - }; -}; - -/* Verify that there is no padding in struct ct_endpoint, to facilitate - * hashing in ct_endpoint_hash_add(). */ -BUILD_ASSERT_DECL(sizeof(struct ct_endpoint) == sizeof(union ct_addr) + 4); - enum key_dir { CT_DIR_FWD = 0, CT_DIR_REV, CT_DIRS, }; -/* Changes to this structure need to be reflected in conn_key_hash() - * and conn_key_cmp(). */ -struct conn_key { - struct ct_endpoint src; - struct ct_endpoint dst; - - ovs_be16 dl_type; - uint16_t zone; - uint8_t nw_proto; -}; - /* Verify that nw_proto stays uint8_t as it's used to index into l4_protos[] */ BUILD_ASSERT_DECL(MEMBER_SIZEOF(struct conn_key, nw_proto) == sizeof(uint8_t)); diff --git a/lib/conntrack.h b/lib/conntrack.h index 0f791e75b4..f09a0c3774 100644 --- a/lib/conntrack.h +++ b/lib/conntrack.h @@ -70,6 +70,35 @@ union ct_addr { struct in6_addr ipv6; }; +/* One endpoint (address + L4 port or ICMP id/type/code) of a connection. */ +struct ct_endpoint { + union ct_addr addr; + union { + ovs_be16 port; + struct { + ovs_be16 icmp_id; + uint8_t icmp_type; + uint8_t icmp_code; + }; + }; +}; + +/* Verify that there is no padding in struct ct_endpoint, to facilitate + * hashing. */ +BUILD_ASSERT_DECL(sizeof(struct ct_endpoint) == sizeof(union ct_addr) + 4); + +/* Connection 5-tuple: forward-direction match key. + * Changes to this structure must be reflected in conn_key_hash() + * and conn_key_cmp(). */ +struct conn_key { + struct ct_endpoint src; + struct ct_endpoint dst; + + ovs_be16 dl_type; + uint16_t zone; + uint8_t nw_proto; +}; + enum nat_action_e { NAT_ACTION_SRC = 1 << 0, NAT_ACTION_SRC_PORT = 1 << 1, diff --git a/lib/ct-offload.c b/lib/ct-offload.c new file mode 100644 index 0000000000..e92d8f9508 --- /dev/null +++ b/lib/ct-offload.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2026 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 "ct-offload.h" + +#include <errno.h> + +#include "ovs-thread.h" +#include "util.h" + +#include "openvswitch/list.h" +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(ct_offload); + +/* Node in the registered-provider list. */ +struct ct_offload_class_node { + const struct ct_offload_class *class; + struct ovs_list list_node; +}; + +/* Global list of registered CT offload classes and a rwlock to protect it. + * Write lock is held only during register/unregister; fast-path operations + * hold the read lock so multiple PMD threads can iterate concurrently. */ +static struct ovs_rwlock ct_offload_rwlock = OVS_RWLOCK_INITIALIZER; +static struct ovs_list ct_offload_classes + OVS_GUARDED_BY(ct_offload_rwlock) + = OVS_LIST_INITIALIZER(&ct_offload_classes); + + +/* ct_offload_register() - register a CT offload provider class. + * + * Calls class->init() if provided. Returns 0 on success or a positive + * errno value on failure. Attempting to register the same class twice + * returns EEXIST. */ +int +ct_offload_register(const struct ct_offload_class *class) +{ + struct ct_offload_class_node *node; + int error = 0; + + ovs_assert(class); + ovs_assert(class->name); + ovs_assert(class->conn_add); + ovs_assert(class->conn_del); + ovs_assert(class->can_offload); + + ovs_rwlock_wrlock(&ct_offload_rwlock); + + /* Detect duplicate registrations. */ + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + if (!strcmp(node->class->name, class->name)) { + VLOG_WARN("attempted to register duplicate ct offload class: %s", + class->name); + error = EEXIST; + goto out; + } + } + + error = class->init ? class->init() : 0; + if (error) { + VLOG_WARN("failed to initialize ct offload class %s: %s", + class->name, ovs_strerror(error)); + goto out; + } + + node = xmalloc(sizeof *node); + node->class = class; + ovs_list_push_back(&ct_offload_classes, &node->list_node); + VLOG_DBG("registered ct offload class: %s", class->name); + +out: + ovs_rwlock_unlock(&ct_offload_rwlock); + return error; +} + +/* ct_offload_unregister() - unregister a previously registered class. + * + * Safe to call even if the class was never registered (no-op in that + * case). */ +void +ct_offload_unregister(const struct ct_offload_class *class) +{ + struct ct_offload_class_node *node; + + ovs_assert(class); + + ovs_rwlock_wrlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + if (node->class == class) { + ovs_list_remove(&node->list_node); + free(node); + VLOG_DBG("unregistered ct offload class: %s", class->name); + goto out; + } + } + VLOG_WARN("attempted to unregister unknown ct offload class: %s", + class->name); + +out: + ovs_rwlock_unlock(&ct_offload_rwlock); +} + +/* ct_offload_module_init() - register built-in CT offload providers. + * + * Must be called once before any connections are created. */ +void +ct_offload_module_init(void) +{ + /* No built-in providers yet; third parties call ct_offload_register() + * directly from their own module-init routines. */ +} + +/* ct_offload_conn_add() - notify all eligible providers of a new connection. + * + * Iterates over registered providers and calls conn_add() on each one that + * reports can_offload() == true for this context. Returns the first non-zero + * error encountered, but continues notifying remaining providers. This allows + * the underlying hardware conntrack details across providers function. */ +int +ct_offload_conn_add(const struct ct_offload_ctx *ctx) +{ + struct ct_offload_class_node *node; + int ret = 0; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + const struct ct_offload_class *class = node->class; + + if (!class->can_offload(ctx)) { + continue; + } + + int error = class->conn_add(ctx); + + if (error && !ret) { + ret = error; + } + } + ovs_rwlock_unlock(&ct_offload_rwlock); + + return ret; +} + +/* ct_offload_conn_del() - notify all providers that a connection was removed. + * + * Called unconditionally on all providers so that each can clean up any + * state it may have installed. */ +void +ct_offload_conn_del(const struct ct_offload_ctx *ctx) +{ + struct ct_offload_class_node *node; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + node->class->conn_del(ctx); + } + ovs_rwlock_unlock(&ct_offload_rwlock); +} + +void +ct_offload_conn_established(const struct ct_offload_ctx *ctx) +{ + struct ct_offload_class_node *node; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + const struct ct_offload_class *class = node->class; + + if (class->conn_established) { + class->conn_established(ctx); + } + } + ovs_rwlock_unlock(&ct_offload_rwlock); +} + +/* ct_offload_conn_update() - query the hardware last-used timestamp. + * + * Iterates over providers and returns the first non-zero timestamp returned + * by a provider's conn_update() callback. Returns 0 if no provider + * supplies a timestamp. */ +long long +ct_offload_conn_update(const struct ct_offload_ctx *ctx) +{ + struct ct_offload_class_node *node; + long long last_used = 0; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + const struct ct_offload_class *class = node->class; + + if (class->conn_update) { + long long ts = class->conn_update(ctx); + + if (ts) { + last_used = ts; + break; + } + } + } + ovs_rwlock_unlock(&ct_offload_rwlock); + + return last_used; +} + +/* ct_offload_can_offload() - returns true if any provider can offload ctx. */ +bool +ct_offload_can_offload(const struct ct_offload_ctx *ctx) +{ + struct ct_offload_class_node *node; + bool result = false; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + if (node->class->can_offload(ctx)) { + result = true; + break; + } + } + ovs_rwlock_unlock(&ct_offload_rwlock); + + return result; +} + +/* ct_offload_flush() - flush all offloaded connections from every provider. */ +void +ct_offload_flush(void) +{ + struct ct_offload_class_node *node; + + ovs_rwlock_rdlock(&ct_offload_rwlock); + LIST_FOR_EACH (node, list_node, &ct_offload_classes) { + const struct ct_offload_class *class = node->class; + + if (class->flush) { + class->flush(); + } + } + ovs_rwlock_unlock(&ct_offload_rwlock); +} diff --git a/lib/ct-offload.h b/lib/ct-offload.h new file mode 100644 index 0000000000..f44bfee217 --- /dev/null +++ b/lib/ct-offload.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2026 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 CT_OFFLOAD_H +#define CT_OFFLOAD_H + +#include "conntrack.h" +#include "conntrack-private.h" +#include "openvswitch/types.h" + +struct netdev; + +/* Context for offload as part of the callbacks that all connection + * offload APIs receive. + */ +struct ct_offload_ctx { + struct conn *conn; /* Connection object being offloaded. */ + struct netdev *netdev_in; /* Input netdev (may be NULL). */ + odp_port_t input_port_id; /* ODP port number. */ + const struct conn_key *key; /* Forward-direction 5-tuple. */ +}; + +/* CT offload class describes a conntrack offload provider implementation. */ +struct ct_offload_class { + const char *name; + + /* Optional initialization routine for the provider. */ + int (*init)(void); + + /* Per-connection operation callbacks get called for individual operations + * on the fast path or when batching is not in use. + * conn_add, conn_del, and can_offload are mandatory (non-NULL). */ + int (*conn_add)(const struct ct_offload_ctx *); + void (*conn_del)(const struct ct_offload_ctx *); + + /* Populate the last-used timestamp for the connection. Returns the + * last-used time in milliseconds since epoch, or 0 if the connection + * is not offloaded or the timestamp is not available. The caller only + * updates the connection expiration if the returned value is newer than + * the current expiration. */ + long long (*conn_update)(const struct ct_offload_ctx *); + /* Called exactly once when the first reply-direction packet is seen + * for an offloaded connection. */ + void (*conn_established)(const struct ct_offload_ctx *); + /* Check whether this provider can offload a connection. */ + bool (*can_offload)(const struct ct_offload_ctx *); + /* Flush all offloaded connections. */ + void (*flush)(void); +}; + +/* Register/unregister a provider. Must be called at module init, before + * any connections are created. conn_add, conn_del, and can_offload must + * be non-NULL. */ +int ct_offload_register(const struct ct_offload_class *); +void ct_offload_unregister(const struct ct_offload_class *); + +/* Module initialization (register built-in providers). */ +void ct_offload_module_init(void); + +/* Per-connection offload API that dispatches to all registered providers. */ +int ct_offload_conn_add(const struct ct_offload_ctx *); +void ct_offload_conn_del(const struct ct_offload_ctx *); +long long ct_offload_conn_update(const struct ct_offload_ctx *); +void ct_offload_conn_established(const struct ct_offload_ctx *); +bool ct_offload_can_offload(const struct ct_offload_ctx *); +void ct_offload_flush(void); + +#endif /* CT_OFFLOAD_H */ -- 2.51.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
