Joe, can you please make it work with clang? I sent a patch to ml before. It might still apply, so you can review it. https://travis-ci.org/muvarov/odp/jobs/223572921
the goal is to find good combination of -mcx16 and -latomic flags. And we need to test that it still works on arm due to there is no mcx16 flag. Maxim. On 13.04.2017 17:46, Joe Savage wrote: > Add an example application implementing lock-free IPv4 fragmentation > and reassembly functionality using ODP's packet "concat" and "split". > > Signed-off-by: Joe Savage <joe.sav...@arm.com> > Reviewed-and-tested-by: Bill Fischofer <bill.fischofer at linaro.org> > --- > (This code contribution is provided under the terms of agreement > LES-LTM-21309) > > doc/application-api-guide/examples.dox | 5 + > example/Makefile.am | 1 + > example/ipfragreass/.gitignore | 3 + > example/ipfragreass/Makefile.am | 23 + > example/ipfragreass/odp_ipfragreass.c | 376 +++++++++++ > example/ipfragreass/odp_ipfragreass_atomics.h | 55 ++ > example/ipfragreass/odp_ipfragreass_atomics_arm.h | 120 ++++ > example/ipfragreass/odp_ipfragreass_fragment.c | 99 +++ > example/ipfragreass/odp_ipfragreass_fragment.h | 28 + > example/ipfragreass/odp_ipfragreass_helpers.c | 124 ++++ > example/ipfragreass/odp_ipfragreass_helpers.h | 79 +++ > example/ipfragreass/odp_ipfragreass_ip.h | 251 +++++++ > example/ipfragreass/odp_ipfragreass_reassemble.c | 771 > ++++++++++++++++++++++ > example/ipfragreass/odp_ipfragreass_reassemble.h | 211 ++++++ > example/m4/configure.m4 | 1 + > 15 files changed, 2147 insertions(+) > create mode 100644 example/ipfragreass/.gitignore > create mode 100644 example/ipfragreass/Makefile.am > create mode 100644 example/ipfragreass/odp_ipfragreass.c > create mode 100644 example/ipfragreass/odp_ipfragreass_atomics.h > create mode 100644 example/ipfragreass/odp_ipfragreass_atomics_arm.h > create mode 100644 example/ipfragreass/odp_ipfragreass_fragment.c > create mode 100644 example/ipfragreass/odp_ipfragreass_fragment.h > create mode 100644 example/ipfragreass/odp_ipfragreass_helpers.c > create mode 100644 example/ipfragreass/odp_ipfragreass_helpers.h > create mode 100644 example/ipfragreass/odp_ipfragreass_ip.h > create mode 100644 example/ipfragreass/odp_ipfragreass_reassemble.c > create mode 100644 example/ipfragreass/odp_ipfragreass_reassemble.h > > diff --git a/doc/application-api-guide/examples.dox > b/doc/application-api-guide/examples.dox > index 60d4058..80fe467 100644 > --- a/doc/application-api-guide/examples.dox > +++ b/doc/application-api-guide/examples.dox > @@ -28,3 +28,8 @@ > * @example odp_timer_test.c > * ODP timer example application > */ > + > + /** > + * @example odp_ipfragreass.c > + * ODP IPv4 lock-free fragmentation and reassembly example application > + */ > diff --git a/example/Makefile.am b/example/Makefile.am > index dfc07b6..9503a1b 100644 > --- a/example/Makefile.am > +++ b/example/Makefile.am > @@ -2,6 +2,7 @@ SUBDIRS = classifier \ > generator \ > hello \ > ipsec \ > + ipfragreass \ > l2fwd_simple \ > l3fwd \ > packet \ > diff --git a/example/ipfragreass/.gitignore b/example/ipfragreass/.gitignore > new file mode 100644 > index 0000000..d25d758 > --- /dev/null > +++ b/example/ipfragreass/.gitignore > @@ -0,0 +1,3 @@ > +odp_ipfragreass > +*.log > +*.trs > diff --git a/example/ipfragreass/Makefile.am b/example/ipfragreass/Makefile.am > new file mode 100644 > index 0000000..f805a69 > --- /dev/null > +++ b/example/ipfragreass/Makefile.am > @@ -0,0 +1,23 @@ > +include $(top_srcdir)/example/Makefile.inc > + > +bin_PROGRAMS = odp_ipfragreass$(EXEEXT) > +odp_ipfragreass_LDFLAGS = $(AM_LDFLAGS) -static > +odp_ipfragreass_CFLAGS = $(AM_CFLAGS) -I${top_srcdir}/example > + > +noinst_HEADERS = \ > + $(top_srcdir)/example/ipfragreass/odp_ipfragreass_atomics.h \ > + > $(top_srcdir)/example/ipfragreass/odp_ipfragreass_atomics_arm.h \ > + $(top_srcdir)/example/ipfragreass/odp_ipfragreass_fragment.h \ > + $(top_srcdir)/example/ipfragreass/odp_ipfragreass_helpers.h \ > + $(top_srcdir)/example/ipfragreass/odp_ipfragreass_ip.h \ > + > $(top_srcdir)/example/ipfragreass/odp_ipfragreass_reassemble.h \ > + $(top_srcdir)/example/example_debug.h > + > +dist_odp_ipfragreass_SOURCES = odp_ipfragreass.c \ > + odp_ipfragreass_fragment.c \ > + odp_ipfragreass_helpers.c \ > + odp_ipfragreass_reassemble.c > + > +if test_example > +TESTS = odp_ipfragreass > +endif > diff --git a/example/ipfragreass/odp_ipfragreass.c > b/example/ipfragreass/odp_ipfragreass.c > new file mode 100644 > index 0000000..6ad0db9 > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass.c > @@ -0,0 +1,376 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +/** > + * @file > + * > + * @example odp_ipfragreass.c ODP IPv4 lock-free fragmentation and > reassembly > + */ > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <time.h> > +#include <assert.h> > + > +#include <odp/helper/odph_api.h> > +#include <example_debug.h> > + > +#include "odp_ipfragreass_fragment.h" > +#include "odp_ipfragreass_reassemble.h" > +#include "odp_ipfragreass_helpers.h" > + > +#define NUM_PACKETS 200 /**< Number of packets to fragment/reassemble */ > +#define MAX_WORKERS 32 /**< Maximum number of worker threads */ > +#define FRAGLISTS 16384 /**< Hash map size for reassembly */ > + > +#define MIN_MF_FRAG_SIZE 576 /**< Minimum fragment size */ > +#define MAX_PKT_LEN 8192 /**< Maximum packet size */ > +#define MAX_FRAGS_PER_PKT 6 /**< Maximum number of fragments per packet */ > + > +/** > + * Derived parameters for packet storage (inc. pool configuration) > + */ > +#define POOL_MIN_SEG_LEN IP_HDR_LEN_MIN > +#define POOL_UAREA_SIZE sizeof(struct packet) > +#define MAX_FRAGS (MAX_FRAGS_PER_PKT * NUM_PACKETS) > + > +/** Output queue for fragmentation, input queue for reassembly */ > +static odp_queue_t fragments; > + > +/** Output queue for reassembly, input queue for validation */ > +static odp_queue_t reassembled_pkts; > + > +/** Number of packets reassembled thus far */ > +static odp_atomic_u32_t packets_reassembled; > + > +/** Number of fragments processed per thread in reassembly (for printing) */ > +static struct { > + uint32_t frags; > +} ODP_ALIGNED_CACHE thread_stats[MAX_WORKERS]; > + > +/** Shared hash map structure for reassembly */ > +static union fraglist *fraglists; > + > +/** Barrier for synchronising reassembly worker threads */ > +static odp_barrier_t barrier; > + > +/** > + * Initialise the base structures required for execution of this application > + * > + * @param[out] instance ODP instance handle to initialise > + * @param[out] fragment_pool Output for fragment pool creation > + * @param[out] shm Output for reassembly shared memory creation > + * @param[out] cpumask Output for worker threads CPU mask > + * @param[out] num_workers Output for number of worker threads > + */ > +static void init(odp_instance_t *instance, odp_pool_t *fragment_pool, > + odp_shm_t *shm, odp_cpumask_t *cpumask, int *num_workers) > +{ > + unsigned int seed = time(NULL); > + int i; > + odp_pool_param_t pool_params; > + odp_queue_param_t frag_queue_params; > + odp_queue_param_t reass_queue_params; > + char cpumask_str[ODP_CPUMASK_STR_SIZE]; > + > + srand(seed); > + printf("= Seed: %d\n", seed); > + printf("= MTU: %d\n", MTU); > + > + /* ODP initialisation */ > + if (odp_init_global(instance, NULL, NULL)) { > + fprintf(stderr, "ERROR: ODP global init failed.\n"); > + exit(1); > + } > + if (odp_init_local(*instance, ODP_THREAD_CONTROL)) { > + fprintf(stderr, "ERROR: ODP local init failed.\n"); > + exit(1); > + } > + > + /* Create a pool for packet storage */ > + odp_pool_param_init(&pool_params); > + pool_params.pkt.seg_len = POOL_MIN_SEG_LEN; > + pool_params.pkt.len = MAX_PKT_LEN; > + pool_params.pkt.num = 2 * MAX_FRAGS + MAX_WORKERS; > + pool_params.pkt.uarea_size = POOL_UAREA_SIZE; > + pool_params.type = ODP_POOL_PACKET; > + *fragment_pool = odp_pool_create("packet pool", &pool_params); > + if (*fragment_pool == ODP_POOL_INVALID) { > + fprintf(stderr, "ERROR: packet pool create failed.\n"); > + exit(1); > + } > + > + /* Reserve (and initialise) shared memory for reassembly fraglists */ > + *shm = odp_shm_reserve("fraglists", FRAGLISTS * sizeof(union fraglist), > + ODP_CACHE_LINE_SIZE, 0); > + if (*shm == ODP_SHM_INVALID) { > + fprintf(stderr, "ERROR: odp_shm_reserve\n"); > + exit(1); > + } > + fraglists = odp_shm_addr(*shm); > + if (fraglists == NULL) { > + fprintf(stderr, "ERROR: odp_shm_addr\n"); > + exit(1); > + } > + for (i = 0; i < FRAGLISTS; ++i) > + init_fraglist(&fraglists[i]); > + > + /* Create a queue for holding fragments */ > + odp_queue_param_init(&frag_queue_params); > + frag_queue_params.type = ODP_QUEUE_TYPE_PLAIN; > + frag_queue_params.enq_mode = ODP_QUEUE_OP_MT_UNSAFE; > + fragments = odp_queue_create("fragments", &frag_queue_params); > + if (fragments == ODP_QUEUE_INVALID) { > + fprintf(stderr, "ERROR: odp_queue_create\n"); > + exit(1); > + } > + > + /* Create a queue for holding reassembled packets */ > + odp_queue_param_init(&reass_queue_params); > + reass_queue_params.type = ODP_QUEUE_TYPE_PLAIN; > + reass_queue_params.deq_mode = ODP_QUEUE_OP_MT_UNSAFE; > + reassembled_pkts = odp_queue_create("reassembled packets", > + &reass_queue_params); > + if (reassembled_pkts == ODP_QUEUE_INVALID) { > + fprintf(stderr, "ERROR: odp_queue_create\n"); > + exit(1); > + } > + > + /* Set up worker threads */ > + *num_workers = odp_cpumask_default_worker(cpumask, *num_workers); > + odp_barrier_init(&barrier, *num_workers + 1); > + odp_cpumask_to_str(cpumask, cpumask_str, sizeof(cpumask_str)); > + printf("= Workers: %d\n", *num_workers); > + printf("= CPU Mask: %s (first CPU: %d)\n\n", cpumask_str, > + odp_cpumask_first(cpumask)); > +} > + > +/** > + * Reassembly worker thread function > + * > + * Repeatedly dequeues input fragments, validating them and then passing them > + * to the reassembly procedure "reassemble_ipv4_packets". When a packet has > + * been reassembled, it is added to the output queue, and when NUM_PACKETS > + * packets have been completed the function returns. Thread 0 additionally > + * executes the garbage collection procedure to clean up stale fragments. > + * > + * @param arg The thread number of this worker (masquerading as a pointer) > + * > + * @return Always returns zero > + */ > +static int run_worker(void *arg EXAMPLE_UNUSED) > +{ > + int threadno = odp_thread_id() - 1; > + int iterations = 0; > + odp_event_t ev; > + > + odp_barrier_wait(&barrier); > + while (odp_atomic_load_u32(&packets_reassembled) < NUM_PACKETS) { > + odp_packet_t pkt; > + odp_time_t timestamp; > + odph_ipv4hdr_t *hdr; > + struct packet *fragment; > + int reassembled; > + > + ev = odp_queue_deq(fragments); > + if (ev == ODP_EVENT_INVALID) > + break; > + assert(odp_event_type(ev) == ODP_EVENT_PACKET); > + ++thread_stats[threadno].frags; > + > + pkt = odp_packet_from_event(ev); > + hdr = odp_packet_data(pkt); > + fragment = odp_packet_user_area(pkt); > + timestamp = odp_time_global(); > + assert(fragment != NULL); > + assert(odp_packet_len(pkt) == ipv4hdr_payload_len(*hdr) > + + ipv4hdr_ihl(*hdr)); > + assert(!ipv4hdr_more_fragments(*hdr) || > + (odp_packet_len(pkt) >= MIN_MF_FRAG_SIZE && > + (odp_packet_len(pkt) > + - ipv4hdr_ihl(*hdr)) % 8 == 0)); > + assert(odp_packet_len(pkt) <= MAX_PKT_LEN); > + fragment->handle = pkt; > + fragment->prev = NULL; > + fragment->arrival.t = odp_time_to_ns(timestamp) / TS_RES_NS; > + > + reassembled = reassemble_ipv4_packets(fraglists, FRAGLISTS, > + fragment, 1, > + reassembled_pkts); > + if (reassembled > 0) > + odp_atomic_add_u32(&packets_reassembled, reassembled); > + > + /* > + * Perform garbage collection of stale fragments every 50 > + * iterations. (In real applications, use a timer!) > + */ > + if (threadno == 0 && iterations++ > 50) { > + iterations = 0; > + garbage_collect_fraglists(fraglists, FRAGLISTS, > + reassembled_pkts, 0); > + } > + } > + > + while ((ev = odp_queue_deq(fragments)) != ODP_EVENT_INVALID) { > + assert(odp_event_type(ev) == ODP_EVENT_PACKET); > + odp_packet_free(odp_packet_from_event(ev)); > + } > + > + return 0; > +} > + > +/** > + * ODP fragmentation and reassembly example main function > + */ > +int main(void) > +{ > + odp_instance_t instance; > + odp_pool_t fragment_pool; > + odp_shm_t shm; > + odp_cpumask_t cpumask; > + odph_odpthread_t threads[MAX_WORKERS] = {}; > + odph_odpthread_params_t thread_params; > + odp_packet_t dequeued_pkts[NUM_PACKETS]; > + odp_event_t ev; > + odp_u16be_t ip_id = 0; > + odp_packet_t orig_pkts[NUM_PACKETS]; > + odp_packet_t fragment_buffer[MAX_FRAGS]; > + int total_fragments = 0; > + int i; > + int num_workers = MAX_WORKERS; > + int reassembled; > + > + init(&instance, &fragment_pool, &shm, &cpumask, &num_workers); > + > + /* Packet generation & fragmentation */ > + printf("\n= Fragmenting %d packets...\n", NUM_PACKETS); > + for (i = 0; i < NUM_PACKETS; ++i) { > + odp_packet_t packet; > + int num_fragments; > + > + packet = pack_udp_ipv4_packet(fragment_pool, ip_id++, > + MAX_PKT_LEN, > + MTU + IP_HDR_LEN_MAX + 1); > + if (packet == ODP_PACKET_INVALID) { > + fprintf(stderr, "ERROR: pack_udp_ipv4_packet\n"); > + return 1; > + } > + > + orig_pkts[i] = odp_packet_copy(packet, fragment_pool); > + if (orig_pkts[i] == ODP_PACKET_INVALID) { > + fprintf(stderr, "ERROR: odp_packet_copy\n"); > + return 1; > + } > + > + if (fragment_ipv4_packet(packet, > + &fragment_buffer[total_fragments], > + &num_fragments)) { > + fprintf(stderr, "ERROR: fragment_ipv4_packet\n"); > + return 1; > + } > + > + total_fragments += num_fragments; > + } > + > + /* Shuffle the fragments around so they aren't necessarily in order */ > + printf("\n= Shuffling %d fragments...\n", total_fragments); > + shuffle(fragment_buffer, total_fragments); > + > + /* Insert the fragments into a queue for consumption */ > + for (i = 0; i < total_fragments; ++i) { > + ev = odp_packet_to_event(fragment_buffer[i]); > + > + if (odp_queue_enq(fragments, ev) < 0) { > + fprintf(stderr, "ERROR: odp_queue_enq\n"); > + return 1; > + } > + } > + > + /* Spawn the worker threads for reassembly */ > + memset(&thread_params, 0, sizeof(thread_params)); > + thread_params.start = run_worker; > + thread_params.arg = 0; > + thread_params.thr_type = ODP_THREAD_WORKER; > + thread_params.instance = instance; > + odph_odpthreads_create(threads, &cpumask, &thread_params); > + > + /* Go! */ > + printf("\n= Starting reassembly...\n"); > + odp_barrier_wait(&barrier); > + > + /* Wait for all threads to complete and output statistics */ > + odph_odpthreads_join(threads); > + for (i = 0; i < num_workers; ++i) > + printf("=== Thread %02d processed %3d fragments\n", i, > + thread_stats[i].frags); > + > + /* Dequeue the reassembled packets */ > + for (reassembled = 0; (ev = odp_queue_deq(reassembled_pkts)) != > + ODP_EVENT_INVALID; ++reassembled) { > + assert(reassembled < NUM_PACKETS); > + assert(odp_event_type(ev) == ODP_EVENT_PACKET); > + dequeued_pkts[reassembled] = odp_packet_from_event(ev); > + } > + > + /* Check reassembled packets against the originals */ > + printf("\n= Checking reassembled packets...\n"); > + for (i = 0; i < reassembled; ++i) { > + int j = -1; > + int k; > + odp_packet_t packet = dequeued_pkts[i]; > + uint32_t len = odp_packet_len(packet); > + odph_ipv4hdr_t hdr; > + odph_ipv4hdr_t reassembled_hdr; > + > + reassembled_hdr = *(odph_ipv4hdr_t *)odp_packet_data(packet); > + for (k = 0; k < reassembled; ++k) { > + hdr = *(odph_ipv4hdr_t *)odp_packet_data(orig_pkts[k]); > + if (hdr.src_addr == reassembled_hdr.src_addr && > + hdr.dst_addr == reassembled_hdr.dst_addr && > + hdr.id == reassembled_hdr.id && > + hdr.proto == reassembled_hdr.proto) { > + assert(j < 0); > + j = k; > + } > + } > + assert(j >= 0); > + > + assert(odp_packet_is_valid(packet)); > + assert(len == odp_packet_len(orig_pkts[j])); > + assert(!packet_memcmp(orig_pkts[j], packet, 0, 0, len)); > + } > + printf("=== Successfully reassembled %d of %d packets\n", reassembled, > + NUM_PACKETS); > + assert(reassembled == NUM_PACKETS); > + printf("\n= Complete!\n"); > + > + /* Free packets */ > + for (i = 0; i < reassembled; ++i) > + odp_packet_free(dequeued_pkts[i]); > + for (i = 0; i < NUM_PACKETS; ++i) > + odp_packet_free(orig_pkts[i]); > + garbage_collect_fraglists(fraglists, FRAGLISTS, reassembled_pkts, 1); > + > + /* ODP cleanup and termination */ > + assert(!odp_queue_destroy(fragments)); > + assert(!odp_queue_destroy(reassembled_pkts)); > + assert(!odp_shm_free(shm)); > + if (odp_pool_destroy(fragment_pool)) { > + fprintf(stderr, > + "ERROR: fragment_pool destruction failed\n"); > + return 1; > + } > + if (odp_term_local()) { > + fprintf(stderr, "ERROR: odp_term_local\n"); > + return 1; > + } > + if (odp_term_global(instance)) { > + fprintf(stderr, "ERROR: odp_term_global\n"); > + return 1; > + } > + > + return 0; > +} > diff --git a/example/ipfragreass/odp_ipfragreass_atomics.h > b/example/ipfragreass/odp_ipfragreass_atomics.h > new file mode 100644 > index 0000000..691978e > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_atomics.h > @@ -0,0 +1,55 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_ATOMICS_H_ > +#define ODP_FRAGREASS_PP_ATOMICS_H_ > + > +#include <example_debug.h> > + > +#if __SIZEOF_POINTER__ == 4 > +/** > + * A wrapper function to perform a 64-bit "strong" atomic compare and swap > + * (CAS) operation. This generic version of the function is used if an > + * architecture-specific (e.g. lockless) variant is not specified. > + * > + * @param var The location at which the CAS should take place > + * @param exp A pointer to the expected value > + * @param neu A pointer to the new value to be written on > success > + * @param mo_success The memory order on success > + * @param mo_failure The memory order on failure > + * > + * @return Whether the operation succeeded > + */ > +static inline bool atomic_strong_cas_dblptr(uint64_t *var, uint64_t *exp, > + uint64_t neu, int mo_success, > + int mo_failure) > +{ > + return __atomic_compare_exchange_n(var, exp, neu, 0, mo_success, > + mo_failure); > +} > +#elif __SIZEOF_POINTER__ == 8 > +/** > + * A wrapper function to perform a 128-bit "strong" atomic compare and swap > + * (CAS) operation. This generic version of the function is used if an > + * architecture-specific (e.g. lockless) variant is not specified. > + * > + * @param var The location at which the CAS should take place > + * @param exp A pointer to the expected value > + * @param neu A pointer to the new value to be written on > success > + * @param mo_success The memory order on success > + * @param mo_failure The memory order on failure > + * > + * @return Whether the operation succeeded > + */ > +static inline bool atomic_strong_cas_dblptr(__int128 *var, __int128 *exp, > + __int128 neu, int mo_success, > + int mo_failure) > +{ > + return __atomic_compare_exchange_n(var, exp, neu, 0, mo_success, > + mo_failure); > +} > +#endif > +#endif > diff --git a/example/ipfragreass/odp_ipfragreass_atomics_arm.h > b/example/ipfragreass/odp_ipfragreass_atomics_arm.h > new file mode 100644 > index 0000000..99c37a7 > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_atomics_arm.h > @@ -0,0 +1,120 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_ATOMICS_ARM_H_ > +#define ODP_FRAGREASS_PP_ATOMICS_ARM_H_ > + > +#include <example_debug.h> > + > +#if __SIZEOF_POINTER__ == 8 && defined(__aarch64__) > +static inline __int128 lld(__int128 *var, int mo) > +{ > + __int128 old; > + > + if (mo == __ATOMIC_ACQUIRE) > + __asm__ volatile("ldaxp %0, %H0, [%1]" : "=&r" (old) > + : "r" (var) : "memory"); > + else /* mo == __ATOMIC_RELAXED */ > + __asm__ volatile("ldxp %0, %H0, [%1]" : "=&r" (old) > + : "r" (var) : ); > + return old; > +} > + > +static inline uint32_t scd(__int128 *var, __int128 neu, int mo) > +{ > + uint32_t ret; > + > + if (mo == __ATOMIC_RELEASE) > + __asm__ volatile("stlxp %w0, %1, %H1, [%2]" : "=&r" (ret) > + : "r" (neu), "r" (var) : "memory"); > + else /* mo == __ATOMIC_RELAXED */ > + __asm__ volatile("stxp %w0, %1, %H1, [%2]" : "=&r" (ret) > + : "r" (neu), "r" (var) : ); > + return ret; > +} > + > +static inline bool atomic_strong_cas_dblptr(__int128 *var, __int128 *exp, > + __int128 neu, int mo_success, > + int mo_failure EXAMPLE_UNUSED) > +{ > + register __int128 old; > + register __int128 expected = *exp; > + int ll_mo, sc_mo; > + > + ll_mo = (mo_success != __ATOMIC_RELAXED && > + mo_success != __ATOMIC_RELEASE) ? __ATOMIC_ACQUIRE > + : __ATOMIC_RELAXED; > + sc_mo = (mo_success == __ATOMIC_RELEASE || > + mo_success == __ATOMIC_ACQ_REL || > + mo_success == __ATOMIC_SEQ_CST) ? __ATOMIC_RELEASE > + : __ATOMIC_RELAXED; > + > + /* > + * To prevent spurious failures and ensure atomicity, we must write some > + * value back -- whether it's the value we wanted to write, or the value > + * that is currently there. Repeat until we perform a successful write. > + */ > + do { > + old = lld(var, ll_mo); > + } while (scd(var, old == expected ? neu : old, sc_mo)); > + > + *exp = old; > + return (old == expected); > +} > +#elif __SIZEOF_POINTER__ == 4 && defined(__ARM_ARCH) && __ARM_ARCH == 7 > +static inline uint64_t lld(uint64_t *var, int mo) > +{ > + uint64_t old; > + > + __asm__ volatile("ldrexd %0, %H0, [%1]" : "=&r" (old) : "r" (var) : ); > + if (mo == __ATOMIC_ACQUIRE) > + __asm__ volatile("dmb ish" ::: "memory"); > + return old; > +} > + > +static inline uint32_t scd(uint64_t *var, uint64_t neu, int mo) > +{ > + uint32_t ret; > + > + if (mo == __ATOMIC_RELEASE) > + __asm__ volatile("dmb ish" ::: "memory"); > + __asm__ volatile("strexd %0, %1, %H1, [%2]" : "=&r" (ret) > + : "r" (neu), "r" (var) : ); > + return ret; > +} > + > +static inline bool atomic_strong_cas_dblptr(uint64_t *var, uint64_t *exp, > + uint64_t neu, int mo_success, > + int mo_failure EXAMPLE_UNUSED) > +{ > + register uint64_t old; > + register uint64_t expected = *exp; > + int ll_mo, sc_mo; > + > + ll_mo = (mo_success != __ATOMIC_RELAXED && > + mo_success != __ATOMIC_RELEASE) ? __ATOMIC_ACQUIRE > + : __ATOMIC_RELAXED; > + sc_mo = (mo_success == __ATOMIC_RELEASE || > + mo_success == __ATOMIC_ACQ_REL || > + mo_success == __ATOMIC_SEQ_CST) ? __ATOMIC_RELEASE > + : __ATOMIC_RELAXED; > + > + /* > + * To prevent spurious failures and ensure atomicity, we must write some > + * value back -- whether it's the value we wanted to write, or the value > + * that is currently there. Repeat until we perform a successful write. > + */ > + do { > + old = lld(var, ll_mo); > + } while (scd(var, old == expected ? neu : old, sc_mo)); > + > + *exp = old; > + return (old == expected); > +} > +#else > +#include "odp_ipfragreass_atomics.h" > +#endif > +#endif > diff --git a/example/ipfragreass/odp_ipfragreass_fragment.c > b/example/ipfragreass/odp_ipfragreass_fragment.c > new file mode 100644 > index 0000000..b2fe6ad > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_fragment.c > @@ -0,0 +1,99 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#include <stdio.h> > +#include <assert.h> > + > +#include "odp_ipfragreass_ip.h" > +#include "odp_ipfragreass_fragment.h" > + > +int fragment_ipv4_packet(odp_packet_t orig_packet, odp_packet_t *out, > + int *out_len) > +{ > + int fragments = 0; > + odp_bool_t first_fragment = 1; > + odph_ipv4hdr_t *orig_header = odp_packet_data(orig_packet); > + uint32_t header_len = ipv4hdr_ihl(*orig_header); > + uint32_t orig_len = odp_packet_len(orig_packet); > + uint32_t bytes_remaining = orig_len - header_len; > + uint32_t frag_offset = 0; > + odp_packet_t frag = orig_packet; > + odp_packet_t rest = ODP_PACKET_INVALID; > + > + if (bytes_remaining <= MTU) > + return -1; > + > + /* > + * The main fragmentation loop (continue until all bytes from the > + * original payload have been assigned to a fragment) > + */ > + while (bytes_remaining) { > + odph_ipv4hdr_t *header; > + uint32_t frag_len; > + > + frag_len = (bytes_remaining > MTU ? (MTU + header_len) > + : (bytes_remaining > + + header_len)); > + bytes_remaining -= (frag_len - header_len); > + if (bytes_remaining) { > + uint32_t split_point; > + int split_success; > + > + split_point = first_fragment ? (frag_len) > + : (frag_len - header_len); > + split_success = odp_packet_split(&frag, split_point, > + &rest); > + if (split_success < 0) { > + fprintf(stderr, > + "ERROR: odp_packet_split\n"); > + return -1; > + } else if (first_fragment && split_success > 0) { > + orig_header = odp_packet_data(frag); > + } > + } > + > + /* Add a header to the packet if necessary */ > + if (first_fragment) { > + first_fragment = 0; > + } else { > + if (odp_packet_extend_head(&frag, header_len, NULL, > + NULL) < 0) { > + fprintf(stderr, > + "ERROR: odp_packet_extend_head\n"); > + return -1; > + } > + if (odp_packet_copy_from_mem(frag, 0, header_len, > + orig_header)) { > + fprintf(stderr, > + "ERROR: odp_packet_copy_from_mem\n"); > + return -1; > + } > + } > + > + /* Update the header */ > + header = odp_packet_data(frag); > + ipv4hdr_set_more_fragments(header, bytes_remaining != 0); > + header->tot_len = odp_cpu_to_be_16(frag_len); > + ipv4hdr_set_fragment_offset_oct(header, (frag_offset / 8)); > + assert((bytes_remaining == 0) || > + (odp_packet_len(frag) == (MTU + header_len))); > + assert((bytes_remaining != 0) || > + (ipv4hdr_reass_payload_len(*header) + header_len > + == orig_len)); > + odp_packet_l3_offset_set(frag, 0); > + header->chksum = 0; > + odph_ipv4_csum_update(frag); > + > + out[fragments++] = frag; > + frag = rest; > + frag_offset += (frag_len - header_len); > + } > + > + if (out_len) > + *out_len = fragments; > + > + return 0; > +} > diff --git a/example/ipfragreass/odp_ipfragreass_fragment.h > b/example/ipfragreass/odp_ipfragreass_fragment.h > new file mode 100644 > index 0000000..f497fad > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_fragment.h > @@ -0,0 +1,28 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_FRAG_H_ > +#define ODP_FRAGREASS_PP_FRAG_H_ > + > +#include <odp/helper/ip.h> > + > +#define MTU 1480 /**< IPv4 payload MTU */ > + > +ODP_STATIC_ASSERT(!(MTU % 8), "ODPFRAG_MTU__SIZE_ERROR"); > + > +/** > + * Break apart a larger-than-MTU packet into smaller IPv4 fragments > + * > + * @param orig_packet The larger-than-MTU packet to fragment > + * @param[out] out The packet buffer to write fragments out to > + * @param[out] out_len The number of fragments produced > + * > + * @return 0 on success, -1 otherwise > + */ > +int fragment_ipv4_packet(odp_packet_t orig_packet, odp_packet_t *out, > + int *out_len); > + > +#endif > diff --git a/example/ipfragreass/odp_ipfragreass_helpers.c > b/example/ipfragreass/odp_ipfragreass_helpers.c > new file mode 100644 > index 0000000..e8d89b9 > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_helpers.c > @@ -0,0 +1,124 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#include <stdio.h> > +#include <stdlib.h> > + > +#include <assert.h> > +#include <arpa/inet.h> > + > +#include "odp_ipfragreass_helpers.h" > +#include "odp_ipfragreass_ip.h" > + > +#define IP_SRC_ADDR 0xC0A80001 /* 192.168.0.1 */ > +#define IP_DST_ADDR 0xC0A80002 /* 192.168.0.2 */ > + > +static void set_random_payload(odp_packet_t packet, uint32_t payload_offset, > + uint32_t size) > +{ > + uint32_t i, j; > + uint32_t bytes_remaining; > + unsigned char *buffer; > + unsigned char seed = rand() % (UINT8_MAX + 1); > + > + /* > + * Set the payload to a run of consecutive numbers from a random seed. > + * Not completely random as some structure is good for debugging. > + */ > + for (i = 0; i < size; ++i) { > + buffer = odp_packet_offset(packet, payload_offset + i, > + &bytes_remaining, NULL); > + for (j = 0; i < size && j < bytes_remaining; ++j, ++i) > + buffer[j] = (unsigned char)(seed + i); > + } > +} > + > +odp_packet_t pack_udp_ipv4_packet(odp_pool_t pool, odp_u16be_t ip_id, > + uint32_t max_size, uint32_t min_size) > +{ > + odp_packet_t result; > + unsigned char *buffer; > + odph_ipv4hdr_t *header; > + uint32_t size; > + uint32_t header_len; > + uint32_t header_len_words; > + > + size = (rand() % (max_size - min_size)) + min_size; > + result = odp_packet_alloc(pool, size); > + if (result == ODP_PACKET_INVALID) > + return ODP_PACKET_INVALID; > + odp_packet_ts_set(result, odp_time_global()); > + > + /* Set the IPv4 header */ > + header_len_words = (rand() % (IP_IHL_MAX - IP_IHL_MIN + 1)); > + header_len_words += IP_IHL_MIN; > + header_len = WORDS_TO_BYTES(header_len_words); > + assert(header_len >= IP_HDR_LEN_MIN && header_len <= IP_HDR_LEN_MAX); > + odp_packet_l3_offset_set(result, 0); > + odp_packet_has_ipv4_set(result, 1); > + buffer = odp_packet_data(result); > + header = (odph_ipv4hdr_t *)buffer; > + ipv4hdr_init(header); > + header->id = odp_cpu_to_be_16(ip_id); > + header->proto = ODPH_IPPROTO_UDP; > + header->src_addr = odp_cpu_to_be_32(IP_SRC_ADDR); > + header->dst_addr = odp_cpu_to_be_32(IP_DST_ADDR); > + ipv4hdr_set_ihl_words(header, header_len_words); > + ipv4hdr_set_payload_len(header, size - header_len); > + header->chksum = 0; > + odph_ipv4_csum_update(result); > + > + /* Set the payload */ > + size -= header_len; > + set_random_payload(result, header_len, size); > + > + return result; > +} > + > +void shuffle(odp_packet_t *packets, int num_packets) > +{ > + int i; > + > + if (num_packets <= 1) > + return; > + > + for (i = 0; i < num_packets; ++i) { > + odp_packet_t tmp = packets[i]; > + int j = rand() % num_packets; > + > + packets[i] = packets[j]; > + packets[j] = tmp; > + } > +} > + > +int packet_memcmp(odp_packet_t a, odp_packet_t b, uint32_t offset_a, > + uint32_t offset_b, uint32_t length) > +{ > + uint32_t i = 0; > + void *data_a, *data_b; > + > + while (i < length) { > + int status; > + uint32_t bytes_remaining_a; > + uint32_t bytes_remaining_b; > + uint32_t bytes_remaining; > + > + data_a = odp_packet_offset(a, offset_a + i, &bytes_remaining_a, > + NULL); > + data_b = odp_packet_offset(b, offset_b + i, &bytes_remaining_b, > + NULL); > + bytes_remaining = min(bytes_remaining_a, bytes_remaining_b); > + bytes_remaining = min(bytes_remaining, length - i); > + assert(bytes_remaining != 0 && data_a && data_b); > + > + status = memcmp(data_a, data_b, bytes_remaining); > + if (status != 0) > + return status; > + i += bytes_remaining; > + } > + > + return 0; > +} > diff --git a/example/ipfragreass/odp_ipfragreass_helpers.h > b/example/ipfragreass/odp_ipfragreass_helpers.h > new file mode 100644 > index 0000000..ffb3bfb > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_helpers.h > @@ -0,0 +1,79 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_HELPERS_H_ > +#define ODP_FRAGREASS_PP_HELPERS_H_ > + > +#include <odp/helper/ip.h> > + > +#if defined(__ARM_ARCH) > +#include "odp_ipfragreass_atomics_arm.h" > +#else > +#include "odp_ipfragreass_atomics.h" > +#endif > + > +/** > + * Generate a random IPv4 UDP packet from the specified parameters > + * > + * @param pool The pool used to allocate the packet > + * @param ip_id The IP ID of the packet to be generated > + * @param max_size The maximum size of the generated packet > + * @param min_size The minimum size of the generated packet > + * > + * @return A handle to the generated packet > + */ > +odp_packet_t pack_udp_ipv4_packet(odp_pool_t pool, odp_u16be_t ip_id, > + uint32_t max_size, uint32_t min_size); > + > +/** > + * Roughly perform a random shuffle on an array of packets > + * > + * @param packets A pointer to the packets to shuffle > + * @param num_packets The number of packets in the array > + */ > +void shuffle(odp_packet_t *packets, int num_packets); > + > +/** > + * Compare the contents of two packets > + * > + * @param a The first packet to compare > + * @param b The second packet to compare > + * @param offset_a The offset in the first packet to begin comparing at > + * @param offset_b The offset in the second packet to begin comparing at > + * @param length The number of bytes to compare > + * > + * @return Returns the same values as memcmp (0 if both packets are equal) > + */ > +int packet_memcmp(odp_packet_t a, odp_packet_t b, uint32_t offset_a, > + uint32_t offset_b, uint32_t length); > + > +/** > + * Get the smallest of two uint32_t values > + * > + * @param a The first value > + * @param b The second value > + * > + * @return The smallest of the two input values > + */ > +static inline uint32_t min(uint32_t a, uint32_t b) > +{ > + return a < b ? a : b; > +} > + > +/** > + * Get the largest of two uint32_t values > + * > + * @param a The first value > + * @param b The second value > + * > + * @return The largest of the two input values > + */ > +static inline uint32_t max(uint32_t a, uint32_t b) > +{ > + return a > b ? a : b; > +} > + > +#endif > diff --git a/example/ipfragreass/odp_ipfragreass_ip.h > b/example/ipfragreass/odp_ipfragreass_ip.h > new file mode 100644 > index 0000000..e7281e5 > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_ip.h > @@ -0,0 +1,251 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_IP_H_ > +#define ODP_FRAGREASS_PP_IP_H_ > + > +#include <odp/helper/ip.h> > + > +#define IP_HDR_LEN_MIN ODPH_IPV4HDR_LEN > +#define IP_HDR_LEN_MAX 60 > +#define IP_IHL_MIN ODPH_IPV4HDR_IHL_MIN > +#define IP_IHL_MAX 15 > +#define IP_OCTET_MAX ((1U << 14U) - 1U) /* 65535 bytes, 8192 octs, 14 bits */ > +#define IP_FRAGOFF_MAX 8192 /* 13 bits */ > + > +#define IP_FRAG_RESV 0x8000 /**< "Reserved" fragment flag (must be zero) */ > +#define IP_FRAG_DONT 0x4000 /**< "Don't Fragment" (DF) fragment flag */ > +#define IP_FRAG_MORE 0x2000 /**< "More Fragments" (MF) fragment flag */ > +#define IP_FRAG_MASK 0x1fff /**< "Fragment Offset" mask */ > + > +#define BYTES_TO_OCTS(bytes) (((bytes) + 7U) / 8U) > +#define OCTS_TO_BYTES(bytes) ((bytes) * 8) > +#define WORDS_TO_BYTES(words) ((words) * 4) > + > +/** > + * Initialise an IPv4 header structure > + * > + * @param h A pointer to the header structure to initialise > + */ > +static inline void ipv4hdr_init(odph_ipv4hdr_t *h) > +{ > + h->ver_ihl = 0x45; /* IHL of 5, Version of 4 [0x45 = 0100 0101] */ > + h->tos = 0; > + h->tot_len = odp_cpu_to_be_16(IP_HDR_LEN_MIN); > + h->frag_offset = 0; > + h->ttl = UINT8_MAX; > + h->chksum = 0; > +} > + > +/** > + * Get the Internet Header Length (IHL) of an IPv4 header in words > + * > + * @param h The header to get the IHL of > + * > + * @return The IHL of "h" in words > + */ > +static inline uint8_t ipv4hdr_ihl_words(odph_ipv4hdr_t h) > +{ > + return ODPH_IPV4HDR_IHL(h.ver_ihl); > +} > + > +/** > + * Get the Internet Header Length (IHL) of an IPv4 header in bytes > + * > + * @param h The header to get the IHL of > + * > + * @return The IHL of "h" in bytes > + */ > +static inline uint8_t ipv4hdr_ihl(odph_ipv4hdr_t h) > +{ > + return WORDS_TO_BYTES(ipv4hdr_ihl_words(h)); > +} > + > +/** > + * Set the Internet Header Length (IHL) of an IPv4 header in words > + * > + * @param h A pointer to the header to set the IHL of > + * @param ihl The new IHL in words > + */ > +static inline void ipv4hdr_set_ihl_words(odph_ipv4hdr_t *h, uint8_t ihl) > +{ > + h->ver_ihl = (ODPH_IPV4HDR_VER(h->ver_ihl) << 4) > + | ODPH_IPV4HDR_IHL(ihl); > +} > + > +/** > + * Check whether the "Don't Fragment" (DF) flag is set in an IPv4 header > + * > + * @param h The header to check the DF flag of > + * > + * @return Whether the DF flag is set > + */ > +static inline bool ipv4hdr_dont_fragment(odph_ipv4hdr_t h) > +{ > + return (h.frag_offset & odp_cpu_to_be_16(IP_FRAG_DONT)); > +} > + > +/** > + * Set the "Don't Fragment" (DF) flag of an IPv4 header > + * > + * @param h A pointer to the header to set the DF of > + * @param df Whether the DF flag should be set or unset > + */ > +static inline void ipv4hdr_set_dont_fragment(odph_ipv4hdr_t *h, bool df) > +{ > + if (df) > + h->frag_offset |= odp_cpu_to_be_16(IP_FRAG_DONT); > + else > + h->frag_offset &= ~odp_cpu_to_be_16(IP_FRAG_DONT); > +} > + > +/** > + * Check whether the "More Fragments" (MF) flag is set in an IPv4 header > + * > + * @param h The header to check the MF flag of > + * > + * @return Whether the MF flag is set > + */ > +static inline bool ipv4hdr_more_fragments(odph_ipv4hdr_t h) > +{ > + return (h.frag_offset & odp_cpu_to_be_16(IP_FRAG_MORE)); > +} > + > +/** > + * Set the "More Fragments" (MF) flag of an IPv4 header > + * > + * @param h A pointer to the header to set the MF of > + * @param mf Whether the MF flag should be set or unset > + */ > +static inline void ipv4hdr_set_more_fragments(odph_ipv4hdr_t *h, bool mf) > +{ > + if (mf) > + h->frag_offset |= odp_cpu_to_be_16(IP_FRAG_MORE); > + else > + h->frag_offset &= ~odp_cpu_to_be_16(IP_FRAG_MORE); > +} > + > +/** > + * Check whether an IPv4 header represents an IPv4 fragment or not > + * > + * @param h The header to check > + * > + * @return Whether "h" is the header of an IPv4 fragment or not > + */ > +static inline bool ipv4hdr_is_fragment(odph_ipv4hdr_t h) > +{ > + return (h.frag_offset & odp_cpu_to_be_16(IP_FRAG_MORE | IP_FRAG_MASK)); > +} > + > +/** > + * Get the fragment offset of an IPv4 header in octets > + * > + * @param h The header to get the fragment offset of > + * > + * @return The fragment offset of "h" in octets > + */ > +static inline uint16_t ipv4hdr_fragment_offset_oct(odph_ipv4hdr_t h) > +{ > + return (odp_be_to_cpu_16(h.frag_offset) & IP_FRAG_MASK); > +} > + > +/** > + * Set the fragment offset of an IPv4 header in octets > + * > + * @param h A pointer to the header to set the fragment offset of > + * @param offset The new fragment offset in octets > + */ > +static inline void ipv4hdr_set_fragment_offset_oct(odph_ipv4hdr_t *h, > + uint16_t offset) > +{ > + h->frag_offset = odp_cpu_to_be_16((odp_be_to_cpu_16(h->frag_offset) > + & ~IP_FRAG_MASK) | offset); > +} > + > +/** > + * Get the payload length of an IPv4 header in bytes > + * > + * @param h The header to get the payload length of > + * > + * @return The payload length of "h" in bytes > + */ > +static inline uint16_t ipv4hdr_payload_len(odph_ipv4hdr_t h) > +{ > + uint32_t packet_len = odp_be_to_cpu_16(h.tot_len); > + uint32_t header_size = ipv4hdr_ihl(h); > + > + return (uint16_t)(packet_len >= header_size ? (packet_len - header_size) > + : 0); > +} > + > +/** > + * Get the payload length of an IPv4 header in octets > + * > + * @param h The header to get the payload length of > + * > + * @return The payload length of "h" in octets > + */ > +static inline uint16_t ipv4hdr_payload_len_oct(odph_ipv4hdr_t h) > +{ > + return BYTES_TO_OCTS(ipv4hdr_payload_len(h)); > +} > + > +/** > + * Set the payload length of an IPv4 header in bytes > + * > + * @param h A pointer to the header to set the payload length of > + * @param len The new payload length in bytes > + */ > +static inline void ipv4hdr_set_payload_len(odph_ipv4hdr_t *h, uint16_t len) > +{ > + h->tot_len = odp_cpu_to_be_16(len + ipv4hdr_ihl(*h)); > +} > + > +/** > + * Get the upper bound length in bytes of the reassembled payload that this > + * fragment contributes to from an IPv4 header > + * > + * If this is called with the final fragment in a series, the exact length of > + * the reassembled payload is computed. Otherwise, UINT16_MAX is returned. > + * > + * @param h The header to get the reassembled payload length from > + * > + * @return The upper bound for the reassembled payload length of "h" in bytes > + */ > +static inline uint16_t ipv4hdr_reass_payload_len(odph_ipv4hdr_t h) > +{ > + uint16_t fragment_offset_oct; > + > + if (ipv4hdr_more_fragments(h)) > + return UINT16_MAX; > + > + fragment_offset_oct = ipv4hdr_fragment_offset_oct(h); > + return OCTS_TO_BYTES(fragment_offset_oct) + ipv4hdr_payload_len(h); > +} > + > +/** > + * Get the upper bound length in octets of the reassembled payload that this > + * fragment contributes to from an IPv4 header > + * > + * If this is called with the final fragment in a series, the exact length of > + * the reassembled payload is computed. Otherwise, IP_OCTET_MAX is returned. > + * > + * @param h The header to estimate the reassembled payload length from > + * > + * @return The estimate for the reassembled payload length of "h" in octets > + */ > +static inline uint16_t ipv4hdr_reass_payload_len_oct(odph_ipv4hdr_t h) > +{ > + uint16_t fragment_offset_oct; > + > + if (ipv4hdr_more_fragments(h)) > + return IP_OCTET_MAX; > + > + fragment_offset_oct = ipv4hdr_fragment_offset_oct(h); > + return fragment_offset_oct + ipv4hdr_payload_len_oct(h); > +} > + > +#endif > diff --git a/example/ipfragreass/odp_ipfragreass_reassemble.c > b/example/ipfragreass/odp_ipfragreass_reassemble.c > new file mode 100644 > index 0000000..fd1618f > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_reassemble.c > @@ -0,0 +1,771 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <assert.h> > + > +#include "odp_ipfragreass_reassemble.h" > +#include "odp_ipfragreass_helpers.h" > + > +#define ROT(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) > + > +/** > + * Check whether two packets have the same flow (src/dst/id/proto) > + * > + * @param current The first packet to compare > + * @param frag The second packet to compare > + * > + * @return true if the flows match, false otherwise > + */ > +static inline odp_bool_t equal_flow(struct packet *current, struct packet > *frag) > +{ > + odph_ipv4hdr_t *curr_h = odp_packet_data(current->handle); > + odph_ipv4hdr_t *frag_h = odp_packet_data(frag->handle); > + > + return (memcmp(&curr_h->src_addr, &frag_h->src_addr, > + sizeof(curr_h->src_addr) + sizeof(curr_h->dst_addr)) > + == 0 && > + curr_h->id == frag_h->id && > + curr_h->proto == frag_h->proto); > +} > + > +/** > + * Check whether one packet has a "later" flow than another > + * > + * @param current The first packet to compare > + * @param frag The second packet to compare > + * > + * @return true if the first packet's flow is later, false otherwise > + */ > +static inline odp_bool_t later_flow(struct packet *current, struct packet > *frag) > +{ > + odph_ipv4hdr_t *curr_h = odp_packet_data(current->handle); > + odph_ipv4hdr_t *frag_h = odp_packet_data(frag->handle); > + > + return (memcmp(&curr_h->src_addr, &frag_h->src_addr, > + sizeof(curr_h->src_addr) + sizeof(curr_h->dst_addr)) > + > 0 || > + curr_h->id > frag_h->id || > + curr_h->proto > frag_h->proto); > +} > + > +/** > + * Find the earliest of two fraglist timestamps, considering a "now" > timestamp > + * > + * @param a The first timestamp > + * @param b The second timestamp > + * @param now A timestamp indication of the time "now" > + * > + * @return The earliest of the first and second timestamps > + */ > +static inline struct flts earliest(struct flts a, struct flts b, > + struct flts now) > +{ > + struct flts result; > + struct flts elapsed_a; > + struct flts elapsed_b; > + > + now.t += TS_NOW_TOLERANCE; > + elapsed_a.t = now.t - a.t; > + elapsed_b.t = now.t - b.t; > + result.t = now.t - max(elapsed_a.t, elapsed_b.t); > + return result; > +} > + > +/** > + * Hash the flow information within an IPv4 header > + * > + * @param hdr A pointer to the header to hash > + * > + * @return A hash of the src/dst/id/proto information in the header > + */ > +static inline uint32_t hash(odph_ipv4hdr_t *hdr) > +{ > + uint32_t a = hdr->src_addr; > + uint32_t b = hdr->dst_addr; > + uint32_t c = hdr->id << 16 | hdr->proto; > + > + /* A degenerate 3x32-bit Jenkins hash */ > + c ^= b; > + c -= ROT(b, 14); > + a ^= c; > + a -= ROT(c, 11); > + b ^= a; > + b -= ROT(a, 25); > + c ^= b; > + c -= ROT(b, 16); > + a ^= c; > + a -= ROT(c, 4); > + b ^= a; > + b -= ROT(a, 14); > + c ^= b; > + c -= ROT(b, 24); > + return c; > +} > + > +/** > + * Check whether one fragment is "later" than another, considering a > timestamp > + * of "now" > + * > + * This definition of "later" relies firstly on flow, then on the endpoint of > + * the fragment, then on the fragment offset, then on arrival time. > + * > + * @param a The first fragment to compare > + * @param b The second fragment to compare > + * @param now A timestamp indication of the time "now" > + * > + * @return true if the first fragment is "later", false otherwise > + */ > +static inline odp_bool_t later_fragment(struct packet *a, struct packet *b, > + struct flts now) > +{ > + odph_ipv4hdr_t hdr_a = *(odph_ipv4hdr_t *)odp_packet_data(a->handle); > + odph_ipv4hdr_t hdr_b = *(odph_ipv4hdr_t *)odp_packet_data(b->handle); > + uint32_t offset_a = ipv4hdr_fragment_offset_oct(hdr_a); > + uint32_t offset_b = ipv4hdr_fragment_offset_oct(hdr_b); > + uint32_t payload_len_a = ipv4hdr_payload_len(hdr_a); > + uint32_t payload_len_b = ipv4hdr_payload_len(hdr_b); > + uint32_t endpoint_a = OCTS_TO_BYTES(offset_a) + payload_len_a; > + uint32_t endpoint_b = OCTS_TO_BYTES(offset_b) + payload_len_b; > + > + if (later_flow(a, b)) { > + return 1; > + } else if (equal_flow(a, b)) { > + if (endpoint_a > endpoint_b) { > + return 1; > + } else if (endpoint_a == endpoint_b) { > + if (offset_a < offset_b) { > + return 1; > + } else if (offset_a == offset_b) { > + return b->arrival.t == earliest(a->arrival, > + b->arrival, > + now).t; > + } > + } > + } > + > + return 0; > +} > + > +/** > + * Attempt to extract a whole packet from a list of sorted fragments > + * > + * If a complete packet is formed, its tail pointer is returned, and the tail > + * pointer for the remaining packets is written out to remaining_packets. > + * > + * @param[in] tail The tail of the list of fragments to parse > + * @param[out] remaining_packets The pointer to any remaining packets > + * @param[out] made_changes Whether any changes were made to the fragments > + * > + * @return The tail pointer of any reassembled packet, or NULL otherwise > + */ > +static struct packet *extract_complete_packet(struct packet *tail, > + struct packet **remaining_packets, > + int *made_changes) > +{ > + /* > + * Iterate through the flows in this fragment list (until a packet is > + * reassembled). > + */ > + while (tail) { > + /* > + * Work backwards through the fragments in a single flow, > + * attempting to glue together a whole packet. Upon finding a > + * discontinuity, break out of the loop for this flow and try > + * the next (if there is one). > + */ > + struct packet *current = tail; > + odph_ipv4hdr_t tail_hdr; > + uint16_t final_frag_offset; > + uint16_t expected_frag_offset; > + > + tail_hdr = *(odph_ipv4hdr_t *)odp_packet_data(tail->handle); > + final_frag_offset = ipv4hdr_fragment_offset_oct(tail_hdr); > + expected_frag_offset = final_frag_offset; > + while (current) { > + odph_ipv4hdr_t curr_hdr; > + uint16_t curr_offset_oct; > + odp_bool_t final_fragment; > + struct packet *prev; > + void *tmp; > + odph_ipv4hdr_t prev_hdr; > + uint16_t prev_off_oct; > + uint16_t prev_oct; > + > + tmp = odp_packet_data(current->handle); > + curr_hdr = *(odph_ipv4hdr_t *)tmp; > + curr_offset_oct = ipv4hdr_fragment_offset_oct(curr_hdr); > + final_fragment = (curr_offset_oct == final_frag_offset); > + > + /* > + * If the final fragment in the chain has "More > + * Fragments" set, it's not the final packet of the > + * datagram as a whole. > + */ > + if (final_fragment && ipv4hdr_more_fragments(curr_hdr)) > + break; > + > + /* > + * If this is the first fragment in a chain, we may have > + * completed the reassembly of a whole packet. > + */ > + prev = prev_packet(*current); > + if (prev == NULL || !equal_flow(current, prev)) { > + if (curr_offset_oct) > + break; > + > + /* > + * Extract the complete packet from the list of > + * remaining packets > + */ > + if (remaining_packets) > + *remaining_packets = prev; > + set_prev_packet(current, NULL); > + if (made_changes) > + *made_changes = 1; > + return tail; > + } > + > + /* > + * Fragments should be consistent with those previously > + * processed > + */ > + tmp = odp_packet_data(prev->handle); > + prev_hdr = *(odph_ipv4hdr_t *)tmp; > + prev_off_oct = ipv4hdr_fragment_offset_oct(prev_hdr); > + prev_oct = BYTES_TO_OCTS(ipv4hdr_payload_len(prev_hdr)); > + if (curr_offset_oct != prev_off_oct + prev_oct) { > + if (prev_off_oct + prev_oct < curr_offset_oct) { > + /* > + * If there's no overlap, this is just a > + * regular discontinuity > + */ > + break; > + } > + > + /* > + * Fragment duplication or overlap has occurred! > + * We don't handle such occurrences in this > + * simple example application. > + */ > + assert(0); > + break; > + } > + > + expected_frag_offset -= prev_oct; > + current = prev; > + } > + > + /* > + * Since we haven't had any luck finding a whole packet within > + * this flow, let's try to look at other flows in this fraglist > + * (if there are any others). > + */ > + if (!current) { > + tail = NULL; > + break; > + } > + > + while (prev_packet(*current) && > + equal_flow(current, prev_packet(*current))) { > + current = prev_packet(*current); > + } > + tail = prev_packet(*current); > + remaining_packets = prev_packet_ptr(current); > + } > + > + return NULL; > +} > + > +/* > + * Glue together a list of fragments sorted by fragment offset, writing the > + * result to an output queue > + * > + * @param tail The tail pointer to the list of fragments to reassemble > + * @param out The output queue to write the result to > + * > + * @return 0 on success, -1 otherwise > + */ > +static int send_packet(struct packet *tail, odp_queue_t out) > +{ > + struct packet result = *tail; > + struct packet *current = prev_packet(result); > + odph_ipv4hdr_t *header; > + uint32_t length; > + > + /* > + * Reassemble the complete packet (working backwards from the last > + * fragment) > + */ > + while (current && equal_flow(current, &result)) { > + struct packet new_result = *current; > + int concat_success; > + > + current = prev_packet(new_result); > + header = odp_packet_data(result.handle); > + odp_packet_pull_head(result.handle, ipv4hdr_ihl(*header)); > + concat_success = odp_packet_concat(&new_result.handle, > + result.handle); > + if (concat_success < 0) { > + fprintf(stderr, "ERROR: odp_packet_concat\n"); > + return -1; > + } > + result = new_result; > + } > + > + /* Fix the header */ > + header = odp_packet_data(result.handle); > + length = odp_packet_len(result.handle); > + assert(length >= IP_HDR_LEN_MIN || length <= UINT16_MAX); > + header->tot_len = odp_cpu_to_be_16(length); > + ipv4hdr_set_more_fragments(header, 0); > + ipv4hdr_set_fragment_offset_oct(header, 0); > + header->chksum = 0; > + odph_ipv4_csum_update(result.handle); > + > + assert(odp_queue_enq(out, odp_packet_to_event(result.handle)) >= 0); > + return 0; > +} > + > +/** > + * Sort a fraglist using the "later_fragment" function > + * > + * @param fl A pointer to the fraglist to sort > + * @param now A timestamp indication of the time "now" > + */ > +static void sort_fraglist(union fraglist *fl, struct flts now) > +{ > + struct packet *to_insert = fl->tail; > + > + fl->tail = NULL; > + while (to_insert) { > + struct packet *to_insert_next = prev_packet(*to_insert); > + > + if (fl->tail == NULL || > + later_fragment(to_insert, fl->tail, now)) { > + set_prev_packet(to_insert, fl->tail); > + fl->tail = to_insert; > + } else { > + struct packet *current = fl->tail; > + > + while (prev_packet(*current) && > + later_fragment(prev_packet(*current), to_insert, > + now)) { > + current = prev_packet(*current); > + } > + set_prev_packet(to_insert, prev_packet(*current)); > + set_prev_packet(current, to_insert); > + } > + to_insert = to_insert_next; > + } > +} > + > +/** > + * Add a thread local fraglist to a shared fraglist > + * > + * @param fl Pointer to the shared fraglist to add "frags" to > + * @param frags The thread local fraglist to add to "fl" > + * @param frags_head Pointer to the head fragment of "frags" > + * @param now A timestamp indication of the time "now" > + * @param out The queue to which reassembled packets should > be written > + * @param dont_assemble Whether reassembly should be attempted by > default > + * > + * @return The number of packets reassembled and sent to the output queue > + */ > +static int add_fraglist_to_fraglist(union fraglist *fl, union fraglist frags, > + struct packet *frags_head, struct flts now, > + odp_queue_t out, odp_bool_t dont_assemble) > +{ > + int reassembled = 0; > + > + /* > + * We may need to recursively call this function a number of times, > + * keeping count of the total number of packets reassembled. Sadly, > + * tail call optimisation doesn't seem to be working very well, so > + * we're using good ol' fashioned GOTOs instead. > + */ > +redo:; > + union fraglist oldfl; > + union fraglist newfl; > + union fraglist nullfl; > + struct flts oldfl_earliest; > + struct flts frags_earliest; > + > + __atomic_load(&fl->half[0], &oldfl.half[0], __ATOMIC_RELAXED); > + __atomic_load(&fl->half[1], &oldfl.half[1], __ATOMIC_RELAXED); > + > + /* > + * If we're updating a non-empty fraglist, we should always attempt > + * reassembly! > + */ > + if (oldfl.tail != NULL) > + dont_assemble = 0; > + > + /* Insert the new fragment(s) to the tail of the fraglist */ > + set_prev_packet(frags_head, oldfl.tail); > + newfl.tail = frags.tail; > + > + /* > + * Update the fraglist variables (accumulating the length of the > + * received pieces into "part_len", and updating the perceived 'true' > + * length of the whole packet along with the timestamp of the earliest > + * fragment in this list). > + */ > + oldfl_earliest.t = oldfl.earliest; > + frags_earliest.t = frags.earliest; > + newfl.part_len = min(IP_OCTET_MAX, oldfl.part_len + frags.part_len); > + newfl.whole_len = min(oldfl.whole_len, frags.whole_len); > + newfl.earliest = (oldfl.tail == NULL ? frags.earliest > + : earliest(oldfl_earliest, > + frags_earliest, > + now).t); > + > + /* > + * Check if it looks like we have all the fragments for a whole packet > + * yet. If not, just write out our changes and move on. > + */ > + if (newfl.part_len < newfl.whole_len || dont_assemble) { > + if (!atomic_strong_cas_dblptr(&fl->raw, &oldfl.raw, newfl.raw, > + __ATOMIC_RELEASE, > + __ATOMIC_RELAXED)) { > + /* Failed to add this fragment? Try again. */ > + set_prev_packet(frags_head, NULL); > + goto redo; > + } > + return reassembled; > + } > + > + /* > + * It /appears/ that we have all the fragments for a packet. Things are > + * not always as they appear, however, particularly in the case of a > + * hash map collision where part_len and whole_len may be incorrect > + * (and, hence, must be verified). > + * > + * Take exclusive ownership over this fraglist while we attempt > + * reassembly. If we're truly done with it, then this releases the slot, > + * otherwise we'll update the slot with our changes later. > + */ > + init_fraglist(&nullfl); > + if (!atomic_strong_cas_dblptr(&fl->raw, &oldfl.raw, nullfl.raw, > + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { > + /* Failed to take this fraglist? Try again. */ > + set_prev_packet(frags_head, NULL); > + goto redo; > + } > + > + /* > + * Find any complete packets within the fraglist, cut them out of the > + * list, and send them to the output queue. Note that there may be > + * several complete packets, as we may have added multiple new fragments > + * into the fraglist. > + */ > + struct packet *remaining_packets; > + struct packet *complete_datagram; > + int fraglist_changed = 0; > + int call_changed_fraglist = 0; > + union fraglist update; > + > + sort_fraglist(&newfl, now); > + remaining_packets = newfl.tail; > + dont_assemble = 1; > + while ((complete_datagram = > + extract_complete_packet(remaining_packets, &remaining_packets, > + &call_changed_fraglist)) || > + call_changed_fraglist) { > + fraglist_changed = 1; > + if (complete_datagram) { > + assert(!send_packet(complete_datagram, out)); > + ++reassembled; > + dont_assemble = 0; > + } > + call_changed_fraglist = 0; > + } > + > + /* No remaining fragments in this fraglist? We're done. */ > + if (!remaining_packets) > + return reassembled; > + > + /* > + * If there are still fragments in this fraglist, we have changes to > + * write back. > + * > + * Note that we may have to reassemble more packets, as adding the > + * fragments this thread has exclusive access to into the shared > + * fraglist may entail new packets being completed. Thus, we have to > + * repeat this whole add_fraglist_to_fraglist process with the remaining > + * fragments. > + */ > + update.tail = remaining_packets; > + update.part_len = newfl.part_len; > + update.whole_len = newfl.whole_len; > + update.earliest = newfl.earliest; > + > + /* > + * We've cut fragments from the fragment list chain, and so should > + * recalculate the part_len, whole_len, and earliest variables before > + * writing out our changes. > + */ > + if (fraglist_changed) { > + struct packet *current = remaining_packets; > + > + update.earliest = now.t; > + while (current) { > + odph_ipv4hdr_t *h; > + uint16_t part_oct; > + uint16_t whole_oct; > + struct flts update_earliest; > + > + update_earliest.t = update.earliest; > + h = odp_packet_data(current->handle); > + part_oct = ipv4hdr_payload_len_oct(*h); > + whole_oct = ipv4hdr_reass_payload_len_oct(*h); > + update.part_len = min(IP_OCTET_MAX, > + update.part_len + part_oct); > + update.whole_len = min(update.whole_len, whole_oct); > + update.earliest = earliest(update_earliest, > + current->arrival, now).t; > + frags_head = current; > + current = prev_packet(*current); > + } > + frags = update; > + goto redo; > + } > + > + frags = update; > + frags_head = frags.tail; > + while (prev_packet(*frags_head)) > + frags_head = prev_packet(*frags_head); > + goto redo; > +} > + > +/** > + * Add a single fragment to a shared fraglist > + * > + * @param fl Pointer to the shared fraglist to add "frag" to > + * @param frag Pointer to the fragment to add to "fl" > + * @param frag_payload_len The payload length of "frag" in octets > + * @param frag_reass_payload_len The estimated reassembled payload length of > + * "frag" in octets > + * @param out The queue to which reassembled packets should be written > + * > + * @return The number of packets reassembled and sent to the output > + */ > +static int add_frag_to_fraglist(union fraglist *fl, struct packet *frag, > + uint16_t frag_payload_len, > + uint16_t frag_reass_payload_len, > + odp_queue_t out) > +{ > + union fraglist frags; > + > + frags.tail = frag; > + frags.part_len = frag_payload_len; > + frags.whole_len = frag_reass_payload_len; > + frags.earliest = frag->arrival.t; > + > + return add_fraglist_to_fraglist(fl, frags, frags.tail, frag->arrival, > + out, 0); > +} > + > +/** > + * Remove the stale flows from a shared fraglist this thread has exclusive > + * access over > + * > + * @param fl Pointer to the shared fraglist to clean stale flows from > + * @param oldfl The value of the fraglist before we took exclusive > access > + * @param time_now A timestamp indication of the time "now" > + * @param out The queue to which reassembled packets should be > written > + * @param force Whether all flows in the fraglist should be > considered stale > + */ > +static void remove_stale_flows(union fraglist *fl, union fraglist oldfl, > + struct flts timestamp_now, odp_queue_t out, > + odp_bool_t force) > +{ > + union fraglist newfl = oldfl; > + struct packet *current; > + struct packet *current_tail; > + struct packet *remaining_frags_head; > + struct flts flow_earliest; > + > + /* > + * Sort the fraglist so we can step through its fragments flow-by-flow > + */ > + sort_fraglist(&newfl, timestamp_now); > + > + /* Remove stale flows from the fraglist */ > + current = newfl.tail; > + current_tail = newfl.tail; > + remaining_frags_head = NULL; > + flow_earliest = current->arrival; > + newfl.earliest = timestamp_now.t; > + while (current) { > + struct packet *prev = prev_packet(*current); > + > + /* > + * If this is the first fragment in a chain, we can make the > + * decision on whether this flow should be kept or discarded > + */ > + if (prev == NULL || !equal_flow(current, prev)) { > + struct flts elapsed; > + uint64_t elapsed_ns; > + > + elapsed.t = timestamp_now.t - flow_earliest.t; > + elapsed_ns = (elapsed.t * TS_RES_NS); > + if ((elapsed_ns >= FLOW_TIMEOUT_NS && > + elapsed.t + TS_NOW_TOLERANCE >= > + TS_NOW_TOLERANCE) || force) { > + struct packet *to_free = current_tail; > + > + while (to_free != prev) { > + struct packet *next; > + > + next = prev_packet(*to_free); > + odp_packet_free(to_free->handle); > + to_free = next; > + } > + > + if (remaining_frags_head) > + set_prev_packet(remaining_frags_head, > + prev); > + else > + newfl.tail = prev; > + } else { > + odph_ipv4hdr_t *h; > + uint16_t part_oct; > + uint16_t whole_oct; > + struct flts newfl_earliest; > + > + newfl_earliest.t = newfl.earliest; > + remaining_frags_head = current; > + h = odp_packet_data(current->handle); > + part_oct = ipv4hdr_payload_len_oct(*h); > + whole_oct = ipv4hdr_reass_payload_len_oct(*h); > + newfl.part_len = min(IP_OCTET_MAX, > + newfl.part_len + part_oct); > + newfl.whole_len = min(newfl.whole_len, > + whole_oct); > + newfl.earliest = earliest(newfl_earliest, > + flow_earliest, > + timestamp_now).t; > + } > + > + current_tail = prev; > + flow_earliest.t = EARLIEST_MAX; > + } else { > + flow_earliest = earliest(flow_earliest, > + current->arrival, > + timestamp_now); > + } > + > + current = prev; > + } > + > + /* > + * If there are any remaining fragments, write them back into the > + * fraglist > + */ > + if (remaining_frags_head) > + add_fraglist_to_fraglist(fl, newfl, remaining_frags_head, > + timestamp_now, out, 0); > +} > + > +/** > + * Clean up any stale flows within a fraglist > + * > + * @param fl Pointer to the shared fraglist to clean stale flows from > + * @param out The queue to which reassembled packets should be > written > + * @param force Whether all flows in the fraglist should be > considered stale > + */ > +static void garbage_collect_fraglist(union fraglist *fl, odp_queue_t out, > + odp_bool_t force) > +{ > + uint64_t time_now; > + struct flts timestamp_now; > + struct flts elapsed; > + uint64_t elapsed_ns; > + union fraglist oldfl; > + odp_bool_t success = 1; > + > + do { > + time_now = odp_time_to_ns(odp_time_global()); > + timestamp_now.t = time_now / TS_RES_NS; > + __atomic_load(&fl->half[0], &oldfl.half[0], __ATOMIC_RELAXED); > + __atomic_load(&fl->half[1], &oldfl.half[1], __ATOMIC_RELAXED); > + elapsed.t = timestamp_now.t - oldfl.earliest; > + > + if (oldfl.tail == NULL || > + elapsed.t + TS_NOW_TOLERANCE < TS_NOW_TOLERANCE) > + return; > + > + elapsed_ns = (elapsed.t * TS_RES_NS); > + assert(force || elapsed_ns <= 86400000000000); > + if (elapsed_ns >= FLOW_TIMEOUT_NS || force) { > + union fraglist nullfl; > + > + init_fraglist(&nullfl); > + success = atomic_strong_cas_dblptr(&fl->raw, &oldfl.raw, > + nullfl.raw, > + __ATOMIC_ACQUIRE, > + __ATOMIC_RELAXED); > + > + if (success) > + remove_stale_flows(fl, oldfl, timestamp_now, > + out, force); > + } > + } while (!success); > +} > + > +int reassemble_ipv4_packets(union fraglist *fraglists, int num_fraglists, > + struct packet *fragments, int num_fragments, > + odp_queue_t out) > +{ > + int i; > + int packets_reassembled = 0; > + > + for (i = 0; i < num_fragments; ++i) { > + struct packet frag; > + odph_ipv4hdr_t *hdr; > + uint16_t frag_payload_len; > + uint16_t frag_reass_payload_len; > + uint32_t key; > + union fraglist *fl; > + int status; > + > + frag = fragments[i]; > + hdr = odp_packet_data(frag.handle); > + frag_payload_len = ipv4hdr_payload_len_oct(*hdr); > + frag_reass_payload_len = ipv4hdr_reass_payload_len_oct(*hdr); > + > + /* > + * Find the appropriate hash map bucket for fragments in this > + * flow. In the case of collisions, fragments for multiple flows > + * are simply stored in the same list. > + */ > + key = hash(hdr); > + fl = &fraglists[key % num_fraglists]; > + > + status = add_frag_to_fraglist(fl, &fragments[i], > + frag_payload_len, > + frag_reass_payload_len, out); > + if (status < 0) { > + fprintf(stderr, > + "ERROR: failed to add fragment to fraglist\n"); > + return -1; > + } > + packets_reassembled += status; > + } > + > + return packets_reassembled; > +} > + > +void garbage_collect_fraglists(union fraglist *fraglists, int num_fraglists, > + odp_queue_t out, odp_bool_t destroy_all) > +{ > + int i; > + > + for (i = 0; i < num_fraglists; ++i) > + garbage_collect_fraglist(&fraglists[i], out, destroy_all); > +} > diff --git a/example/ipfragreass/odp_ipfragreass_reassemble.h > b/example/ipfragreass/odp_ipfragreass_reassemble.h > new file mode 100644 > index 0000000..ffc4bfb > --- /dev/null > +++ b/example/ipfragreass/odp_ipfragreass_reassemble.h > @@ -0,0 +1,211 @@ > +/* Copyright (c) 2017, Linaro Limited > + * All rights reserved. > + * > + * SPDX-License-Identifier: BSD-3-Clause > + */ > + > +#ifndef ODP_FRAGREASS_PP_REASSEMBLE_H_ > +#define ODP_FRAGREASS_PP_REASSEMBLE_H_ > + > +#include <odp/helper/ip.h> > + > +#include "odp_ipfragreass_ip.h" > +#include "odp_ipfragreass_helpers.h" > + > +ODP_STATIC_ASSERT((__SIZEOF_POINTER__ == 4 || __SIZEOF_POINTER__ == 8), > + "ODPREASS_PTR__SIZE_ERROR"); > + > +/** > + * The time in nanoseconds after reception of the earliest fragment that a > + * flow of traffic is considered to be stale > + */ > +#define FLOW_TIMEOUT_NS 15000000000ULL > + > +/** Convert nanoseconds into a unit for packet.arrival */ > +#if __SIZEOF_POINTER__ == 4 > +#define TS_RES_NS ((uint64_t)5000000000) /**< ns -> 5s */ > +#elif __SIZEOF_POINTER__ == 8 > +#define TS_RES_NS ((uint64_t)1000000) /**< ns -> 1ms */ > +#endif > + > +/** > + * The maximum value of the packet.arrival field. > + */ > +#if __SIZEOF_POINTER__ == 4 > +#define EARLIEST_MAX 15 > +#elif __SIZEOF_POINTER__ == 8 > +#define EARLIEST_MAX UINT32_MAX > +#endif > + > +/** > + * The time in packet.arrival ticks that indications of the time "now" are > + * permitted to be off by. > + */ > +#if __SIZEOF_POINTER__ == 4 > +#define TS_NOW_TOLERANCE 1 > +#elif __SIZEOF_POINTER__ == 8 > +#define TS_NOW_TOLERANCE 5000 > +#endif > + > +/** > + * The timestamp format used for fragments. Sadly, this has to be a structure > + * as we may need a bit field. > + */ > +struct flts { > +#if __SIZEOF_POINTER__ == 4 > + uint8_t t:4; > +#elif __SIZEOF_POINTER__ == 8 > + uint32_t t; > +#endif > +}; > + > +/** > + * Metadata for reassembly, to be stored alongside each fragment > + */ > +struct packet { > + odp_packet_t handle; /**< The ODP packet handle for this fragment */ > + struct packet *prev; /**< Pointer to the fragment "before" this one */ > + struct flts arrival; /**< Arrival timestamp for this fragment */ > +}; > + > +/** > + * A list of IP fragments associated with one or more traffic flows, along > with > + * some related data > + * > + * This is used as an atomically-updated hash map bucket in reassembly, and > is > + * assumed to be packed with no padding. > + */ > +union fraglist { > + struct { > + /** > + * The timestamp of the earliest arriving fragment in this > + * fraglist > + */ > +#if __SIZEOF_POINTER__ == 4 > + uint32_t earliest:4; > +#elif __SIZEOF_POINTER__ == 8 > + uint32_t earliest; > +#endif > + > + /** > + * The sum of the payloads of the fragments in this list > + * > + * That is, the size of reassembling all the fragments in this > + * list into one big packet (minus the header). > + */ > + uint32_t part_len:14; > + > + /** > + * The smallest reassembled payload length upper bound from > + * all fragments in this list > + * > + * This is the threshold over which, given the right > + * circumstances, "part_len" might indicate that we are able > + * to reassemble a packet. > + */ > + uint32_t whole_len:14; > + > + /** > + * The tail of a "reverse" linked list of fragments > + * > + * Each fragment element has a "prev" pointer to the element > + * before it in the list. When used in a multi-threaded > + * environment, new elements should be inserted atomically by > + * modifying this tail pointer. > + */ > + struct packet *tail; > + }; > + > +#if __SIZEOF_POINTER__ == 4 > + struct { > + uint32_t half[2]; > + }; > + uint64_t raw; > +#elif __SIZEOF_POINTER__ == 8 > + struct { > + uint64_t half[2]; > + }; > + __int128 raw; > +#endif > +}; > + > +/** > + * Initialise a fraglist structure > + * > + * @param fl The fraglist to initialise > + */ > +static inline void init_fraglist(union fraglist *fl) > +{ > + fl->earliest = EARLIEST_MAX; > + fl->part_len = 0; > + fl->whole_len = IP_OCTET_MAX; > + fl->tail = NULL; > +} > + > +/** > + * Get the packet "before" a packet in a linked list > + * > + * @param packet The packet from which the previous should be located > + * > + * @return A pointer to the packet before the input packet (can be NULL) > + */ > +static inline struct packet *prev_packet(struct packet packet) > +{ > + return packet.prev; > +} > + > +/** > + * Get the address of the pointer to the packet "before" a packet in a linked > + * list > + * > + * @param packet The packet for which the previous pointer should be located > + * > + * @return A pointer to the "prev" packet pointer of the input packet > + */ > +static inline struct packet **prev_packet_ptr(struct packet *packet) > +{ > + return &packet->prev; > +} > + > +/** > + * Set the packet "before" a packet in a linked list > + * > + * @param packet The packet to set the previous packet from > + * @param prev The packet to set as being before "packet" > + */ > +static inline void set_prev_packet(struct packet *packet, struct packet > *prev) > +{ > + packet->prev = prev; > +} > + > +/** > + * Attempt packet reassembly with the aid of a number of new fragments > + * > + * Add "num_fragments" fragments to a fraglist hash map (with "num_fraglist" > + * entries), attempting reassembly and writing any successfully reassembled > + * packets out to the "out" queue. > + * > + * @param fraglists The hash map structure to add the fragments to > + * @param num_fraglists The number of entries in the hash map > + * @param fragments Pointer to the fragments to add > + * @param num_fragments The number of fragments to add > + * @param out The queue to which reassembled packets should > be written > + * > + * @return The number of packets successfully reassembled and written to > "out" > + */ > +int reassemble_ipv4_packets(union fraglist *fraglists, int num_fraglists, > + struct packet *fragments, int num_fragments, > + odp_queue_t out); > + > +/** > + * Clean up any stale flows within a fraglist hash map > + * > + * @param fraglists The hash map structure to clean flows from > + * @param num_fraglists The number of entries in the hash map > + * @param out The queue to which reassembled packets should > be written > + * @param destroy_all Whether all encountered flows should be cleaned > up > + */ > +void garbage_collect_fraglists(union fraglist *fraglists, int num_fraglists, > + odp_queue_t out, odp_bool_t destroy_all); > + > +#endif > diff --git a/example/m4/configure.m4 b/example/m4/configure.m4 > index 620db04..18218d0 100644 > --- a/example/m4/configure.m4 > +++ b/example/m4/configure.m4 > @@ -14,6 +14,7 @@ AC_CONFIG_FILES([example/classifier/Makefile > example/generator/Makefile > example/hello/Makefile > example/ipsec/Makefile > + example/ipfragreass/Makefile > example/l2fwd_simple/Makefile > example/l3fwd/Makefile > example/packet/Makefile >