As discussed during today's ARCH call, we need to close on the
remaining IPsec confusion surrounding the definition of
odp_ipsec_sa_disable().
The main scenario that this API is designed to address, from an
application standpoint is as follows:
- Application has one or more active SAs that are receiving inline RX
IPsec traffic.
- An active SA needs to be deactivated / destroyed for some reason
- Application needs to be sure that "in flight" events that it is not
yet aware of are in a defined state.
odp_ipsec_sa_disable() allows the application to tell the ODP
implementation that it wants to deactivate an SA so that it can be
destroyed. In response to this the implementation is expected to:
- Stop making internal use of this SA
- Tell the application when the SA is quiesced as far as the
implementation is concerned.
As currently defined, the way this second step is communicated to the
application is via a status event posted to the event queue associated
with the SA.
The main point of the discussion is that NXP HW does not have a direct
analog to this API. As Nikhil explained it, the "quiescing" function
is assumed to be an application responsibility such that by the time
an application calls odp_ipsec_sa_destroy() it already knows that the
SA is inactive.
When an application is using IPsec in lookaside mode this is a not
unreasonable requirement, as the application explicitly invokes every
IPsec operation, so it knows which operations are in flight.
For inline processing, however, it's less clear what the application
can do since packets may be in process that the application has yet to
see. If an application calls odp_ipsec_sa_destroy() the implementation
can pick a point after which all packets matching that SA are
ignored/dropped, but the issue is what about packets that have been
processed and are sitting in event queues that the application is not
yet aware of?
If an application receives an event that was processed under a
destroyed SA, the concern is that it not segfault or experience some
other anomaly trying to access any application context that it had
associated with that now-defunct SA.
The solution proposed in PR #109[1] is that odp_ipsec_sa_disable() be
allowed to complete synchronously rather than asynchronously, which
means that the NXP implementation can treat it as an effective NOP
since it doesn't map to any HW function. While this solves one
problem, there is still the problem of how to deal with events that
are already enqueued at the time of this call. The intent of
odp_ipsec_sa_disable() is that after the application is assured that
the SA is disabled it may safely call odp_ipsec_sa_destroy() with
assurance that it doesn't have to deal with expired SA handles.
It seems to me there are two ways we can square this circle. The first
would be to say that in the NXP implementation odp_ipsec_sa_destroy()
doesn't actually destroy the SA--it just marks it as destroyed. Actual
cleanup/destruction would occur sometime later (e.g., when
odp_term_global() is called). This would allow any pending events to
be processed normally regardless of when the application calls
odp_ipsec_sa_destroy().
Another approach would rely on how IPsec events themselves are
processed. An IPsec event appears as an ODP_EVENT_PACKET with subtype
ODP_EVENT_PACKET_IPSEC.
A typical event dispatch loop for handling these events might be:
odp_event_t ev;
odp_event_type_t ev_t;
odp_event_subtype_t ev_sub_t;
odp_packet_t pkt;
odp_ipsec_result_t ipsec_res;
odp_sa_t sa;
struct my_sa_context *ctx; /* This is the key application need */
while (1) {
ev = odp_schedule(&queue, ODP_SCHED_WAIT);
ev_t = odp_event_types(ev, &ev_sub_t);
switch (ev_sub_t) {
case ODP_EVENT_PACKET_IPSEC:
pkt = odp_ipsec_packet_from_event(ev);
if (odp_ipsec_result(&ipsec_res, pkt) < 0) { /* Stale
event, discard */
odp_event_free(ev);
continue;
}
if (ipsec_res->status.all != ODP_IPSEC_OK) {
...Handle IPsec failure
odp_event_free(ev);
continue;
}
ctx = odp_ipsec_sa_context(ipsec_res->sa); /* Get our
context from result */
...process the IPsec packet
case /* Other cases go here */
...etc.
}
}
It would seem that a synchronous odp_ipsec_sa_disable() call would not
be a problem if the NXP implementation can guarantee that after
odp_ipsec_sa_destroy() is called, odp_ipsec_result() is guaranteed to
return RC < 0 when called for a packet that references a destroyed SA.
Alternately, an odp_ipsec_result_t could be returned with a status
field that explicitly states that the SA has been destroyed, in which
case it's a simple matter for the application to handle that case as
well without ambiguity.
Based on what Nikhil said this morning about how NXP hardware works,
this approach should be possible. If so this would seem to address
both NXP's and Nokia's concerns about this PR.
Please feel free to make corrections if I've misstated anything here
or if there are other potential solutions that should be explored
here.
Thanks.
---
[1] https://github.com/Linaro/odp/pull/109