On 30 Jul 2026 at 01:49:20 PM, Aaron Conole <[email protected]> wrote:

> 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);
> +}
> +

a comment header for consistency?

> +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 {

same as above, maybe specifying its uniqueness is checked at
registration time.

> +    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

Reply via email to