diff mbox series

[API-NEXT,v3,1/4] api: ipsec: extend lookaside API

Message ID 1490367881-16266-1-git-send-email-petri.savolainen@linaro.org
State Superseded
Headers show
Series [API-NEXT,v3,1/4] api: ipsec: extend lookaside API | expand

Commit Message

Petri Savolainen March 24, 2017, 3:04 p.m. UTC
Added configuration option for inbound SPI range (for
lookups). Removed unique SPI requirement and added config
option for overlap. Added default queue for lookup misses.
Added SA disable function and status event for the response
from it. The same event may be used for e.g. IPSEC
statistics, etc queries. Improved outbound fragmentation
documentation.

Signed-off-by: Petri Savolainen <petri.savolainen@linaro.org>

---
 include/odp/api/spec/event.h |   2 +-
 include/odp/api/spec/ipsec.h | 191 +++++++++++++++++++++++++++++++++----------
 2 files changed, 151 insertions(+), 42 deletions(-)

-- 
2.8.1

Comments

Bill Fischofer March 24, 2017, 11:51 p.m. UTC | #1
On Fri, Mar 24, 2017 at 10:04 AM, Petri Savolainen
<petri.savolainen@linaro.org> wrote:
> Added configuration option for inbound SPI range (for

> lookups). Removed unique SPI requirement and added config

> option for overlap. Added default queue for lookup misses.

> Added SA disable function and status event for the response

> from it. The same event may be used for e.g. IPSEC

> statistics, etc queries. Improved outbound fragmentation

> documentation.

>

> Signed-off-by: Petri Savolainen <petri.savolainen@linaro.org>

> ---

>  include/odp/api/spec/event.h |   2 +-

>  include/odp/api/spec/ipsec.h | 191 +++++++++++++++++++++++++++++++++----------

>  2 files changed, 151 insertions(+), 42 deletions(-)

>

> diff --git a/include/odp/api/spec/event.h b/include/odp/api/spec/event.h

> index 75c0bbc..f22efce 100644

> --- a/include/odp/api/spec/event.h

> +++ b/include/odp/api/spec/event.h

> @@ -39,7 +39,7 @@ extern "C" {

>   * @typedef odp_event_type_t

>   * ODP event types:

>   * ODP_EVENT_BUFFER, ODP_EVENT_PACKET, ODP_EVENT_TIMEOUT,

> - * ODP_EVENT_CRYPTO_COMPL, ODP_EVENT_IPSEC_RESULT

> + * ODP_EVENT_CRYPTO_COMPL, ODP_EVENT_IPSEC_RESULT, ODP_EVENT_IPSEC_STATUS

>   */

>

>  /**

> diff --git a/include/odp/api/spec/ipsec.h b/include/odp/api/spec/ipsec.h

> index 66222d8..d3e51bc 100644

> --- a/include/odp/api/spec/ipsec.h

> +++ b/include/odp/api/spec/ipsec.h

> @@ -56,6 +56,34 @@ typedef enum odp_ipsec_op_mode_t {

>  } odp_ipsec_op_mode_t;

>

>  /**

> + * Configuration options for IPSEC inbound processing

> + */

> +typedef struct odp_ipsec_inbound_config_t {

> +       /** Default destination queue for IPSEC events

> +        *

> +        *  When inbound SA lookup fails in asynchronous or inline modes,

> +        *  resulting IPSEC events are enqueued into this queue.

> +        */

> +       odp_queue_t default_queue;

> +

> +       /** Constraints for SPI values of inbound SAs for which lookup is

> +        *  enabled. Minimal range size and unique SPI values may improve

> +        *  performance. */

> +       struct {

> +               /** Minimum inbound SPI value. Default value is 0. */

> +               uint32_t min;

> +

> +               /** Maximum inbound SPI value. Default value is UINT32_MAX. */

> +               uint32_t max;

> +

> +               /** Inbound SPI values may overlap. Default value is 0. */

> +               odp_bool_t overlap;


It's not clear what you mean by SPI values overlapping since an SPI is
a unit32_t value. Some explanation / illustration seems warranted
here.

> +

> +       } spi_lookup;

> +

> +} odp_ipsec_inbound_config_t;

> +

> +/**

>   * IPSEC capability

>   */

>  typedef struct odp_ipsec_capability_t {

> @@ -111,6 +139,13 @@ typedef struct odp_ipsec_config_t {

>          */

>         odp_ipsec_op_mode_t op_mode;

>

> +       /** Maximum number of IPSEC SAs that application will use

> +        * simultaneously */

> +       uint32_t max_num_sa;

> +

> +       /** IPSEC inbound processing configuration */

> +       odp_ipsec_inbound_config_t inbound;

> +

>  } odp_ipsec_config_t;

>

>  /**

> @@ -349,8 +384,10 @@ typedef enum odp_ipsec_lookup_mode_t {

>         /** Inbound SA lookup is disabled. */

>         ODP_IPSEC_LOOKUP_DISABLED = 0,

>

> -       /** Inbound SA lookup is enabled. Used SPI values must be unique. */

> -       ODP_IPSEC_LOOKUP_IN_UNIQUE_SA

> +       /** Inbound SA lookup is enabled. Lookup matches only SPI value.

> +        *  SA lookup failure status (error.sa_lookup) is reported through

> +        *  odp_ipsec_packet_result_t. */

> +       ODP_IPSEC_LOOKUP_SPI

>

>  } odp_ipsec_lookup_mode_t;

>

> @@ -529,6 +566,29 @@ void odp_ipsec_sa_param_init(odp_ipsec_sa_param_t *param);

>  odp_ipsec_sa_t odp_ipsec_sa_create(odp_ipsec_sa_param_t *param);

>

>  /**

> + * Disable IPSEC SA

> + *

> + * Application must use this call to disable a SA before destroying it. The call

> + * marks the SA disabled, so that IPSEC implementation stops using it. For

> + * example, inbound SPI lookups will not match any more. Application must

> + * stop providing the SA as parameter to new IPSEC input/output operations

> + * before calling disable. Packets in progress during the call may still match

> + * the SA and be processed successfully.


Does this mean that it is an application responsibility to ensure that
there are no "in flight" IPsec operations at the time this call is
made? That seems unnecessarily burdensome, as one of the purposes of
an API like this is to flush the request pipeline before issuing a
destroy call. I'd prefer the following sort of wording:

"Operations initiated or in progress at the time this call is made
will complete normally, however no further operations on this SA will
be accepted. For example, inbound SPI lookups will not match any more,
and outbound operations referencing this SA will fail."

> + *

> + * When in synchronous operation mode, the call will return when it's possible

> + * to destroy the SA. In asynchronous mode, the same is indicated by an

> + * ODP_EVENT_IPSEC_STATUS event sent to the queue specified for the SA.

> + *

> + * @param sa      IPSEC SA to be disabled

> + *

> + * @retval 0      On success

> + * @retval <0     On failure

> + *

> + * @see odp_ipsec_sa_destroy()

> + */

> +int odp_ipsec_sa_disable(odp_ipsec_sa_t sa);

> +

> +/**

>   * Destroy IPSEC SA

>   *

>   * Destroy an unused IPSEC SA. Result is undefined if the SA is being used

> @@ -567,55 +627,59 @@ typedef struct odp_ipsec_op_opt_t {

>  #define ODP_IPSEC_OK 0

>

>  /** IPSEC operation status */

> -typedef union odp_ipsec_status_t {

> -       /** Error flags */

> -       struct {

> -               /** Protocol error. Not a valid ESP or AH packet. */

> -               uint32_t proto            : 1;

> +typedef struct odp_ipsec_op_status_t {

> +       union {

> +               /** Error flags */

> +               struct {

> +                       /** Protocol error. Not a valid ESP or AH packet. */

> +                       uint32_t proto            : 1;

>

> -               /** SA lookup failed */

> -               uint32_t sa_lookup        : 1;

> +                       /** SA lookup failed */

> +                       uint32_t sa_lookup        : 1;

>

> -               /** Authentication failed */

> -               uint32_t auth             : 1;

> +                       /** Authentication failed */

> +                       uint32_t auth             : 1;

>

> -               /** Anti-replay check failed */

> -               uint32_t antireplay       : 1;

> +                       /** Anti-replay check failed */

> +                       uint32_t antireplay       : 1;

>

> -               /** Other algorithm error */

> -               uint32_t alg              : 1;

> +                       /** Other algorithm error */

> +                       uint32_t alg              : 1;

>

> -               /** Packet does not fit into the given MTU size */

> -               uint32_t mtu              : 1;

> +                       /** Packet does not fit into the given MTU size */

> +                       uint32_t mtu              : 1;

>

> -               /** Soft lifetime expired: seconds */

> -               uint32_t soft_exp_sec     : 1;

> +                       /** Soft lifetime expired: seconds */

> +                       uint32_t soft_exp_sec     : 1;

>

> -               /** Soft lifetime expired: bytes */

> -               uint32_t soft_exp_bytes   : 1;

> +                       /** Soft lifetime expired: bytes */

> +                       uint32_t soft_exp_bytes   : 1;

>

> -               /** Soft lifetime expired: packets */

> -               uint32_t soft_exp_packets : 1;

> +                       /** Soft lifetime expired: packets */

> +                       uint32_t soft_exp_packets : 1;

>

> -               /** Hard lifetime expired: seconds */

> -               uint32_t hard_exp_sec     : 1;

> +                       /** Hard lifetime expired: seconds */

> +                       uint32_t hard_exp_sec     : 1;

>

> -               /** Hard lifetime expired: bytes */

> -               uint32_t hard_exp_bytes   : 1;

> +                       /** Hard lifetime expired: bytes */

> +                       uint32_t hard_exp_bytes   : 1;

>

> -               /** Hard lifetime expired: packets */

> -               uint32_t hard_exp_packets : 1;

> -       } error;

> +                       /** Hard lifetime expired: packets */

> +                       uint32_t hard_exp_packets : 1;

>

> -       /** All bits of the bit field structure

> -         *

> -         * This field can be used to set, clear or compare multiple flags.

> -         * For example, 'status.all != ODP_IPSEC_OK' checks if there are any

> -         * errors.

> -         */

> -       uint32_t all;

> +               } error;

>

> -} odp_ipsec_status_t;

> +               /** All error bits

> +                *

> +                *  This field can be used to set, clear or compare multiple

> +                *  flags. For example, 'status.all_error != ODP_IPSEC_OK'

> +                *  checks if there are

> +                *  any errors.

> +                */

> +               uint32_t all_error;

> +       };

> +

> +} odp_ipsec_op_status_t;

>

>  /**

>   * IPSEC operation input parameters

> @@ -673,14 +737,15 @@ typedef struct odp_ipsec_op_param_t {

>   */

>  typedef struct odp_ipsec_packet_result_t {

>         /** IPSEC operation status */

> -       odp_ipsec_status_t status;

> +       odp_ipsec_op_status_t status;

>

>         /** Number of output packets created from the corresponding input packet

>          *

>          *  Without fragmentation offload this is always one. However, if the

>          *  input packet was fragmented during the operation this is larger than

> -        *  one for the first fragment and zero for the rest of the fragments

> -        *  (following the first one in the 'pkt' array).

> +        *  one for the first returned fragment and zero for the rest of the

> +        *  fragments. All the fragments (of the same source packet) are stored

> +        *  consecutively in the 'pkt' array.

>          */

>         int num_out;

>

> @@ -745,6 +810,34 @@ typedef struct odp_ipsec_op_result_t {

>  } odp_ipsec_op_result_t;

>

>  /**

> + * IPSEC status ID

> + */

> +typedef enum odp_ipsec_status_id_t {

> +       /** Response to SA disable command */

> +       ODP_IPSEC_STATUS_SA_DISABLE = 0

> +

> +} odp_ipsec_status_id_t;

> +

> +/**

> + * IPSEC status content

> + */

> +typedef struct odp_ipsec_status_t {

> +       /** IPSEC status ID */

> +       odp_ipsec_status_id_t id;

> +

> +       /** Return value from the operation

> +        *

> +        *   0:    Success

> +        *  <0:    Failure

> +        */

> +       int ret;

> +

> +       /** IPSEC SA that was target of the operation */

> +       odp_ipsec_sa_t sa;

> +

> +} odp_ipsec_status_t;

> +

> +/**

>   * Inbound synchronous IPSEC operation

>   *

>   * This operation does inbound IPSEC processing in synchronous mode

> @@ -897,6 +990,22 @@ int odp_ipsec_out_enq(const odp_ipsec_op_param_t *input);

>  int odp_ipsec_result(odp_ipsec_op_result_t *result, odp_event_t event);

>

>  /**

> + * Get IPSEC status information from an ODP_EVENT_IPSEC_STATUS event

> + *

> + * Copies IPSEC status information from an event. The event must be of

> + * type ODP_EVENT_IPSEC_STATUS.

> + *

> + * @param[out]    status  Pointer to status information structure for output.

> + * @param         event   An ODP_EVENT_IPSEC_STATUS event

> + *

> + * @retval  0     On success

> + * @retval <0     On failure

> + *

> + * @see odp_ipsec_sa_disable()

> + */

> +int odp_ipsec_status(odp_ipsec_status_t *status, odp_event_t event);

> +

> +/**

>   * Update MTU for outbound IP fragmentation

>   *

>   * When IP fragmentation offload is enabled, the SA is created with an MTU.

> --

> 2.8.1

>
Savolainen, Petri (Nokia - FI/Espoo) March 27, 2017, 10:27 a.m. UTC | #2
> >  /**

> > + * Configuration options for IPSEC inbound processing

> > + */

> > +typedef struct odp_ipsec_inbound_config_t {

> > +       /** Default destination queue for IPSEC events

> > +        *

> > +        *  When inbound SA lookup fails in asynchronous or inline

> modes,

> > +        *  resulting IPSEC events are enqueued into this queue.

> > +        */

> > +       odp_queue_t default_queue;

> > +

> > +       /** Constraints for SPI values of inbound SAs for which lookup

> is

> > +        *  enabled. Minimal range size and unique SPI values may

> improve

> > +        *  performance. */

> > +       struct {

> > +               /** Minimum inbound SPI value. Default value is 0. */

> > +               uint32_t min;

> > +

> > +               /** Maximum inbound SPI value. Default value is

> UINT32_MAX. */

> > +               uint32_t max;

> > +

> > +               /** Inbound SPI values may overlap. Default value is 0.

> */

> > +               odp_bool_t overlap;

> 

> It's not clear what you mean by SPI values overlapping since an SPI is

> a unit32_t value. Some explanation / illustration seems warranted

> here.


Application tells if it uses unique SPI values for all (inbound) SAs it creates (== no two SAs have the same SPI), which results simpler lookup for implementation. This should be the default setting, since application can decide inbound SPI values. Since default values are usually zero, I used term "overlap" instead of e.g. "non_unique".


> >

> >  /**

> > + * Disable IPSEC SA

> > + *

> > + * Application must use this call to disable a SA before destroying it.

> The call

> > + * marks the SA disabled, so that IPSEC implementation stops using it.

> For

> > + * example, inbound SPI lookups will not match any more. Application

> must

> > + * stop providing the SA as parameter to new IPSEC input/output

> operations

> > + * before calling disable. Packets in progress during the call may

> still match

> > + * the SA and be processed successfully.

> 

> Does this mean that it is an application responsibility to ensure that

> there are no "in flight" IPsec operations at the time this call is

> made? That seems unnecessarily burdensome, as one of the purposes of

> an API like this is to flush the request pipeline before issuing a

> destroy call. I'd prefer the following sort of wording:

> 

> "Operations initiated or in progress at the time this call is made

> will complete normally, however no further operations on this SA will

> be accepted. For example, inbound SPI lookups will not match any more,

> and outbound operations referencing this SA will fail."



Application must not pass the same SA as parameter simultaneously to disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does not (may not) need to synchronize, between slow path (disable) and fast path (ipsec_in) calls. Appplication should be able to synchronize itself so that one thread does not disable/destroy a SA that other thread still actively uses.

Look ups for in-flight packet are still possible during disable, since that SA usage is in control of implementation. So, application can still continue passing packets without SA (for lookup), but it must not explicitly refer use SA itself on other (fast path calls).

-Petri
Bogdan Pricope March 27, 2017, 10:55 a.m. UTC | #3
On 27 March 2017 at 13:27, Savolainen, Petri (Nokia - FI/Espoo)
<petri.savolainen@nokia-bell-labs.com> wrote:
>

>> >  /**

>> > + * Configuration options for IPSEC inbound processing

>> > + */

>> > +typedef struct odp_ipsec_inbound_config_t {

>> > +       /** Default destination queue for IPSEC events

>> > +        *

>> > +        *  When inbound SA lookup fails in asynchronous or inline

>> modes,

>> > +        *  resulting IPSEC events are enqueued into this queue.

>> > +        */

>> > +       odp_queue_t default_queue;

>> > +

>> > +       /** Constraints for SPI values of inbound SAs for which lookup

>> is

>> > +        *  enabled. Minimal range size and unique SPI values may

>> improve

>> > +        *  performance. */

>> > +       struct {

>> > +               /** Minimum inbound SPI value. Default value is 0. */

>> > +               uint32_t min;

>> > +

>> > +               /** Maximum inbound SPI value. Default value is

>> UINT32_MAX. */

>> > +               uint32_t max;

>> > +

>> > +               /** Inbound SPI values may overlap. Default value is 0.

>> */

>> > +               odp_bool_t overlap;

>>

>> It's not clear what you mean by SPI values overlapping since an SPI is

>> a unit32_t value. Some explanation / illustration seems warranted

>> here.

>

> Application tells if it uses unique SPI values for all (inbound) SAs it creates (== no two SAs have the same SPI), which results simpler lookup for implementation. This should be the default setting, since application can decide inbound SPI values. Since default values are usually zero, I used term "overlap" instead of e.g. "non_unique".


What about "spi_collision_support" term?

RFC 4301:
"In many secure multicast architectures, e.g., [RFC3740], a central

   Group Controller/Key Server unilaterally assigns the Group Security
   Association's (GSA's) SPI.  This SPI assignment is not negotiated or
   coordinated with the key management (e.g., IKE) subsystems that
   reside in the individual end systems that constitute the group.
   Consequently, it is possible that a GSA and a unicast SA can
   simultaneously use the same SPI.  A multicast-capable IPsec
   implementation MUST correctly de-multiplex inbound traffic even in

   the context of SPI collisions."
>

>

>> >

>> >  /**

>> > + * Disable IPSEC SA

>> > + *

>> > + * Application must use this call to disable a SA before destroying it.

>> The call

>> > + * marks the SA disabled, so that IPSEC implementation stops using it.

>> For

>> > + * example, inbound SPI lookups will not match any more. Application

>> must

>> > + * stop providing the SA as parameter to new IPSEC input/output

>> operations

>> > + * before calling disable. Packets in progress during the call may

>> still match

>> > + * the SA and be processed successfully.

>>

>> Does this mean that it is an application responsibility to ensure that

>> there are no "in flight" IPsec operations at the time this call is

>> made? That seems unnecessarily burdensome, as one of the purposes of

>> an API like this is to flush the request pipeline before issuing a

>> destroy call. I'd prefer the following sort of wording:

>>

>> "Operations initiated or in progress at the time this call is made

>> will complete normally, however no further operations on this SA will

>> be accepted. For example, inbound SPI lookups will not match any more,

>> and outbound operations referencing this SA will fail."

>

>

> Application must not pass the same SA as parameter simultaneously to disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does not (may not) need to synchronize, between slow path (disable) and fast path (ipsec_in) calls. Appplication should be able to synchronize itself so that one thread does not disable/destroy a SA that other thread still actively uses.

>

> Look ups for in-flight packet are still possible during disable, since that SA usage is in control of implementation. So, application can still continue passing packets without SA (for lookup), but it must not explicitly refer use SA itself on other (fast path calls).

>

> -Petri
Bill Fischofer March 28, 2017, 1:32 a.m. UTC | #4
On Mon, Mar 27, 2017 at 5:27 AM, Savolainen, Petri (Nokia - FI/Espoo)
<petri.savolainen@nokia-bell-labs.com> wrote:
>

>> >  /**

>> > + * Configuration options for IPSEC inbound processing

>> > + */

>> > +typedef struct odp_ipsec_inbound_config_t {

>> > +       /** Default destination queue for IPSEC events

>> > +        *

>> > +        *  When inbound SA lookup fails in asynchronous or inline

>> modes,

>> > +        *  resulting IPSEC events are enqueued into this queue.

>> > +        */

>> > +       odp_queue_t default_queue;

>> > +

>> > +       /** Constraints for SPI values of inbound SAs for which lookup

>> is

>> > +        *  enabled. Minimal range size and unique SPI values may

>> improve

>> > +        *  performance. */

>> > +       struct {

>> > +               /** Minimum inbound SPI value. Default value is 0. */

>> > +               uint32_t min;

>> > +

>> > +               /** Maximum inbound SPI value. Default value is

>> UINT32_MAX. */

>> > +               uint32_t max;

>> > +

>> > +               /** Inbound SPI values may overlap. Default value is 0.

>> */

>> > +               odp_bool_t overlap;

>>

>> It's not clear what you mean by SPI values overlapping since an SPI is

>> a unit32_t value. Some explanation / illustration seems warranted

>> here.

>

> Application tells if it uses unique SPI values for all (inbound) SAs it creates (== no two SAs have the same SPI), which results simpler lookup for implementation. This should be the default setting, since application can decide inbound SPI values. Since default values are usually zero, I used term "overlap" instead of e.g. "non_unique".


As we discussed today, this is really saying whether lookups will be
done via a combination of SPI + dest addr or SPI only. In the latter
case SPI values must be unique. Either the name should make that
distinction obvious or else some additional documentation text should
be included here to avoid any possible confusion on the part of the
application writer or ODP implementer as to what the intent is here.
Perhaps "lookup_spi_and_dest" ?

>

>

>> >

>> >  /**

>> > + * Disable IPSEC SA

>> > + *

>> > + * Application must use this call to disable a SA before destroying it.

>> The call

>> > + * marks the SA disabled, so that IPSEC implementation stops using it.

>> For

>> > + * example, inbound SPI lookups will not match any more. Application

>> must

>> > + * stop providing the SA as parameter to new IPSEC input/output

>> operations

>> > + * before calling disable. Packets in progress during the call may

>> still match

>> > + * the SA and be processed successfully.

>>

>> Does this mean that it is an application responsibility to ensure that

>> there are no "in flight" IPsec operations at the time this call is

>> made? That seems unnecessarily burdensome, as one of the purposes of

>> an API like this is to flush the request pipeline before issuing a

>> destroy call. I'd prefer the following sort of wording:

>>

>> "Operations initiated or in progress at the time this call is made

>> will complete normally, however no further operations on this SA will

>> be accepted. For example, inbound SPI lookups will not match any more,

>> and outbound operations referencing this SA will fail."

>

>

> Application must not pass the same SA as parameter simultaneously to disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does not (may not) need to synchronize, between slow path (disable) and fast path (ipsec_in) calls. Appplication should be able to synchronize itself so that one thread does not disable/destroy a SA that other thread still actively uses.

>

> Look ups for in-flight packet are still possible during disable, since that SA usage is in control of implementation. So, application can still continue passing packets without SA (for lookup), but it must not explicitly refer use SA itself on other (fast path calls).


An IPsec operation referencing an SA can do so explicitly (via an
application odp_ipsec_in/out call) or implicitly (via an inline
lookup). As part of any such SA reference, the ODP implementation must
always determine whether the SA is valid. Reasons for an SA not being
valid include:

- The SA lookup failed (implicit only)
- The SA has expired due to elapsed time or number of bytes processed
- The SA has been administratively disabled

Since the ODP implementation has to do these checks for every
operation anyway, having it do the checks for administrative
disablement, which is what odp_ipsec_sa_disable() does, is not adding
any real additional work. The proposal that the implementation does
the check for administrative disablement only for implicit SA
references imposes an additional burden on the part of every ODP
application that uses IPsec that seems unnecessary.

I view odp_ipsec_sa_disable() to be analogous to odp_pktio_stop(),
which is used to quiesce an odp_pktio_t prior to closing it. Note the
analogous documentation in odp_pktio_stop():

 * Stop packet receive and transmit on a previously started interface. New
 * packets are not received from or transmitted to the network. Packets already
 * received from the network may be still available from interface and
 * application can receive those normally. New packets may not be accepted for
 * transmit. Packets already stored for transmit are not freed. A following
 * odp_packet_start() call restarts packet receive and transmit.

The "new packets may not be accepted for transmit" is the way I'd like
to see odp_ipsec_out() behave following an odp_ipsec_sa_disable()
call. Disable simply marks the SA so that in-flight operations can
complete but no new operations on that SA will be accepted. Once it
returns (indicating that the operation "pipeline" for that SA has been
flushed) the application can know that it is safe to destroy the SA.

If odp_ipsec_sa_disable() does not behave this way, then the
application would have to separately track and check SA enable/disable
status before it makes any odp_ipsec_out() call. But note that we're
not asking the application to track and check time/data expiration
limits on the SA, so this is inconsistent. Either the application
using lookaside processing should be responsible for tracking
everything or the ODP implementation should do this for the
application. The ODP philosophy has been that it's better to do such
things within the implementation than to require every ODP application
to invent their own scheme for doing this sort of thing.

Hope that clarifies things.


>

> -Petri
Savolainen, Petri (Nokia - FI/Espoo) March 28, 2017, 9:58 a.m. UTC | #5
> -----Original Message-----

> From: Bill Fischofer [mailto:bill.fischofer@linaro.org]

> Sent: Tuesday, March 28, 2017 4:32 AM

> To: Savolainen, Petri (Nokia - FI/Espoo) <petri.savolainen@nokia-bell-

> labs.com>

> Cc: lng-odp-forward <lng-odp@lists.linaro.org>

> Subject: Re: [lng-odp] [API-NEXT PATCH v3 1/4] api: ipsec: extend

> lookaside API

> 

> On Mon, Mar 27, 2017 at 5:27 AM, Savolainen, Petri (Nokia - FI/Espoo)

> <petri.savolainen@nokia-bell-labs.com> wrote:

> >

> >> >  /**

> >> > + * Configuration options for IPSEC inbound processing

> >> > + */

> >> > +typedef struct odp_ipsec_inbound_config_t {

> >> > +       /** Default destination queue for IPSEC events

> >> > +        *

> >> > +        *  When inbound SA lookup fails in asynchronous or inline

> >> modes,

> >> > +        *  resulting IPSEC events are enqueued into this queue.

> >> > +        */

> >> > +       odp_queue_t default_queue;

> >> > +

> >> > +       /** Constraints for SPI values of inbound SAs for which

> lookup

> >> is

> >> > +        *  enabled. Minimal range size and unique SPI values may

> >> improve

> >> > +        *  performance. */

> >> > +       struct {

> >> > +               /** Minimum inbound SPI value. Default value is 0. */

> >> > +               uint32_t min;

> >> > +

> >> > +               /** Maximum inbound SPI value. Default value is

> >> UINT32_MAX. */

> >> > +               uint32_t max;

> >> > +

> >> > +               /** Inbound SPI values may overlap. Default value is

> 0.

> >> */

> >> > +               odp_bool_t overlap;

> >>

> >> It's not clear what you mean by SPI values overlapping since an SPI is

> >> a unit32_t value. Some explanation / illustration seems warranted

> >> here.

> >

> > Application tells if it uses unique SPI values for all (inbound) SAs it

> creates (== no two SAs have the same SPI), which results simpler lookup

> for implementation. This should be the default setting, since application

> can decide inbound SPI values. Since default values are usually zero, I

> used term "overlap" instead of e.g. "non_unique".

> 

> As we discussed today, this is really saying whether lookups will be

> done via a combination of SPI + dest addr or SPI only. In the latter

> case SPI values must be unique. Either the name should make that

> distinction obvious or else some additional documentation text should

> be included here to avoid any possible confusion on the part of the

> application writer or ODP implementer as to what the intent is here.

> Perhaps "lookup_spi_and_dest" ?

> 

> >

> >

> >> >

> >> >  /**

> >> > + * Disable IPSEC SA

> >> > + *

> >> > + * Application must use this call to disable a SA before destroying

> it.

> >> The call

> >> > + * marks the SA disabled, so that IPSEC implementation stops using

> it.

> >> For

> >> > + * example, inbound SPI lookups will not match any more. Application

> >> must

> >> > + * stop providing the SA as parameter to new IPSEC input/output

> >> operations

> >> > + * before calling disable. Packets in progress during the call may

> >> still match

> >> > + * the SA and be processed successfully.

> >>

> >> Does this mean that it is an application responsibility to ensure that

> >> there are no "in flight" IPsec operations at the time this call is

> >> made? That seems unnecessarily burdensome, as one of the purposes of

> >> an API like this is to flush the request pipeline before issuing a

> >> destroy call. I'd prefer the following sort of wording:

> >>

> >> "Operations initiated or in progress at the time this call is made

> >> will complete normally, however no further operations on this SA will

> >> be accepted. For example, inbound SPI lookups will not match any more,

> >> and outbound operations referencing this SA will fail."

> >

> >

> > Application must not pass the same SA as parameter simultaneously to

> disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does

> not (may not) need to synchronize, between slow path (disable) and fast

> path (ipsec_in) calls. Appplication should be able to synchronize itself

> so that one thread does not disable/destroy a SA that other thread still

> actively uses.

> >

> > Look ups for in-flight packet are still possible during disable, since

> that SA usage is in control of implementation. So, application can still

> continue passing packets without SA (for lookup), but it must not

> explicitly refer use SA itself on other (fast path calls).

> 

> An IPsec operation referencing an SA can do so explicitly (via an

> application odp_ipsec_in/out call) or implicitly (via an inline

> lookup). As part of any such SA reference, the ODP implementation must

> always determine whether the SA is valid. Reasons for an SA not being

> valid include:

> 

> - The SA lookup failed (implicit only)

> - The SA has expired due to elapsed time or number of bytes processed

> - The SA has been administratively disabled

>


When application refers explicitly to SA, implementation can trust that the SA exist. It does not do look up, just refer to the SA directly. Expiration counters are part of SA context, SA exist even if counters exceed their limits. Just an error is returned. The last bullet concerns mostly the look up phase (implicit usage of SA).

 
> Since the ODP implementation has to do these checks for every

> operation anyway, having it do the checks for administrative

> disablement, which is what odp_ipsec_sa_disable() does, is not adding

> any real additional work. The proposal that the implementation does

> the check for administrative disablement only for implicit SA

> references imposes an additional burden on the part of every ODP

> application that uses IPsec that seems unnecessary.


Our expectation is that when application passes SA, implementation does not check SA validity but just uses it. Disable step is needed to phase out SA usage in the implicit case (implementation does look up and continues from there to process the packet).

SA state check concerns application only when itself does the SA look up, and thus needs to synchronize SA validity anyway.


> 

> I view odp_ipsec_sa_disable() to be analogous to odp_pktio_stop(),

> which is used to quiesce an odp_pktio_t prior to closing it. Note the

> analogous documentation in odp_pktio_stop():

> 

>  * Stop packet receive and transmit on a previously started interface. New

>  * packets are not received from or transmitted to the network. Packets

> already

>  * received from the network may be still available from interface and

>  * application can receive those normally. New packets may not be accepted

> for

>  * transmit. Packets already stored for transmit are not freed. A

> following

>  * odp_packet_start() call restarts packet receive and transmit.

> 

> The "new packets may not be accepted for transmit" is the way I'd like

> to see odp_ipsec_out() behave following an odp_ipsec_sa_disable()

> call. Disable simply marks the SA so that in-flight operations can

> complete but no new operations on that SA will be accepted. Once it

> returns (indicating that the operation "pipeline" for that SA has been

> flushed) the application can know that it is safe to destroy the SA.


Another analog is queue API.

1) Application first stops itself from adding new events into the queue
2) Application calls dequeue so many times that nothing comes out
3) Application destroys queue only after it's empty

1) stop explicit SA usage
2) disable SA
3) process all inflight IPSEC packets for the SA
4) SA can be destroyed only after all inflight IPSEC for the SA is finished (disable event or return value indicate that)


> 

> If odp_ipsec_sa_disable() does not behave this way, then the

> application would have to separately track and check SA enable/disable

> status before it makes any odp_ipsec_out() call. But note that we're

> not asking the application to track and check time/data expiration

> limits on the SA, so this is inconsistent. Either the application

> using lookaside processing should be responsible for tracking

> everything or the ODP implementation should do this for the

> application. The ODP philosophy has been that it's better to do such

> things within the implementation than to require every ODP application

> to invent their own scheme for doing this sort of thing.

> 

> Hope that clarifies things.



To me it's hard to believe that application would not have any book keeping of active SAs or inflight IPSEC requests. With explicit SA usage, worker thread synchronization through IPSEC block sounds an odd design ("a call failed on a SA, so some other thread must have destroyed the SA").

-Petri
Bill Fischofer March 28, 2017, 11:54 a.m. UTC | #6
On Tue, Mar 28, 2017 at 4:58 AM, Savolainen, Petri (Nokia - FI/Espoo)
<petri.savolainen@nokia-bell-labs.com> wrote:
>

>

>> -----Original Message-----

>> From: Bill Fischofer [mailto:bill.fischofer@linaro.org]

>> Sent: Tuesday, March 28, 2017 4:32 AM

>> To: Savolainen, Petri (Nokia - FI/Espoo) <petri.savolainen@nokia-bell-

>> labs.com>

>> Cc: lng-odp-forward <lng-odp@lists.linaro.org>

>> Subject: Re: [lng-odp] [API-NEXT PATCH v3 1/4] api: ipsec: extend

>> lookaside API

>>

>> On Mon, Mar 27, 2017 at 5:27 AM, Savolainen, Petri (Nokia - FI/Espoo)

>> <petri.savolainen@nokia-bell-labs.com> wrote:

>> >

>> >> >  /**

>> >> > + * Configuration options for IPSEC inbound processing

>> >> > + */

>> >> > +typedef struct odp_ipsec_inbound_config_t {

>> >> > +       /** Default destination queue for IPSEC events

>> >> > +        *

>> >> > +        *  When inbound SA lookup fails in asynchronous or inline

>> >> modes,

>> >> > +        *  resulting IPSEC events are enqueued into this queue.

>> >> > +        */

>> >> > +       odp_queue_t default_queue;

>> >> > +

>> >> > +       /** Constraints for SPI values of inbound SAs for which

>> lookup

>> >> is

>> >> > +        *  enabled. Minimal range size and unique SPI values may

>> >> improve

>> >> > +        *  performance. */

>> >> > +       struct {

>> >> > +               /** Minimum inbound SPI value. Default value is 0. */

>> >> > +               uint32_t min;

>> >> > +

>> >> > +               /** Maximum inbound SPI value. Default value is

>> >> UINT32_MAX. */

>> >> > +               uint32_t max;

>> >> > +

>> >> > +               /** Inbound SPI values may overlap. Default value is

>> 0.

>> >> */

>> >> > +               odp_bool_t overlap;

>> >>

>> >> It's not clear what you mean by SPI values overlapping since an SPI is

>> >> a unit32_t value. Some explanation / illustration seems warranted

>> >> here.

>> >

>> > Application tells if it uses unique SPI values for all (inbound) SAs it

>> creates (== no two SAs have the same SPI), which results simpler lookup

>> for implementation. This should be the default setting, since application

>> can decide inbound SPI values. Since default values are usually zero, I

>> used term "overlap" instead of e.g. "non_unique".

>>

>> As we discussed today, this is really saying whether lookups will be

>> done via a combination of SPI + dest addr or SPI only. In the latter

>> case SPI values must be unique. Either the name should make that

>> distinction obvious or else some additional documentation text should

>> be included here to avoid any possible confusion on the part of the

>> application writer or ODP implementer as to what the intent is here.

>> Perhaps "lookup_spi_and_dest" ?

>>

>> >

>> >

>> >> >

>> >> >  /**

>> >> > + * Disable IPSEC SA

>> >> > + *

>> >> > + * Application must use this call to disable a SA before destroying

>> it.

>> >> The call

>> >> > + * marks the SA disabled, so that IPSEC implementation stops using

>> it.

>> >> For

>> >> > + * example, inbound SPI lookups will not match any more. Application

>> >> must

>> >> > + * stop providing the SA as parameter to new IPSEC input/output

>> >> operations

>> >> > + * before calling disable. Packets in progress during the call may

>> >> still match

>> >> > + * the SA and be processed successfully.

>> >>

>> >> Does this mean that it is an application responsibility to ensure that

>> >> there are no "in flight" IPsec operations at the time this call is

>> >> made? That seems unnecessarily burdensome, as one of the purposes of

>> >> an API like this is to flush the request pipeline before issuing a

>> >> destroy call. I'd prefer the following sort of wording:

>> >>

>> >> "Operations initiated or in progress at the time this call is made

>> >> will complete normally, however no further operations on this SA will

>> >> be accepted. For example, inbound SPI lookups will not match any more,

>> >> and outbound operations referencing this SA will fail."

>> >

>> >

>> > Application must not pass the same SA as parameter simultaneously to

>> disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does

>> not (may not) need to synchronize, between slow path (disable) and fast

>> path (ipsec_in) calls. Appplication should be able to synchronize itself

>> so that one thread does not disable/destroy a SA that other thread still

>> actively uses.

>> >

>> > Look ups for in-flight packet are still possible during disable, since

>> that SA usage is in control of implementation. So, application can still

>> continue passing packets without SA (for lookup), but it must not

>> explicitly refer use SA itself on other (fast path calls).

>>

>> An IPsec operation referencing an SA can do so explicitly (via an

>> application odp_ipsec_in/out call) or implicitly (via an inline

>> lookup). As part of any such SA reference, the ODP implementation must

>> always determine whether the SA is valid. Reasons for an SA not being

>> valid include:

>>

>> - The SA lookup failed (implicit only)

>> - The SA has expired due to elapsed time or number of bytes processed

>> - The SA has been administratively disabled

>>

>

> When application refers explicitly to SA, implementation can trust that the SA exist. It does not do look up, just refer to the SA directly. Expiration counters are part of SA context, SA exist even if counters exceed their limits. Just an error is returned. The last bullet concerns mostly the look up phase (implicit usage of SA).


You can think of a disabled SA as equivalent to one that has been
expired by the application rather than time or usage, so any SA may be
disabled just as it may be expired.

>

>

>> Since the ODP implementation has to do these checks for every

>> operation anyway, having it do the checks for administrative

>> disablement, which is what odp_ipsec_sa_disable() does, is not adding

>> any real additional work. The proposal that the implementation does

>> the check for administrative disablement only for implicit SA

>> references imposes an additional burden on the part of every ODP

>> application that uses IPsec that seems unnecessary.

>

> Our expectation is that when application passes SA, implementation does not check SA validity but just uses it. Disable step is needed to phase out SA usage in the implicit case (implementation does look up and continues from there to process the packet).


Agreed. Attempting to reference a destroyed SA is a programming error,
the same as trying to reference any other stale handle. However an
expired/disabled SA is still a valid SA reference even though it's not
usable for IPsec operations, just as a stopped pktio is not usable for
I/O.

>

> SA state check concerns application only when itself does the SA look up, and thus needs to synchronize SA validity anyway.


No, because an SA may also be unusable because it is expired. It's the
implementation's responsibility to check for expirations since asking
a worker thread to do this would require knowledge it does not easily
have (e.g., how many bytes were sent on this SA by this or any other
thread). As noted above, disable is simply a way for the application
to force an SA into an expired state so that operations against it can
be flushed prior to destroying it.

>

>

>>

>> I view odp_ipsec_sa_disable() to be analogous to odp_pktio_stop(),

>> which is used to quiesce an odp_pktio_t prior to closing it. Note the

>> analogous documentation in odp_pktio_stop():

>>

>>  * Stop packet receive and transmit on a previously started interface. New

>>  * packets are not received from or transmitted to the network. Packets

>> already

>>  * received from the network may be still available from interface and

>>  * application can receive those normally. New packets may not be accepted

>> for

>>  * transmit. Packets already stored for transmit are not freed. A

>> following

>>  * odp_packet_start() call restarts packet receive and transmit.

>>

>> The "new packets may not be accepted for transmit" is the way I'd like

>> to see odp_ipsec_out() behave following an odp_ipsec_sa_disable()

>> call. Disable simply marks the SA so that in-flight operations can

>> complete but no new operations on that SA will be accepted. Once it

>> returns (indicating that the operation "pipeline" for that SA has been

>> flushed) the application can know that it is safe to destroy the SA.

>

> Another analog is queue API.

>

> 1) Application first stops itself from adding new events into the queue

> 2) Application calls dequeue so many times that nothing comes out

> 3) Application destroys queue only after it's empty

>

> 1) stop explicit SA usage

> 2) disable SA

> 3) process all inflight IPSEC packets for the SA

> 4) SA can be destroyed only after all inflight IPSEC for the SA is finished (disable event or return value indicate that)


I'd argue that adding an odp_queue_disable() API that would prevent
further enqueues while allowing dequeues would be a good add and is
something we're going to need anyway as we have more queues that can
have events added to it by the ODP implementation itself (e.g., SA
error queues). Otherwise we'll run into similar race conditions trying
to destroy them. The odp_pktio_stop() model should be the norm going
forward for various async operations to permit easy and well-defined
termination paths.

>

>

>>

>> If odp_ipsec_sa_disable() does not behave this way, then the

>> application would have to separately track and check SA enable/disable

>> status before it makes any odp_ipsec_out() call. But note that we're

>> not asking the application to track and check time/data expiration

>> limits on the SA, so this is inconsistent. Either the application

>> using lookaside processing should be responsible for tracking

>> everything or the ODP implementation should do this for the

>> application. The ODP philosophy has been that it's better to do such

>> things within the implementation than to require every ODP application

>> to invent their own scheme for doing this sort of thing.

>>

>> Hope that clarifies things.

>

>

> To me it's hard to believe that application would not have any book keeping of active SAs or inflight IPSEC requests. With explicit SA usage, worker thread synchronization through IPSEC block sounds an odd design ("a call failed on a SA,

so some other thread must have destroyed the SA").

Disabled, not destroyed. And no different than an operation failure
because the SA has expired (which could occur at any time from the
worker's perspective).

>

> -Petri

>

>
Bogdan Pricope March 28, 2017, 12:21 p.m. UTC | #7
There are two aspects to consider:
1. How to process odp_ipsec_in(), odp_ipsec_out(),etc. with explicit
SA reference after an odp_ipsec_sa_disable() (or after
odp_ipsec_sa_destroy())?
2. When is safe to odp_ipsec_sa_destroy() after odp_ipsec_sa_disable()?



If application holds a SA handler and IS NOT doing book keeping, it
may end up using a SA after it was disabled and destroyed – accessing
invalid/reused SA etc.

If application holds a SA handler and IS doing book keeping it may
avoid above case…. but will be difficult to tell when all workers
stopped using that SA (pending operations inside application, etc.).


We can ask application to do book keeping for the SA used directly and
use a timeout to guarantee to application that SA will not be
destroyed for a determined amount of time: we can have an
odp_ipsec_sa_shutdown() to do: disable, timeout and destroy. Than
odp_ipsec_in(), odp_ipsec_out(), etc. with explicit SA will return
error between disable and destroy and trigger fatal error after
destroy.

On 28 March 2017 at 14:54, Bill Fischofer <bill.fischofer@linaro.org> wrote:
> On Tue, Mar 28, 2017 at 4:58 AM, Savolainen, Petri (Nokia - FI/Espoo)

> <petri.savolainen@nokia-bell-labs.com> wrote:

>>

>>

>>> -----Original Message-----

>>> From: Bill Fischofer [mailto:bill.fischofer@linaro.org]

>>> Sent: Tuesday, March 28, 2017 4:32 AM

>>> To: Savolainen, Petri (Nokia - FI/Espoo) <petri.savolainen@nokia-bell-

>>> labs.com>

>>> Cc: lng-odp-forward <lng-odp@lists.linaro.org>

>>> Subject: Re: [lng-odp] [API-NEXT PATCH v3 1/4] api: ipsec: extend

>>> lookaside API

>>>

>>> On Mon, Mar 27, 2017 at 5:27 AM, Savolainen, Petri (Nokia - FI/Espoo)

>>> <petri.savolainen@nokia-bell-labs.com> wrote:

>>> >

>>> >> >  /**

>>> >> > + * Configuration options for IPSEC inbound processing

>>> >> > + */

>>> >> > +typedef struct odp_ipsec_inbound_config_t {

>>> >> > +       /** Default destination queue for IPSEC events

>>> >> > +        *

>>> >> > +        *  When inbound SA lookup fails in asynchronous or inline

>>> >> modes,

>>> >> > +        *  resulting IPSEC events are enqueued into this queue.

>>> >> > +        */

>>> >> > +       odp_queue_t default_queue;

>>> >> > +

>>> >> > +       /** Constraints for SPI values of inbound SAs for which

>>> lookup

>>> >> is

>>> >> > +        *  enabled. Minimal range size and unique SPI values may

>>> >> improve

>>> >> > +        *  performance. */

>>> >> > +       struct {

>>> >> > +               /** Minimum inbound SPI value. Default value is 0. */

>>> >> > +               uint32_t min;

>>> >> > +

>>> >> > +               /** Maximum inbound SPI value. Default value is

>>> >> UINT32_MAX. */

>>> >> > +               uint32_t max;

>>> >> > +

>>> >> > +               /** Inbound SPI values may overlap. Default value is

>>> 0.

>>> >> */

>>> >> > +               odp_bool_t overlap;

>>> >>

>>> >> It's not clear what you mean by SPI values overlapping since an SPI is

>>> >> a unit32_t value. Some explanation / illustration seems warranted

>>> >> here.

>>> >

>>> > Application tells if it uses unique SPI values for all (inbound) SAs it

>>> creates (== no two SAs have the same SPI), which results simpler lookup

>>> for implementation. This should be the default setting, since application

>>> can decide inbound SPI values. Since default values are usually zero, I

>>> used term "overlap" instead of e.g. "non_unique".

>>>

>>> As we discussed today, this is really saying whether lookups will be

>>> done via a combination of SPI + dest addr or SPI only. In the latter

>>> case SPI values must be unique. Either the name should make that

>>> distinction obvious or else some additional documentation text should

>>> be included here to avoid any possible confusion on the part of the

>>> application writer or ODP implementer as to what the intent is here.

>>> Perhaps "lookup_spi_and_dest" ?

>>>

>>> >

>>> >

>>> >> >

>>> >> >  /**

>>> >> > + * Disable IPSEC SA

>>> >> > + *

>>> >> > + * Application must use this call to disable a SA before destroying

>>> it.

>>> >> The call

>>> >> > + * marks the SA disabled, so that IPSEC implementation stops using

>>> it.

>>> >> For

>>> >> > + * example, inbound SPI lookups will not match any more. Application

>>> >> must

>>> >> > + * stop providing the SA as parameter to new IPSEC input/output

>>> >> operations

>>> >> > + * before calling disable. Packets in progress during the call may

>>> >> still match

>>> >> > + * the SA and be processed successfully.

>>> >>

>>> >> Does this mean that it is an application responsibility to ensure that

>>> >> there are no "in flight" IPsec operations at the time this call is

>>> >> made? That seems unnecessarily burdensome, as one of the purposes of

>>> >> an API like this is to flush the request pipeline before issuing a

>>> >> destroy call. I'd prefer the following sort of wording:

>>> >>

>>> >> "Operations initiated or in progress at the time this call is made

>>> >> will complete normally, however no further operations on this SA will

>>> >> be accepted. For example, inbound SPI lookups will not match any more,

>>> >> and outbound operations referencing this SA will fail."

>>> >

>>> >

>>> > Application must not pass the same SA as parameter simultaneously to

>>> disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does

>>> not (may not) need to synchronize, between slow path (disable) and fast

>>> path (ipsec_in) calls. Appplication should be able to synchronize itself

>>> so that one thread does not disable/destroy a SA that other thread still

>>> actively uses.

>>> >

>>> > Look ups for in-flight packet are still possible during disable, since

>>> that SA usage is in control of implementation. So, application can still

>>> continue passing packets without SA (for lookup), but it must not

>>> explicitly refer use SA itself on other (fast path calls).

>>>

>>> An IPsec operation referencing an SA can do so explicitly (via an

>>> application odp_ipsec_in/out call) or implicitly (via an inline

>>> lookup). As part of any such SA reference, the ODP implementation must

>>> always determine whether the SA is valid. Reasons for an SA not being

>>> valid include:

>>>

>>> - The SA lookup failed (implicit only)

>>> - The SA has expired due to elapsed time or number of bytes processed

>>> - The SA has been administratively disabled

>>>

>>

>> When application refers explicitly to SA, implementation can trust that the SA exist. It does not do look up, just refer to the SA directly. Expiration counters are part of SA context, SA exist even if counters exceed their limits. Just an error is returned. The last bullet concerns mostly the look up phase (implicit usage of SA).

>

> You can think of a disabled SA as equivalent to one that has been

> expired by the application rather than time or usage, so any SA may be

> disabled just as it may be expired.

>

>>

>>

>>> Since the ODP implementation has to do these checks for every

>>> operation anyway, having it do the checks for administrative

>>> disablement, which is what odp_ipsec_sa_disable() does, is not adding

>>> any real additional work. The proposal that the implementation does

>>> the check for administrative disablement only for implicit SA

>>> references imposes an additional burden on the part of every ODP

>>> application that uses IPsec that seems unnecessary.

>>

>> Our expectation is that when application passes SA, implementation does not check SA validity but just uses it. Disable step is needed to phase out SA usage in the implicit case (implementation does look up and continues from there to process the packet).

>

> Agreed. Attempting to reference a destroyed SA is a programming error,

> the same as trying to reference any other stale handle. However an

> expired/disabled SA is still a valid SA reference even though it's not

> usable for IPsec operations, just as a stopped pktio is not usable for

> I/O.

>

>>

>> SA state check concerns application only when itself does the SA look up, and thus needs to synchronize SA validity anyway.

>

> No, because an SA may also be unusable because it is expired. It's the

> implementation's responsibility to check for expirations since asking

> a worker thread to do this would require knowledge it does not easily

> have (e.g., how many bytes were sent on this SA by this or any other

> thread). As noted above, disable is simply a way for the application

> to force an SA into an expired state so that operations against it can

> be flushed prior to destroying it.

>

>>

>>

>>>

>>> I view odp_ipsec_sa_disable() to be analogous to odp_pktio_stop(),

>>> which is used to quiesce an odp_pktio_t prior to closing it. Note the

>>> analogous documentation in odp_pktio_stop():

>>>

>>>  * Stop packet receive and transmit on a previously started interface. New

>>>  * packets are not received from or transmitted to the network. Packets

>>> already

>>>  * received from the network may be still available from interface and

>>>  * application can receive those normally. New packets may not be accepted

>>> for

>>>  * transmit. Packets already stored for transmit are not freed. A

>>> following

>>>  * odp_packet_start() call restarts packet receive and transmit.

>>>

>>> The "new packets may not be accepted for transmit" is the way I'd like

>>> to see odp_ipsec_out() behave following an odp_ipsec_sa_disable()

>>> call. Disable simply marks the SA so that in-flight operations can

>>> complete but no new operations on that SA will be accepted. Once it

>>> returns (indicating that the operation "pipeline" for that SA has been

>>> flushed) the application can know that it is safe to destroy the SA.

>>

>> Another analog is queue API.

>>

>> 1) Application first stops itself from adding new events into the queue

>> 2) Application calls dequeue so many times that nothing comes out

>> 3) Application destroys queue only after it's empty

>>

>> 1) stop explicit SA usage

>> 2) disable SA

>> 3) process all inflight IPSEC packets for the SA

>> 4) SA can be destroyed only after all inflight IPSEC for the SA is finished (disable event or return value indicate that)

>

> I'd argue that adding an odp_queue_disable() API that would prevent

> further enqueues while allowing dequeues would be a good add and is

> something we're going to need anyway as we have more queues that can

> have events added to it by the ODP implementation itself (e.g., SA

> error queues). Otherwise we'll run into similar race conditions trying

> to destroy them. The odp_pktio_stop() model should be the norm going

> forward for various async operations to permit easy and well-defined

> termination paths.

>

>>

>>

>>>

>>> If odp_ipsec_sa_disable() does not behave this way, then the

>>> application would have to separately track and check SA enable/disable

>>> status before it makes any odp_ipsec_out() call. But note that we're

>>> not asking the application to track and check time/data expiration

>>> limits on the SA, so this is inconsistent. Either the application

>>> using lookaside processing should be responsible for tracking

>>> everything or the ODP implementation should do this for the

>>> application. The ODP philosophy has been that it's better to do such

>>> things within the implementation than to require every ODP application

>>> to invent their own scheme for doing this sort of thing.

>>>

>>> Hope that clarifies things.

>>

>>

>> To me it's hard to believe that application would not have any book keeping of active SAs or inflight IPSEC requests. With explicit SA usage, worker thread synchronization through IPSEC block sounds an odd design ("a call failed on a SA,

> so some other thread must have destroyed the SA").

>

> Disabled, not destroyed. And no different than an operation failure

> because the SA has expired (which could occur at any time from the

> worker's perspective).

>

>>

>> -Petri

>>

>>
Bill Fischofer March 28, 2017, 12:32 p.m. UTC | #8
On Tue, Mar 28, 2017 at 7:21 AM, Bogdan Pricope
<bogdan.pricope@linaro.org> wrote:
> There are two aspects to consider:

> 1. How to process odp_ipsec_in(), odp_ipsec_out(),etc. with explicit

> SA reference after an odp_ipsec_sa_disable() (or after

> odp_ipsec_sa_destroy())?

> 2. When is safe to odp_ipsec_sa_destroy() after odp_ipsec_sa_disable()?

>

>

>

> If application holds a SA handler and IS NOT doing book keeping, it

> may end up using a SA after it was disabled and destroyed – accessing

> invalid/reused SA etc.

>

> If application holds a SA handler and IS doing book keeping it may

> avoid above case…. but will be difficult to tell when all workers

> stopped using that SA (pending operations inside application, etc.).

>

>

> We can ask application to do book keeping for the SA used directly and

> use a timeout to guarantee to application that SA will not be

> destroyed for a determined amount of time: we can have an

> odp_ipsec_sa_shutdown() to do: disable, timeout and destroy. Than

> odp_ipsec_in(), odp_ipsec_out(), etc. with explicit SA will return

> error between disable and destroy and trigger fatal error after

> destroy.


Passing an undefined / invalid handle to an ODP API always results in
undefined behavior except for those APIs explicitly designed for
validation (e.g., odp_packet_is_valid()). These are always application
programming errors.

To avoid such errors during termination paths in multithreaded
environments, it's necessary to have some sort of quiesce/flush
protocol to ensure that the application is finished using a handle
before it is destroyed. While applications can design their own such
protocols, having ODP provide support for this common need improves
application simplicity and robustness since getting this sort of thing
right is often non-trivial in the general case. In general, it's
better for an ODP implementation do to things like this correctly in a
manner optimal for its platform than to ask every ODP application to
figure out how to best do this on its own.

In the specific case of SA's the ODP SA is simply a handle
(odp_ipsec_sa_t) that is opaque to the application. For the
application to be able to track the SA state independent of the ODP
implementation would require that it allocate and maintain its own
"shadow SA" that contains the needed state information, which would be
both error-prone and wasteful..That's why ODP handles things like SA
expiration processing, and why, I argue, it should do the same for
disablement status.

>

> On 28 March 2017 at 14:54, Bill Fischofer <bill.fischofer@linaro.org> wrote:

>> On Tue, Mar 28, 2017 at 4:58 AM, Savolainen, Petri (Nokia - FI/Espoo)

>> <petri.savolainen@nokia-bell-labs.com> wrote:

>>>

>>>

>>>> -----Original Message-----

>>>> From: Bill Fischofer [mailto:bill.fischofer@linaro.org]

>>>> Sent: Tuesday, March 28, 2017 4:32 AM

>>>> To: Savolainen, Petri (Nokia - FI/Espoo) <petri.savolainen@nokia-bell-

>>>> labs.com>

>>>> Cc: lng-odp-forward <lng-odp@lists.linaro.org>

>>>> Subject: Re: [lng-odp] [API-NEXT PATCH v3 1/4] api: ipsec: extend

>>>> lookaside API

>>>>

>>>> On Mon, Mar 27, 2017 at 5:27 AM, Savolainen, Petri (Nokia - FI/Espoo)

>>>> <petri.savolainen@nokia-bell-labs.com> wrote:

>>>> >

>>>> >> >  /**

>>>> >> > + * Configuration options for IPSEC inbound processing

>>>> >> > + */

>>>> >> > +typedef struct odp_ipsec_inbound_config_t {

>>>> >> > +       /** Default destination queue for IPSEC events

>>>> >> > +        *

>>>> >> > +        *  When inbound SA lookup fails in asynchronous or inline

>>>> >> modes,

>>>> >> > +        *  resulting IPSEC events are enqueued into this queue.

>>>> >> > +        */

>>>> >> > +       odp_queue_t default_queue;

>>>> >> > +

>>>> >> > +       /** Constraints for SPI values of inbound SAs for which

>>>> lookup

>>>> >> is

>>>> >> > +        *  enabled. Minimal range size and unique SPI values may

>>>> >> improve

>>>> >> > +        *  performance. */

>>>> >> > +       struct {

>>>> >> > +               /** Minimum inbound SPI value. Default value is 0. */

>>>> >> > +               uint32_t min;

>>>> >> > +

>>>> >> > +               /** Maximum inbound SPI value. Default value is

>>>> >> UINT32_MAX. */

>>>> >> > +               uint32_t max;

>>>> >> > +

>>>> >> > +               /** Inbound SPI values may overlap. Default value is

>>>> 0.

>>>> >> */

>>>> >> > +               odp_bool_t overlap;

>>>> >>

>>>> >> It's not clear what you mean by SPI values overlapping since an SPI is

>>>> >> a unit32_t value. Some explanation / illustration seems warranted

>>>> >> here.

>>>> >

>>>> > Application tells if it uses unique SPI values for all (inbound) SAs it

>>>> creates (== no two SAs have the same SPI), which results simpler lookup

>>>> for implementation. This should be the default setting, since application

>>>> can decide inbound SPI values. Since default values are usually zero, I

>>>> used term "overlap" instead of e.g. "non_unique".

>>>>

>>>> As we discussed today, this is really saying whether lookups will be

>>>> done via a combination of SPI + dest addr or SPI only. In the latter

>>>> case SPI values must be unique. Either the name should make that

>>>> distinction obvious or else some additional documentation text should

>>>> be included here to avoid any possible confusion on the part of the

>>>> application writer or ODP implementer as to what the intent is here.

>>>> Perhaps "lookup_spi_and_dest" ?

>>>>

>>>> >

>>>> >

>>>> >> >

>>>> >> >  /**

>>>> >> > + * Disable IPSEC SA

>>>> >> > + *

>>>> >> > + * Application must use this call to disable a SA before destroying

>>>> it.

>>>> >> The call

>>>> >> > + * marks the SA disabled, so that IPSEC implementation stops using

>>>> it.

>>>> >> For

>>>> >> > + * example, inbound SPI lookups will not match any more. Application

>>>> >> must

>>>> >> > + * stop providing the SA as parameter to new IPSEC input/output

>>>> >> operations

>>>> >> > + * before calling disable. Packets in progress during the call may

>>>> >> still match

>>>> >> > + * the SA and be processed successfully.

>>>> >>

>>>> >> Does this mean that it is an application responsibility to ensure that

>>>> >> there are no "in flight" IPsec operations at the time this call is

>>>> >> made? That seems unnecessarily burdensome, as one of the purposes of

>>>> >> an API like this is to flush the request pipeline before issuing a

>>>> >> destroy call. I'd prefer the following sort of wording:

>>>> >>

>>>> >> "Operations initiated or in progress at the time this call is made

>>>> >> will complete normally, however no further operations on this SA will

>>>> >> be accepted. For example, inbound SPI lookups will not match any more,

>>>> >> and outbound operations referencing this SA will fail."

>>>> >

>>>> >

>>>> > Application must not pass the same SA as parameter simultaneously to

>>>> disable() and e.g. ipsec_in() or ipsec_enq(). This way implementation does

>>>> not (may not) need to synchronize, between slow path (disable) and fast

>>>> path (ipsec_in) calls. Appplication should be able to synchronize itself

>>>> so that one thread does not disable/destroy a SA that other thread still

>>>> actively uses.

>>>> >

>>>> > Look ups for in-flight packet are still possible during disable, since

>>>> that SA usage is in control of implementation. So, application can still

>>>> continue passing packets without SA (for lookup), but it must not

>>>> explicitly refer use SA itself on other (fast path calls).

>>>>

>>>> An IPsec operation referencing an SA can do so explicitly (via an

>>>> application odp_ipsec_in/out call) or implicitly (via an inline

>>>> lookup). As part of any such SA reference, the ODP implementation must

>>>> always determine whether the SA is valid. Reasons for an SA not being

>>>> valid include:

>>>>

>>>> - The SA lookup failed (implicit only)

>>>> - The SA has expired due to elapsed time or number of bytes processed

>>>> - The SA has been administratively disabled

>>>>

>>>

>>> When application refers explicitly to SA, implementation can trust that the SA exist. It does not do look up, just refer to the SA directly. Expiration counters are part of SA context, SA exist even if counters exceed their limits. Just an error is returned. The last bullet concerns mostly the look up phase (implicit usage of SA).

>>

>> You can think of a disabled SA as equivalent to one that has been

>> expired by the application rather than time or usage, so any SA may be

>> disabled just as it may be expired.

>>

>>>

>>>

>>>> Since the ODP implementation has to do these checks for every

>>>> operation anyway, having it do the checks for administrative

>>>> disablement, which is what odp_ipsec_sa_disable() does, is not adding

>>>> any real additional work. The proposal that the implementation does

>>>> the check for administrative disablement only for implicit SA

>>>> references imposes an additional burden on the part of every ODP

>>>> application that uses IPsec that seems unnecessary.

>>>

>>> Our expectation is that when application passes SA, implementation does not check SA validity but just uses it. Disable step is needed to phase out SA usage in the implicit case (implementation does look up and continues from there to process the packet).

>>

>> Agreed. Attempting to reference a destroyed SA is a programming error,

>> the same as trying to reference any other stale handle. However an

>> expired/disabled SA is still a valid SA reference even though it's not

>> usable for IPsec operations, just as a stopped pktio is not usable for

>> I/O.

>>

>>>

>>> SA state check concerns application only when itself does the SA look up, and thus needs to synchronize SA validity anyway.

>>

>> No, because an SA may also be unusable because it is expired. It's the

>> implementation's responsibility to check for expirations since asking

>> a worker thread to do this would require knowledge it does not easily

>> have (e.g., how many bytes were sent on this SA by this or any other

>> thread). As noted above, disable is simply a way for the application

>> to force an SA into an expired state so that operations against it can

>> be flushed prior to destroying it.

>>

>>>

>>>

>>>>

>>>> I view odp_ipsec_sa_disable() to be analogous to odp_pktio_stop(),

>>>> which is used to quiesce an odp_pktio_t prior to closing it. Note the

>>>> analogous documentation in odp_pktio_stop():

>>>>

>>>>  * Stop packet receive and transmit on a previously started interface. New

>>>>  * packets are not received from or transmitted to the network. Packets

>>>> already

>>>>  * received from the network may be still available from interface and

>>>>  * application can receive those normally. New packets may not be accepted

>>>> for

>>>>  * transmit. Packets already stored for transmit are not freed. A

>>>> following

>>>>  * odp_packet_start() call restarts packet receive and transmit.

>>>>

>>>> The "new packets may not be accepted for transmit" is the way I'd like

>>>> to see odp_ipsec_out() behave following an odp_ipsec_sa_disable()

>>>> call. Disable simply marks the SA so that in-flight operations can

>>>> complete but no new operations on that SA will be accepted. Once it

>>>> returns (indicating that the operation "pipeline" for that SA has been

>>>> flushed) the application can know that it is safe to destroy the SA.

>>>

>>> Another analog is queue API.

>>>

>>> 1) Application first stops itself from adding new events into the queue

>>> 2) Application calls dequeue so many times that nothing comes out

>>> 3) Application destroys queue only after it's empty

>>>

>>> 1) stop explicit SA usage

>>> 2) disable SA

>>> 3) process all inflight IPSEC packets for the SA

>>> 4) SA can be destroyed only after all inflight IPSEC for the SA is finished (disable event or return value indicate that)

>>

>> I'd argue that adding an odp_queue_disable() API that would prevent

>> further enqueues while allowing dequeues would be a good add and is

>> something we're going to need anyway as we have more queues that can

>> have events added to it by the ODP implementation itself (e.g., SA

>> error queues). Otherwise we'll run into similar race conditions trying

>> to destroy them. The odp_pktio_stop() model should be the norm going

>> forward for various async operations to permit easy and well-defined

>> termination paths.

>>

>>>

>>>

>>>>

>>>> If odp_ipsec_sa_disable() does not behave this way, then the

>>>> application would have to separately track and check SA enable/disable

>>>> status before it makes any odp_ipsec_out() call. But note that we're

>>>> not asking the application to track and check time/data expiration

>>>> limits on the SA, so this is inconsistent. Either the application

>>>> using lookaside processing should be responsible for tracking

>>>> everything or the ODP implementation should do this for the

>>>> application. The ODP philosophy has been that it's better to do such

>>>> things within the implementation than to require every ODP application

>>>> to invent their own scheme for doing this sort of thing.

>>>>

>>>> Hope that clarifies things.

>>>

>>>

>>> To me it's hard to believe that application would not have any book keeping of active SAs or inflight IPSEC requests. With explicit SA usage, worker thread synchronization through IPSEC block sounds an odd design ("a call failed on a SA,

>> so some other thread must have destroyed the SA").

>>

>> Disabled, not destroyed. And no different than an operation failure

>> because the SA has expired (which could occur at any time from the

>> worker's perspective).

>>

>>>

>>> -Petri

>>>

>>>
diff mbox series

Patch

diff --git a/include/odp/api/spec/event.h b/include/odp/api/spec/event.h
index 75c0bbc..f22efce 100644
--- a/include/odp/api/spec/event.h
+++ b/include/odp/api/spec/event.h
@@ -39,7 +39,7 @@  extern "C" {
  * @typedef odp_event_type_t
  * ODP event types:
  * ODP_EVENT_BUFFER, ODP_EVENT_PACKET, ODP_EVENT_TIMEOUT,
- * ODP_EVENT_CRYPTO_COMPL, ODP_EVENT_IPSEC_RESULT
+ * ODP_EVENT_CRYPTO_COMPL, ODP_EVENT_IPSEC_RESULT, ODP_EVENT_IPSEC_STATUS
  */
 
 /**
diff --git a/include/odp/api/spec/ipsec.h b/include/odp/api/spec/ipsec.h
index 66222d8..d3e51bc 100644
--- a/include/odp/api/spec/ipsec.h
+++ b/include/odp/api/spec/ipsec.h
@@ -56,6 +56,34 @@  typedef enum odp_ipsec_op_mode_t {
 } odp_ipsec_op_mode_t;
 
 /**
+ * Configuration options for IPSEC inbound processing
+ */
+typedef struct odp_ipsec_inbound_config_t {
+	/** Default destination queue for IPSEC events
+	 *
+	 *  When inbound SA lookup fails in asynchronous or inline modes,
+	 *  resulting IPSEC events are enqueued into this queue.
+	 */
+	odp_queue_t default_queue;
+
+	/** Constraints for SPI values of inbound SAs for which lookup is
+	 *  enabled. Minimal range size and unique SPI values may improve
+	 *  performance. */
+	struct {
+		/** Minimum inbound SPI value. Default value is 0. */
+		uint32_t min;
+
+		/** Maximum inbound SPI value. Default value is UINT32_MAX. */
+		uint32_t max;
+
+		/** Inbound SPI values may overlap. Default value is 0. */
+		odp_bool_t overlap;
+
+	} spi_lookup;
+
+} odp_ipsec_inbound_config_t;
+
+/**
  * IPSEC capability
  */
 typedef struct odp_ipsec_capability_t {
@@ -111,6 +139,13 @@  typedef struct odp_ipsec_config_t {
 	 */
 	odp_ipsec_op_mode_t op_mode;
 
+	/** Maximum number of IPSEC SAs that application will use
+	 * simultaneously */
+	uint32_t max_num_sa;
+
+	/** IPSEC inbound processing configuration */
+	odp_ipsec_inbound_config_t inbound;
+
 } odp_ipsec_config_t;
 
 /**
@@ -349,8 +384,10 @@  typedef enum odp_ipsec_lookup_mode_t {
 	/** Inbound SA lookup is disabled. */
 	ODP_IPSEC_LOOKUP_DISABLED = 0,
 
-	/** Inbound SA lookup is enabled. Used SPI values must be unique. */
-	ODP_IPSEC_LOOKUP_IN_UNIQUE_SA
+	/** Inbound SA lookup is enabled. Lookup matches only SPI value.
+	 *  SA lookup failure status (error.sa_lookup) is reported through
+	 *  odp_ipsec_packet_result_t. */
+	ODP_IPSEC_LOOKUP_SPI
 
 } odp_ipsec_lookup_mode_t;
 
@@ -529,6 +566,29 @@  void odp_ipsec_sa_param_init(odp_ipsec_sa_param_t *param);
 odp_ipsec_sa_t odp_ipsec_sa_create(odp_ipsec_sa_param_t *param);
 
 /**
+ * Disable IPSEC SA
+ *
+ * Application must use this call to disable a SA before destroying it. The call
+ * marks the SA disabled, so that IPSEC implementation stops using it. For
+ * example, inbound SPI lookups will not match any more. Application must
+ * stop providing the SA as parameter to new IPSEC input/output operations
+ * before calling disable. Packets in progress during the call may still match
+ * the SA and be processed successfully.
+ *
+ * When in synchronous operation mode, the call will return when it's possible
+ * to destroy the SA. In asynchronous mode, the same is indicated by an
+ * ODP_EVENT_IPSEC_STATUS event sent to the queue specified for the SA.
+ *
+ * @param sa      IPSEC SA to be disabled
+ *
+ * @retval 0      On success
+ * @retval <0     On failure
+ *
+ * @see odp_ipsec_sa_destroy()
+ */
+int odp_ipsec_sa_disable(odp_ipsec_sa_t sa);
+
+/**
  * Destroy IPSEC SA
  *
  * Destroy an unused IPSEC SA. Result is undefined if the SA is being used
@@ -567,55 +627,59 @@  typedef struct odp_ipsec_op_opt_t {
 #define ODP_IPSEC_OK 0
 
 /** IPSEC operation status */
-typedef union odp_ipsec_status_t {
-	/** Error flags */
-	struct {
-		/** Protocol error. Not a valid ESP or AH packet. */
-		uint32_t proto            : 1;
+typedef struct odp_ipsec_op_status_t {
+	union {
+		/** Error flags */
+		struct {
+			/** Protocol error. Not a valid ESP or AH packet. */
+			uint32_t proto            : 1;
 
-		/** SA lookup failed */
-		uint32_t sa_lookup        : 1;
+			/** SA lookup failed */
+			uint32_t sa_lookup        : 1;
 
-		/** Authentication failed */
-		uint32_t auth             : 1;
+			/** Authentication failed */
+			uint32_t auth             : 1;
 
-		/** Anti-replay check failed */
-		uint32_t antireplay       : 1;
+			/** Anti-replay check failed */
+			uint32_t antireplay       : 1;
 
-		/** Other algorithm error */
-		uint32_t alg              : 1;
+			/** Other algorithm error */
+			uint32_t alg              : 1;
 
-		/** Packet does not fit into the given MTU size */
-		uint32_t mtu              : 1;
+			/** Packet does not fit into the given MTU size */
+			uint32_t mtu              : 1;
 
-		/** Soft lifetime expired: seconds */
-		uint32_t soft_exp_sec     : 1;
+			/** Soft lifetime expired: seconds */
+			uint32_t soft_exp_sec     : 1;
 
-		/** Soft lifetime expired: bytes */
-		uint32_t soft_exp_bytes   : 1;
+			/** Soft lifetime expired: bytes */
+			uint32_t soft_exp_bytes   : 1;
 
-		/** Soft lifetime expired: packets */
-		uint32_t soft_exp_packets : 1;
+			/** Soft lifetime expired: packets */
+			uint32_t soft_exp_packets : 1;
 
-		/** Hard lifetime expired: seconds */
-		uint32_t hard_exp_sec     : 1;
+			/** Hard lifetime expired: seconds */
+			uint32_t hard_exp_sec     : 1;
 
-		/** Hard lifetime expired: bytes */
-		uint32_t hard_exp_bytes   : 1;
+			/** Hard lifetime expired: bytes */
+			uint32_t hard_exp_bytes   : 1;
 
-		/** Hard lifetime expired: packets */
-		uint32_t hard_exp_packets : 1;
-	} error;
+			/** Hard lifetime expired: packets */
+			uint32_t hard_exp_packets : 1;
 
-	/** All bits of the bit field structure
-	  *
-	  * This field can be used to set, clear or compare multiple flags.
-	  * For example, 'status.all != ODP_IPSEC_OK' checks if there are any
-	  * errors.
-	  */
-	uint32_t all;
+		} error;
 
-} odp_ipsec_status_t;
+		/** All error bits
+		 *
+		 *  This field can be used to set, clear or compare multiple
+		 *  flags. For example, 'status.all_error != ODP_IPSEC_OK'
+		 *  checks if there are
+		 *  any errors.
+		 */
+		uint32_t all_error;
+	};
+
+} odp_ipsec_op_status_t;
 
 /**
  * IPSEC operation input parameters
@@ -673,14 +737,15 @@  typedef struct odp_ipsec_op_param_t {
  */
 typedef struct odp_ipsec_packet_result_t {
 	/** IPSEC operation status */
-	odp_ipsec_status_t status;
+	odp_ipsec_op_status_t status;
 
 	/** Number of output packets created from the corresponding input packet
 	 *
 	 *  Without fragmentation offload this is always one. However, if the
 	 *  input packet was fragmented during the operation this is larger than
-	 *  one for the first fragment and zero for the rest of the fragments
-	 *  (following the first one in the 'pkt' array).
+	 *  one for the first returned fragment and zero for the rest of the
+	 *  fragments. All the fragments (of the same source packet) are stored
+	 *  consecutively in the 'pkt' array.
 	 */
 	int num_out;
 
@@ -745,6 +810,34 @@  typedef struct odp_ipsec_op_result_t {
 } odp_ipsec_op_result_t;
 
 /**
+ * IPSEC status ID
+ */
+typedef enum odp_ipsec_status_id_t {
+	/** Response to SA disable command */
+	ODP_IPSEC_STATUS_SA_DISABLE = 0
+
+} odp_ipsec_status_id_t;
+
+/**
+ * IPSEC status content
+ */
+typedef struct odp_ipsec_status_t {
+	/** IPSEC status ID */
+	odp_ipsec_status_id_t id;
+
+	/** Return value from the operation
+	 *
+	 *   0:    Success
+	 *  <0:    Failure
+	 */
+	int ret;
+
+	/** IPSEC SA that was target of the operation */
+	odp_ipsec_sa_t sa;
+
+} odp_ipsec_status_t;
+
+/**
  * Inbound synchronous IPSEC operation
  *
  * This operation does inbound IPSEC processing in synchronous mode
@@ -897,6 +990,22 @@  int odp_ipsec_out_enq(const odp_ipsec_op_param_t *input);
 int odp_ipsec_result(odp_ipsec_op_result_t *result, odp_event_t event);
 
 /**
+ * Get IPSEC status information from an ODP_EVENT_IPSEC_STATUS event
+ *
+ * Copies IPSEC status information from an event. The event must be of
+ * type ODP_EVENT_IPSEC_STATUS.
+ *
+ * @param[out]    status  Pointer to status information structure for output.
+ * @param         event   An ODP_EVENT_IPSEC_STATUS event
+ *
+ * @retval  0     On success
+ * @retval <0     On failure
+ *
+ * @see odp_ipsec_sa_disable()
+ */
+int odp_ipsec_status(odp_ipsec_status_t *status, odp_event_t event);
+
+/**
  * Update MTU for outbound IP fragmentation
  *
  * When IP fragmentation offload is enabled, the SA is created with an MTU.