On 30 Jul 2026, at 19:49, Aaron Conole wrote:

> Add a documentation topic covering the userspace conntrack offloading
> framework introduced in this series.  The document explains the
> two-layer design (ct_offload_class for connection lifecycle events,
> dpif_offload_class for flow-level offloading), documents every
> ct_offload_class callback with preconditions and expected behavior,
> and provides a step-by-step integration guide for hardware vendors.
>
> Key topics covered:
> - Architecture and relationship between ct-offload and dpif-offload
> - Connection lifecycle state machine (conn_add -> conn_established ->
>   conn_update -> conn_del) with pseudocode at each transition
> - ct_offload_ctx fields, including that netdev_in is NULL at conn_del
>   and conn_update and providers must key on the conn* pointer
> - How hardware bypasses software conntrack via flow offloading, and
>   how conn_update return values signal ongoing hardware activity
> - Step-by-step implementation guide with sample code
> - Minimum viable implementation (three mandatory callbacks)
> - How to structure unit and integration tests using the dummy class
>
> Also add a NEWS entry announcing the new framework.

See some comments below, as we need to rethink the
ct-offload provider and dpif-offload provider relation.
I think the dpif-offload provider should be the lead, as
it controls the port and hardware resources.

Cheers,

Eelco

> Assisted-by: Claude Sonnet 4.6 <[email protected]>
> Signed-off-by: Aaron Conole <[email protected]>
> ---
>  Documentation/automake.mk                     |   1 +
>  Documentation/topics/index.rst                |   1 +
>  .../topics/userspace-conntrack-offloading.rst | 501 ++++++++++++++++++
>  NEWS                                          |   7 +
>  4 files changed, 510 insertions(+)
>  create mode 100644 Documentation/topics/userspace-conntrack-offloading.rst
>
> diff --git a/Documentation/automake.mk b/Documentation/automake.mk
> index 1af3f91904..0d675a2c30 100644
> --- a/Documentation/automake.mk
> +++ b/Documentation/automake.mk
> @@ -58,6 +58,7 @@ DOC_SOURCE = \
>       Documentation/topics/tracing.rst \
>       Documentation/topics/usdt-probes.rst \
>       Documentation/topics/userspace-checksum-offloading.rst \
> +     Documentation/topics/userspace-conntrack-offloading.rst \
>       Documentation/topics/userspace-tso.rst \
>       Documentation/topics/userspace-tx-steering.rst \
>       Documentation/howto/index.rst \
> diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst
> index 4db5fdff12..69ecd55b94 100644
> --- a/Documentation/topics/index.rst
> +++ b/Documentation/topics/index.rst
> @@ -55,6 +55,7 @@ OVS
>     idl-compound-indexes
>     ovs-extensions
>     userspace-checksum-offloading
> +   userspace-conntrack-offloading
>     userspace-tx-steering
>     usdt-probes
>     flow-visualization
> diff --git a/Documentation/topics/userspace-conntrack-offloading.rst 
> b/Documentation/topics/userspace-conntrack-offloading.rst
> new file mode 100644
> index 0000000000..9bf8b2c549
> --- /dev/null
> +++ b/Documentation/topics/userspace-conntrack-offloading.rst
> @@ -0,0 +1,501 @@
> +..
> +      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.
> +
> +      Convention for heading levels in Open vSwitch documentation:
> +
> +      =======  Heading 0 (reserved for the title in a document)
> +      -------  Heading 1
> +      ~~~~~~~  Heading 2
> +      +++++++  Heading 3
> +      '''''''  Heading 4
> +
> +      Avoid deeper levels because they do not render well.
> +
> +=========================================
> +Userspace Datapath - Conntrack Offloading
> +=========================================
> +
> +This document describes the conntrack offloading infrastructure in the OVS
> +userspace datapath, explains the design rationale, and provides a 
> step-by-step
> +guide for hardware vendors implementing a conntrack offload provider.
> +
> +Overview
> +--------
> +
> +Open vSwitch contains a userspace connection tracking (conntrack) engine
> +derived from the OpenBSD pf stack.  For every packet that carries a
> +``ct()`` action, OVS runs the packet through ``conntrack_execute()`` and
> +``process_one()``, which update connection state in software.
> +
> +Modern hardware (SmartNICs, DPUs, and accelerator ASICs), often include a
> +native CT engine that can track connection state and report last-used
> +timestamps without involving the host CPU.  The conntrack offload framework
> +(``ct_offload_class``) provides the lifecycle event hooks that such hardware
> +needs to stay in sync with the OVS software CT table.
> +
> +Architecture
> +------------
> +
> +Conntrack offloading is deliberately kept separate from flow offloading
> +(``dpif_offload_class``).  The two layers serve different purposes:
> +
> +* **Flow offloading** (``dpif_offload_class``): installs match/action rules
> +  in hardware: the equivalent of an ``rte_flow`` or TC flower rule.  After
> +  an upcall installs a datapath flow, the dpif-offload layer can push that
> +  rule to hardware so subsequent packets matching the same 5-tuple never
> +  cause a further upcall.
> +
> +* **Conntrack offloading** (``ct_offload_class``): notifies hardware of
> +  connection lifecycle events:  new connections, bidirectional establishment,
> +  idle-timeout queries, and deletion.  Hardware uses these events to maintain
> +  its own CT state tables, which may be separate from the hardware flow 
> table.
> +
> +A real hardware provider will typically implement **both** interfaces: the
> +dpif-offload side to push forwarding rules, and the ct-offload side to 
> program
> +hardware CT state.  The two interfaces share a common anchor: the
> +``struct netdev *`` passed in ``ct_offload_ctx.netdev_in`` identifies the
> +hardware port, and a provider can recover its ``dpif_offload`` instance from
> +``netdev->dpif_offload`` (as done in the DPDK and TC providers).
> +
> +Software conntrack always runs for the first packets of a new connection.
> +Once the hardware has been programmed (via ``conn_add`` /
> +``conn_established``), the provider's dpif-offload side installs a hardware
> +flow rule that matches all subsequent packets for the connection before OVS's
> +software path, removing them from the upcall / conntrack loop entirely.  The
> +ct-offload callbacks then serve two ongoing roles: ``conn_update`` extends 
> the
> +software expiry timer when hardware confirms the connection is still active,
> +and ``conn_del`` cleans up hardware state when OVS decides to retire the
> +connection.
> +
> +Connection Lifecycle
> +--------------------
> +
> +The following state machine shows when each callback fires and what the
> +provider is expected to do at each transition::
> +
> +    packet arrives, ct(commit)
> +           |
> +           v
> +      [ conn_add ]  -- hardware: allocate CT entry, store conn* -> handle map
> +           |
> +           |  ... forward packets flow through hardware ...
> +           |
> +           v
> +    reply packet seen, CS_ESTABLISHED | CS_REPLY_DIR
> +           |
> +           v
> +    [ conn_established ] -- hardware: update CT entry with reply port;
> +           |                           return true to mark connection in HW
> +           |
> +           |  ... bidirectional traffic flows through hardware ...
> +           |
> +           |  OVS expiry timer fires
> +           |
> +           v
> +     [ conn_update ] -- hardware: return last-used timestamp (ms since epoch)
> +           |                      or 0 if connection unknown / expired in HW
> +           |
> +           |  if timestamp > current expiry: connection stays alive
> +           |
> +           v
> +    OVS decides to expire (or dpctl/flush-conntrack called)
> +           |
> +           v
> +      [ conn_del ]  -- hardware: look up conn* -> handle map, remove CT entry
> +
> +
> +``conn_established`` is called **exactly once** per connection: the first 
> time
> +a reply-direction packet is seen while the connection is in the
> +``CT_OFFLOAD_STATE_ADDED`` state.  If ``conn_established`` returns ``true``,
> +OVS transitions the connection to ``CT_OFFLOAD_STATE_EST``.  If it returns
> +``false`` (the provider did not confirm hardware establishment), the state
> +remains ``CT_OFFLOAD_STATE_ADDED`` and ``conn_established`` will be called
> +again on the next eligible reply packet.
> +
> +``conn_update`` is called periodically on offloaded connections while OVS's
> +idle timer is running.  A non-zero return value refreshes the software 
> expiry,
> +keeping the connection alive as long as hardware reports activity.

See comments on patch 8.  conn_update should return the
last-used timestamp or zero, and the sweep logic should
use it in the timeout algorithm rather than collapsing it
to a boolean.

> +
> +The ``ct_offload_ctx`` Structure
> +---------------------------------
> +
> +Every callback receives a ``const struct ct_offload_ctx *`` with the 
> following
> +fields:
> +
> +``conn``
> +    Pointer to the OVS connection object.  Stable for the lifetime of the
> +    connection (from ``conn_add`` through ``conn_del``).  Use this as the key
> +    in any internal ``conn* -> hardware_handle`` map.
> +
> +``netdev_in``
> +    The input ``struct netdev *`` for the packet that triggered this event.
> +    Non-NULL at ``conn_add`` (forward-direction input port) and at
> +    ``conn_established`` (reply-direction input port).  **NULL at 
> ``conn_del``
> +    and ``conn_update``** - the provider must look up its hardware state by
> +    ``conn`` instead.
> +
> +``input_port_id``
> +    ODP port number corresponding to ``netdev_in``.  Provided for 
> convenience;
> +    the same information can be derived from ``netdev_in``.
> +
> +The ``ct_offload_class`` Interface
> +------------------------------------
> +
> +Mandatory callbacks
> +~~~~~~~~~~~~~~~~~~~
> +
> +``can_offload(ctx)``
> +    Called before ``conn_add`` to determine whether this provider will handle
> +    the connection.  Return ``true`` to accept the connection; return 
> ``false``
> +    to decline (the connection will not be added to this provider).  A 
> minimal
> +    implementation can simply return ``true`` for all connections.

Should this be 'might_offload' instead of 'can_offload'?
'can' sounds definitive, but the provider could still
decide not to offload once it knows the egress netdev or
other details not available at this point.

> +``conn_add(ctx)``
> +    Called when a new connection is committed (``ct(commit)``).
> +    ``ctx->netdev_in`` identifies the forward-direction input port; use it to
> +    find the hardware device (e.g. ``ctx->netdev_in->dpif_offload``).  
> Allocate
> +    a hardware CT entry and store a ``conn* -> hardware_handle`` mapping
> +    internally.  Return 0 on success or a positive ``errno`` on failure.
> +
> +``conn_del(ctx)``
> +    Called when OVS removes a connection (expiry or flush).  
> ``ctx->netdev_in``
> +    is NULL; look up the hardware handle by ``ctx->conn``.  Remove the 
> hardware
> +    CT entry and free internal state.
> +
> +Optional callbacks
> +~~~~~~~~~~~~~~~~~~
> +
> +``conn_established(ctx)``
> +    Called exactly once when the first reply-direction packet is seen for an
> +    offloaded connection, while OVS holds ``conn->lock``.  ``ctx->netdev_in``
> +    is the reply-direction input port.  Update the hardware CT entry with the
> +    reply port so the hardware knows both directions.  Return ``true`` if the
> +    connection is now fully established in hardware; return ``false`` if
> +    hardware establishment failed or was deferred.
> +
> +    If this callback is NULL, the framework skips the established transition
> +    and the provider never receives bidirectional port information.

Does it make sense for conn_established to be optional?
The framework only considers a connection fully offloaded
once this callback returns true.  Without it, the provider
never learns the reverse direction port and is_established
never returns true.

> +
> +``conn_update(ctx)``
> +    Called periodically by the conntrack idle timer to query hardware for a
> +    last-used timestamp.  ``ctx->netdev_in`` is NULL; look up by 
> ``ctx->conn``.
> +    Return the last-used time in milliseconds since epoch, or 0 if the
> +    connection is not found or no timestamp is available.  A non-zero return
> +    value causes OVS to refresh the connection's expiry timer, keeping
> +    long-running hardware-accelerated connections alive.
> +
> +    If this callback is NULL, OVS relies solely on its own software idle 
> timer,
> +    which will expire connections even if hardware is actively forwarding 
> them.
> +    Implementing ``conn_update`` is strongly recommended for any provider 
> that
> +    accelerates established connections.

Does it make sense for conn_update to be optional?
Without it, offloaded connections expire on the software
timer regardless of hardware activity, which defeats the
purpose of offloading.

> +
> +``batch_submit(batch)``
> +    Alternative to per-connection callbacks for providers that benefit from
> +    bulk programming (e.g. single DMA ring submission).  If non-NULL,
> +    ``batch_submit`` is called with the full batch before the framework falls
> +    back to individual callbacks for any remaining operations.  Providers 
> that
> +    do not need batching should leave this NULL.

See earlier comments on batch_submit.

> +``init()``
> +    One-time provider initialization called during ``ct_offload_register()``.
> +    Return 0 on success or a positive ``errno`` to abort registration.  May 
> be
> +    NULL if no initialization is required.
> +

See my earlier comment on init.  I think we need finer
control over when the module gets initialized, based on
the control of the offload provider.  I would suggest
using a similar approach to the offload provider with
init/open/close.

> +``flush()``
> +    Remove all offloaded connections from hardware.  Called when OVS flushes
> +    the entire conntrack table (e.g. ``dpctl/flush-conntrack``).  May be 
> NULL.

When conntrack_flush is called, conn_clean() is called for
each connection, which in turn calls conn_del() on the
provider.  So each connection does get an individual
conn_del during a flush.

That said, if flush() is meant as a bulk optimization to
avoid N individual conn_del calls, it should be documented
as such.  If not, and conn_del is always called per
connection anyway, should we remove flush() to avoid
confusion about resource cleanup responsibility?

> +
> +Registration and Automatic Activation
> +--------------------------------------
> +
> +CT offload providers are registered with ``ct_offload_register()`` and
> +identified by a ``name`` string.  The framework automatically activates a
> +built-in provider if a dpif offload class with a matching ``type`` field is
> +already registered.
> +
> +The activation path is::
> +
> +    ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
> +        |
> +        v
> +    dpif_offload_set_global_cfg()
> +        |
> +        +-- dpif_offload_module_init()   [first call only]
> +        |     registers all base dpif_offload_class entries
> +        |     (including "dummy", "tc", "dpdk") in the class shash
> +        |
> +        +-- sets offload_global_enabled = true
> +        |
> +        +-- ct_offload_set_global_cfg()  [first call only]
> +              calls ct_offload_module_init()
> +                for each built-in ct_offload_class:
> +                  if dpif_offload_class_is_registered(class->name):
> +                      ct_offload_register(class)
> +
> +The key invariant is: **a ct_offload_class is activated if and only if a
> +dpif_offload_class with the same type string is already registered**.  For a
> +new hardware provider, this means:
> +
> +* Set ``dpif_offload_class.type = "myhw"``
> +* Set ``ct_offload_class.name = "myhw"``
> +* Add both to their respective ``base_*_classes[]`` arrays in
> +  ``lib/dpif-offload.c`` and ``lib/ct-offload.c``.
> +
> +When ``hw-offload`` is enabled, the CT offload module will check
> +``dpif_offload_class_is_registered("myhw")``; if true, it calls
> +``ct_offload_register(&ct_offload_myhw_class)`` automatically.
>


See my earlier comment.  dpif_offload_module_init() just
registers the offload provider classes, not when they are
actually used.  The provider is only active when its open()
callback is called.  I think the ct-offload provider should
follow a similar approach: a global class init for
registration, but the dpif-offload provider calls open()
when the ct-offload provider is actually needed, and
close() when the dpif-offload provider is removed.  This
way resources are managed correctly and there is a clean
separation between initialization and actual use.  See the
problem with the dummy provider explained in an earlier
patch.

This also allows for reuse of an ct-offload class by multiple
offload providers, whenever the ct framework becomes
multi-instance. See comment on previous revision.

> +Implementing a New Provider
> +----------------------------
> +
> +This section walks through a minimum viable implementation.

No sure if we need this, we could reference to ct-offload-dummy.
> +
> +Step 1: Define a hardware handle type and internal state
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +The provider owns the ``conn* -> hardware_handle`` mapping.  A simple 
> approach
> +uses a hash map or linked list protected by a mutex::
> +
> +    struct myhw_ct_entry {
> +        struct hmap_node  hmap_node;  /* keyed by (uintptr_t)conn */
> +        const struct conn *conn;
> +        uint64_t          hw_handle;  /* opaque HW reference */
> +        struct netdev     *fwd_netdev;
> +        struct netdev     *rev_netdev;
> +    };
> +
> +    static struct hmap    myhw_ct_table = HMAP_INITIALIZER(&myhw_ct_table);
> +    static struct ovs_mutex myhw_mutex  = OVS_MUTEX_INITIALIZER;
> +
> +Step 2: Implement conn_add
> +~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> +    static int
> +    myhw_conn_add(const struct ct_offload_ctx *ctx)
> +    {
> +        /* Recover the dpif_offload instance from the netdev. */
> +        struct dpif_offload *offload = ctx->netdev_in->dpif_offload;
> +        if (!offload) {
> +            return ENODEV;
> +        }
> +
> +        uint64_t hw_handle;
> +        int err = myhw_program_ct_entry(offload, ctx->conn, &hw_handle);
> +        if (err) {
> +            return err;
> +        }
> +
> +        struct myhw_ct_entry *e = xmalloc(sizeof *e);
> +        e->conn       = ctx->conn;
> +        e->hw_handle  = hw_handle;
> +        e->fwd_netdev = ctx->netdev_in;
> +        e->rev_netdev = NULL;
> +
> +        ovs_mutex_lock(&myhw_mutex);
> +        hmap_insert(&myhw_ct_table, &e->hmap_node, (uintptr_t) ctx->conn);
> +        ovs_mutex_unlock(&myhw_mutex);
> +        return 0;
> +    }
> +
> +Step 3: Implement conn_established
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> +    static bool
> +    myhw_conn_established(const struct ct_offload_ctx *ctx)
> +    {
> +        ovs_mutex_lock(&myhw_mutex);
> +        struct myhw_ct_entry *e = myhw_find__(ctx->conn);
> +        bool ok = false;
> +
> +        if (e && !e->rev_netdev) {
> +            struct dpif_offload *offload = ctx->netdev_in->dpif_offload;
> +            if (offload && !myhw_update_ct_entry_rev(offload, e->hw_handle,
> +                                                     ctx->netdev_in)) {
> +                e->rev_netdev = ctx->netdev_in;
> +                ok = true;
> +            }
> +        }
> +        ovs_mutex_unlock(&myhw_mutex);
> +        return ok;
> +    }
> +
> +Step 4: Implement conn_update
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> +    static long long
> +    myhw_conn_update(const struct ct_offload_ctx *ctx)
> +    {
> +        ovs_mutex_lock(&myhw_mutex);
> +        struct myhw_ct_entry *e = myhw_find__(ctx->conn);
> +        long long last_used = 0;
> +
> +        if (e) {
> +            last_used = myhw_query_last_used(e->hw_handle);
> +        }
> +        ovs_mutex_unlock(&myhw_mutex);
> +        return last_used;  /* ms since epoch, or 0 */
> +    }
> +
> +Step 5: Implement conn_del
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> +    static void
> +    myhw_conn_del(const struct ct_offload_ctx *ctx)
> +    {
> +        /* netdev_in is NULL here; look up by conn pointer. */
> +        ovs_mutex_lock(&myhw_mutex);
> +        struct myhw_ct_entry *e = myhw_find__(ctx->conn);
> +
> +        if (e) {
> +            myhw_remove_ct_entry(e->hw_handle);
> +            hmap_remove(&myhw_ct_table, &e->hmap_node);
> +            free(e);
> +        }
> +        ovs_mutex_unlock(&myhw_mutex);
> +    }
> +
> +Step 6: Declare the class
> +~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> +    const struct ct_offload_class ct_offload_myhw_class = {
> +        .name             = "myhw",   /* must match dpif_offload class type 
> */
> +        .init             = NULL,
> +        .batch_submit     = NULL,
> +        .conn_add         = myhw_conn_add,
> +        .conn_del         = myhw_conn_del,
> +        .conn_update      = myhw_conn_update,
> +        .conn_established = myhw_conn_established,
> +        .can_offload      = myhw_can_offload,
> +        .flush            = myhw_flush,
> +    };
> +
> +Step 7: Register in the built-in list
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +In ``lib/ct-offload.c``, add the new class to ``base_ct_offload_classes[]``::
> +
> +    static const struct ct_offload_class *base_ct_offload_classes[] = {
> +        &ct_offload_dummy_class,
> +        &ct_offload_myhw_class,   /* add here */
> +    };
> +
> +And declare the extern in ``lib/ct-offload.h``::
> +
> +    extern const struct ct_offload_class ct_offload_myhw_class;
> +
> +Signaling Hardware Handling Back to the dpif-offload Layer
> +-----------------------------------------------------------
> +
> +When a connection is fully accelerated in hardware, subsequent packets for
> +that connection should bypass software conntrack.  This bypass is achieved
> +through the **flow offloading layer**, not through a direct feedback signal 
> in
> +the ct-offload API.
> +
> +The mechanism works as follows:
> +
> +1. The first packet for a new connection causes an upcall.  OVS processes the
> +   packet through software conntrack (``conn_add`` fires), then installs a
> +   **datapath flow** that matches all subsequent packets for this 5-tuple.
> +
> +2. The dpif-offload layer (``dpif_offload_class``) can push this datapath 
> flow
> +   to hardware.  Once installed in hardware, matching packets are forwarded
> +   directly, never reaching the upcall handler or software conntrack again.
> +
> +3. The ct-offload layer is responsible only for keeping the hardware CT table
> +   in sync: ``conn_add`` programs hardware CT state, ``conn_established``
> +   updates the reverse direction, ``conn_update`` extends the software expiry
> +   based on hardware activity, and ``conn_del`` cleans up.
> +
> +In this model the ct-offload provider does not need to signal anything back
> +to the dpif-offload layer.  The two layers operate independently on the same
> +netdev.  The provider's dpif-offload implementation handles flow-rule
> +installation; the ct-offload implementation handles CT-state synchronization.

I think they need to work together, as they might need
access to shared resources to program the hardware.  For
example, the hardware offload port configuration and
resources are tracked in the offload provider.  An example
of how to handle dependencies and interact between the two
providers would be good to add to the dummy implementation.

In practice the two are tightly coupled.  It would be good
to show in the dummy implementation that it gets the netdev
and checks with the offload provider if offload is enabled
for this port.  A simple unit test could disable offload on
the ingress port and verify that the connection is not
offloaded.

Other things that cross the boundary are that the CT
implementation needs to know which actions are executed as
part of the offloaded flow, which is not available in the
ct-offload module but is in the dpif-offload part.

Gaetan might have more input on this.

> +However, if the hardware CT engine can generate expiry events or last-used
> +interrupts, the provider should surface those as return values from
> +``conn_update``.  A non-zero return from ``conn_update`` causes OVS to 
> refresh
> +the software expiry timer, preventing the software side from expiring a
> +connection that hardware is still actively forwarding.
> +
> +Minimum Viable Implementation
> +------------------------------
> +
> +To get a working provider with no hardware acceleration (useful for testing
> +the integration path), implement only the three mandatory callbacks and 
> accept
> +all connections::
> +
> +    static bool  myhw_can_offload(const struct ct_offload_ctx *ctx)
> +    {
> +        return true;
> +    }
> +    static int   myhw_conn_add(const struct ct_offload_ctx *ctx)
> +    {
> +        return 0;
> +    }
> +    static void  myhw_conn_del(const struct ct_offload_ctx *ctx) { }
> +
> +    const struct ct_offload_class ct_offload_myhw_class = {
> +        .name        = "myhw",
> +        .can_offload = myhw_can_offload,
> +        .conn_add    = myhw_conn_add,
> +        .conn_del    = myhw_conn_del,
> +    };
> +
> +Without ``conn_update``, software idle timers will expire connections
> +regardless of hardware activity.  Without ``conn_established``, the provider
> +never learns the reply-direction port.  A production implementation should
> +provide both.
> +
> +Configuration
> +-------------
> +
> +Conntrack offloading is enabled as part of the global hardware offload
> +configuration::
> +
> +    ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
> +
> +No additional configuration is required.  The CT offload framework activates
> +automatically when ``hw-offload`` is set and a matching dpif offload class is
> +registered.  To disable, set ``hw-offload=false`` and restart OVS (the
> +enable path is a one-shot ``ovsthread_once``).
> +
> +Testing
> +-------
> +
> +The ``ct_offload_dummy_class`` (``lib/ct-offload-dummy.c``) is a complete
> +software-only reference implementation.  It records every lifecycle event and
> +exposes inspection helpers (``ct_offload_dummy_n_added()``,
> +``ct_offload_dummy_is_bidirectional()``, etc.) that unit tests use to verify
> +callback delivery.
> +
> +The dummy class is registered automatically when the dummy dpif offload class
> +is active (``hw-offload=true`` with a dummy datapath), making it possible to
> +exercise the full ct-offload path in the OVS test suite without any hardware.
> +The unit tests in ``tests/library.at`` cover each individual callback, and 
> the
> +integration test in ``tests/dpif-netdev.at`` verifies the end-to-end path
> +including bidirectional ``netdev_in`` propagation.
> +
> +New providers should follow the same test structure: unit tests that call
> +``ct_offload_register()`` directly (bypassing the dpif-offload gate), and an
> +integration test that enables ``hw-offload`` with the real hardware type and
> +verifies each lifecycle event fires with the correct context.
> diff --git a/NEWS b/NEWS
> index 58bf257670..28f7b3a202 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,12 @@
>  Post-v4.0.0
>  --------------------
> +   - Userspace datapath:
> +     * Conntrack offloading framework: hardware vendors can now register a
> +       ct_offload_class to receive connection lifecycle events (conn_add,
> +       conn_established, conn_update, conn_del) and maintain hardware CT
> +       state in sync with OVS's software conntrack table.  See
> +       Documentation/topics/userspace-conntrack-offloading.rst for the
> +       integration guide.
>
>
>  v4.0.0 - xx xxx xxxx
> -- 
> 2.51.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to