From patchwork Fri Jun 6 14:41:36 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Robbie King X-Patchwork-Id: 31491 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-ie0-f200.google.com (mail-ie0-f200.google.com [209.85.223.200]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id DB8C7202DA for ; Fri, 6 Jun 2014 14:42:01 +0000 (UTC) Received: by mail-ie0-f200.google.com with SMTP id to1sf5417457ieb.3 for ; Fri, 06 Jun 2014 07:42:01 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:delivered-to:from:to:date:message-id:subject :precedence:list-id:list-unsubscribe:list-archive:list-post :list-help:list-subscribe:mime-version:errors-to:sender :x-original-sender:x-original-authentication-results:mailing-list :content-type:content-transfer-encoding; bh=LDlCZE6j+Y4jMQX+TMJkAblGr7op4oBVACo4+N8S5Mc=; b=lgH8VEYyb+u+6ADkvH05GE1vspbY8e8VETZNi81teKftB1z0vMkfgr1inBuML5CzTB d1jX+BNedUN9D7woFF1J2W6Us9L4D3vMFXnNhp+7oBaMR9n8twtJfZCEE71OeeK07LK5 +L0lYiMwdyDElsA2Zx5sk9D/GHbnO2Dvb+aLTUvP/evHJzatXCT0cttyeta6cpqsOyGE xPTlbTwq9vHoKAxJ6swRhwSHGcC0SrXyY/OCSiTNmbqB/YHdzZvyxfhazxOoqzL8EDig w6rGm4OFWyi3Zm8RCS5nNvC6U6nmPfG2dRbijgCFwytNDmXgHI6loWKEPSigchvPQ0rB ZbGg== X-Gm-Message-State: ALoCoQm0VciXHRw1AbVm5K8DqQqa2f2lT5K3f1RkfPn+h5JpfD8MBZBE2DlJ3ASNLZp0m6s6k771 X-Received: by 10.50.79.129 with SMTP id j1mr2600036igx.7.1402065721336; Fri, 06 Jun 2014 07:42:01 -0700 (PDT) X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.100.179 with SMTP id s48ls681316qge.17.gmail; Fri, 06 Jun 2014 07:42:01 -0700 (PDT) X-Received: by 10.220.18.134 with SMTP id w6mr5825922vca.5.1402065721094; Fri, 06 Jun 2014 07:42:01 -0700 (PDT) Received: from mail-vc0-x22b.google.com (mail-vc0-x22b.google.com [2607:f8b0:400c:c03::22b]) by mx.google.com with ESMTPS id o5si5224083vcj.31.2014.06.06.07.42.01 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 06 Jun 2014 07:42:01 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 2607:f8b0:400c:c03::22b as permitted sender) client-ip=2607:f8b0:400c:c03::22b; Received: by mail-vc0-f171.google.com with SMTP id id10so1386187vcb.2 for ; Fri, 06 Jun 2014 07:42:01 -0700 (PDT) X-Received: by 10.52.52.168 with SMTP id u8mr1910190vdo.25.1402065720891; Fri, 06 Jun 2014 07:42:00 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.221.54.6 with SMTP id vs6csp107425vcb; Fri, 6 Jun 2014 07:41:59 -0700 (PDT) X-Received: by 10.221.27.8 with SMTP id ro8mr5967602vcb.30.1402065719619; Fri, 06 Jun 2014 07:41:59 -0700 (PDT) Received: from ip-10-141-164-156.ec2.internal (lists.linaro.org. [54.225.227.206]) by mx.google.com with ESMTPS id i5si5222241vcp.33.2014.06.06.07.41.58 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Fri, 06 Jun 2014 07:41:59 -0700 (PDT) Received-SPF: none (google.com: lng-odp-bounces@lists.linaro.org does not designate permitted sender hosts) client-ip=54.225.227.206; Received: from localhost ([127.0.0.1] helo=ip-10-141-164-156.ec2.internal) by ip-10-141-164-156.ec2.internal with esmtp (Exim 4.76) (envelope-from ) id 1WsvJo-0005zW-Gp; Fri, 06 Jun 2014 14:40:32 +0000 Received: from rcdn-iport-8.cisco.com ([173.37.86.79]) by ip-10-141-164-156.ec2.internal with esmtp (Exim 4.76) (envelope-from ) id 1WsvJb-0005zR-49 for lng-odp@lists.linaro.org; Fri, 06 Jun 2014 14:40:19 +0000 X-IronPort-AV: E=Sophos;i="4.98,989,1392163200"; d="scan'208";a="331132997" Received: from alln-core-2.cisco.com ([173.36.13.135]) by rcdn-iport-8.cisco.com with ESMTP; 06 Jun 2014 14:41:39 +0000 Received: from cpp-rtpbld-55.cisco.com (cpp-rtpbld-55.cisco.com [172.18.5.199]) by alln-core-2.cisco.com (8.14.5/8.14.5) with ESMTP id s56EfcBS023699 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Fri, 6 Jun 2014 14:41:39 GMT Received: from cpp-rtpbld-55.cisco.com (localhost.localdomain [127.0.0.1]) by cpp-rtpbld-55.cisco.com (8.13.8/8.13.8) with ESMTP id s56Efc9g019409; Fri, 6 Jun 2014 10:41:38 -0400 Received: (from robking@localhost) by cpp-rtpbld-55.cisco.com (8.13.8/8.13.8/Submit) id s56Efcnq019408; Fri, 6 Jun 2014 10:41:38 -0400 From: Robbie King To: lng-odp@lists.linaro.org Date: Fri, 6 Jun 2014 10:41:36 -0400 Message-Id: <1402065696-19351-1-git-send-email-robking@cisco.com> X-Mailer: git-send-email 1.9.2 X-Topics: patch Subject: [lng-odp] [PATCHv2] IPsec example application X-BeenThere: lng-odp@lists.linaro.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: , List-Help: , List-Subscribe: , MIME-Version: 1.0 Errors-To: lng-odp-bounces@lists.linaro.org Sender: lng-odp-bounces@lists.linaro.org X-Original-Sender: robking@cisco.com X-Original-Authentication-Results: mx.google.com; spf=pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 2607:f8b0:400c:c03::22b as permitted sender) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org; dkim=fail header.i=@cisco.com Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 Last update before sprint. I have added the standalone support for packet generation and verification, fixed several issues found along the way. Please do not consider this patch for GIT, just want anyone working on the sprint for next week to have the standalone support. Have added several scripts for running standalone tests. Signed-off-by: Robbie King --- include/helper/odp_ipsec.h | 73 + test/ipsec/Makefile | 44 + test/ipsec/README | 164 +++ test/ipsec/odp_example_ipsec.c | 3120 ++++++++++++++++++++++++++++++++++++++++ test/ipsec/run_ah_in.sh | 13 + test/ipsec/run_ah_out.sh | 12 + test/ipsec/run_both_in.sh | 14 + test/ipsec/run_both_out.sh | 14 + test/ipsec/run_esp_in.sh | 13 + test/ipsec/run_esp_out.sh | 13 + test/ipsec/run_simple.sh | 10 + 11 files changed, 3490 insertions(+) create mode 100644 include/helper/odp_ipsec.h create mode 100644 test/ipsec/Makefile create mode 100644 test/ipsec/README create mode 100644 test/ipsec/odp_example_ipsec.c create mode 100755 test/ipsec/run_ah_in.sh create mode 100755 test/ipsec/run_ah_out.sh create mode 100755 test/ipsec/run_both_in.sh create mode 100755 test/ipsec/run_both_out.sh create mode 100755 test/ipsec/run_esp_in.sh create mode 100755 test/ipsec/run_esp_out.sh create mode 100755 test/ipsec/run_simple.sh diff --git a/include/helper/odp_ipsec.h b/include/helper/odp_ipsec.h new file mode 100644 index 0000000..9d29651 --- /dev/null +++ b/include/helper/odp_ipsec.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2014, Linaro Limited + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +/** + * @file + * + * ODP IPSec headers + */ + +#ifndef ODP_IPSEC_H_ +#define ODP_IPSEC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#define ODP_ESPHDR_LEN 8 /**< IPSec ESP header length */ +#define ODP_ESPTRL_LEN 2 /**< IPSec ESP trailer length */ +#define ODP_AHHDR_LEN 12 /**< IPSec AH header length */ + +/** + * IPSec ESP header + */ +typedef struct ODP_PACKED { + uint32be_t spi; /**< Security Parameter Index */ + uint32be_t seq_no; /**< Sequence Number */ + uint8_t iv[0]; /**< Initialization vector */ +} odp_esphdr_t; + +/** @internal Compile time assert */ +ODP_ASSERT(sizeof(odp_esphdr_t) == ODP_ESPHDR_LEN, ODP_ESPHDR_T__SIZE_ERROR); + +/** + * IPSec ESP trailer + */ +typedef struct ODP_PACKED { + uint8_t pad_len; /**< Padding length (0-255) */ + uint8_t next_header; /**< Next header protocol */ + uint8_t icv[0]; /**< Integrity Check Value (optional) */ +} odp_esptrl_t; + +/** @internal Compile time assert */ +ODP_ASSERT(sizeof(odp_esptrl_t) == ODP_ESPTRL_LEN, ODP_ESPTRL_T__SIZE_ERROR); + +/** + * IPSec AH header + */ +typedef struct ODP_PACKED { + uint8_t next_header; /**< Next header protocol */ + uint8_t ah_len; /**< AH header length */ + uint16be_t pad; /**< Padding (must be 0) */ + uint32be_t spi; /**< Security Parameter Index */ + uint32be_t seq_no; /**< Sequence Number */ + uint8_t icv[0]; /**< Integrity Check Value */ +} odp_ahhdr_t; + +/** @internal Compile time assert */ +ODP_ASSERT(sizeof(odp_ahhdr_t) == ODP_AHHDR_LEN, ODP_AHHDR_T__SIZE_ERROR); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/ipsec/Makefile b/test/ipsec/Makefile new file mode 100644 index 0000000..be822b5 --- /dev/null +++ b/test/ipsec/Makefile @@ -0,0 +1,44 @@ +# Copyright (c) 2013, Linaro Limited +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +ODP_ROOT = ../.. +ODP_APP = odp_ipsec + +include $(ODP_ROOT)/Makefile.inc +include ../Makefile.inc + +OBJS = +OBJS += $(OBJ_DIR)/odp_example_ipsec.o + +DEPS = $(OBJS:.o=.d) + +.PHONY: default +default: $(OBJ_DIR) $(ODP_APP) + +-include $(DEPS) + +# +# Compile rules +# +$(OBJ_DIR)/%.o: %.c + $(ECHO) Compiling $< + $(CC) -c -MD $(EXTRA_CFLAGS) $(CFLAGS) -o $@ $< + +# +# Link rule +# +$(ODP_APP): $(ODP_LIB) $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) $(ODP_LIB) $(STD_LIBS) -lcrypto -o $@ + +.PHONY: clean +clean: + $(RMDIR) $(OBJ_DIR) + $(RM) $(ODP_APP) + $(MAKE) -C $(ODP_DIR) clean + +.PHONY: install +install: + install -d $(DESTDIR)/share/odp + install -m 0755 $(ODP_APP) $(DESTDIR)/share/odp/ diff --git a/test/ipsec/README b/test/ipsec/README new file mode 100644 index 0000000..7b9dcc3 --- /dev/null +++ b/test/ipsec/README @@ -0,0 +1,164 @@ +1. Intro + +The IPsec example application "odp_ipsec" functions as a simple L3 IPv4 router +with support IPsec 3DES cipher and HMAC-MD5 authentication in both the transmit +and receive directions. Note that only IPsec "transport" mode is supported. + +2. Prerequisites + + 2.1 SSL development libraries + +Development has been done to this point with the openssl-devel libraries, +the makefile specifically links with "-lcrypto". + +3. Topology + +The following test topology was used for development. Each of the VMs +is running Fedora16. Sanity testing consists of pinging VM2 from VM0 +such that the packets traverse VM1. Packets between VM1 and VM2 are +IPsec AH and ESP encapsulated. + + VM0 VM1 (UUT) VM2 ++------------+ +--------------+ +------------+ +| | (clear) | | (crypto) | | +| | subnet | | subnet | | +| p7p1 |<---------------->| p7p1 p8p1 |<---------------->| p7p1 | +| .2 | 192.168.111.0 | .1 .1 | 192.168.222.0 | .2 | +| | | | | | ++------------+ +--------------+ +------------+ + +4. VM configurations + + 4.1 VM0 configuration + +VM0 has the follwing interface configuration: + + cat /etc/sysconfig/network-scripts/ifcfg-p7p1 + DEVICE=p7p1 + HWADDR=08:00:27:76:B5:E0 + BOOTPROTO=static + IPADDR=192.168.111.2 + NETMASK=255.255.255.0 + ONBOOT=yes + +In addition, static ARP and IPv4 routes must be added on VM0: + + sudo ip route add 192.168.222.0/24 via 192.168.111.1 + sudo ip route add 192.168.222.0/24 via 192.168.111.1 + + 4.2 VM1 configuration + +For the unit under test, IP forwarding and IP tables were disabled. + +VM1 has the follwing interface configurations: + + cat /etc/sysconfig/network-scripts/ifcfg-p7p1 + DEVICE=p7p1 + HWADDR=08:00:27:04:BF:8C + BOOTPROTO=static + IPADDR=192.168.111.1 + NETMASK=255.255.255.0 + ONBOOT=yes + + cat /etc/sysconfig/network-scripts/ifcfg-p8p1 + DEVICE=p8p1 + HWADDR=08:00:27:4C:55:CC + BOOTPROTO=static + IPADDR=192.168.222.1 + NETMASK=255.255.255.0 + ONBOOT=yes + +The application is launched on VM1 with the following command line +using a bash script: + + cat test/ipsec/run_test.sh + #!/bin/bash + sudo ./odp_ipsec -i p7p1,p8p1 \ + -r 192.168.111.2/32:p7p1:08.00.27.76.B5.E0 \ + -r 192.168.222.2/32:p8p1:08.00.27.F5.8B.DB \ + -p 192.168.111.0/24:192.168.222.0/24:out:both \ + -e 192.168.111.2:192.168.222.2:3des:201:656c8523255ccc23a66c1917aa0cf30991fce83532a4b224 \ + -a 192.168.111.2:192.168.222.2:md5:200:a731649644c5dee92cbd9c2e7e188ee6 \ + -p 192.168.222.0/24:192.168.111.0/24:in:both \ + -e 192.168.222.2:192.168.111.2:3des:301:c966199f24d095f3990a320d749056401e82b26570320292 \ + -a 192.168.222.2:192.168.111.2:md5:300:27f6d123d7077b361662fc6e451f65d8 \ + -c 2 -f 0 -m 0 + + 4.3 VM2 configuration + +VM2 must be setup with an IPsec configuration complementing +the configuration used by the "odp_ipsec" application running +on VM1. The configuration is applied using "setkey" + +VM2 has the following setkey configuration file applied: + + cat /media/sf_SharedVM2/setkey_vm2.txt + #!/sbin/setkey -f + + # Flush the SAD and SPD + flush; + spdflush; + + add 192.168.111.2 192.168.222.2 ah 0x200 -A hmac-md5 + 0xa731649644c5dee92cbd9c2e7e188ee6; + add 192.168.222.2 192.168.111.2 ah 0x300 -A hmac-md5 + 0x27f6d123d7077b361662fc6e451f65d8; + + add 192.168.111.2 192.168.222.2 esp 0x201 -E 3des-cbc + 0x656c8523255ccc23a66c1917aa0cf30991fce83532a4b224; + add 192.168.222.2 192.168.111.2 esp 0x301 -E 3des-cbc + 0xc966199f24d095f3990a320d749056401e82b26570320292; + + spdadd 192.168.111.2 192.168.222.2 any -P in ipsec + esp/transport//require + ah/transport//require; + + spdadd 192.168.222.2 192.168.111.2 any -P out ipsec + esp/transport//require + ah/transport//require; + +VM2 has the follwing interface configuration: + + cat /etc/sysconfig/network-scripts/ifcfg-p7p1 + DEVICE=p7p1 + HWADDR=08:00:27:F5:8B:DB + BOOTPROTO=static + IPADDR=192.168.222.2 + NETMASK=255.255.255.0 + ONBOOT=yes + +In addition, static ARP and IPv4 routes must be added on VM2: + + sudo ip route add 192.168.111.0/24 via 192.168.222.1 + sudo arp -s 192.168.222.1 08:00:27:4c:55:cc + +5. Sanity Test with Real Traffic + +Once all three VMs have been configured and static ARP and route +entries added, VM0 should be able to ping VM2 at the 192.168.222.2 +address. + +At VM0 console issue the ping to VM2's address: + + sudo ping -c 2 -i 0.1 192.168.222.2 + PING 192.168.222.2 (192.168.222.2) 56(84) bytes of data. + 64 bytes from 192.168.222.2: icmp_req=1 ttl=64 time=33.9 ms + 64 bytes from 192.168.222.2: icmp_req=2 ttl=64 time=23.3 ms + +At VM2 console use tcpdump to observe IPsec packets : + + sudo tcpdump -nt -i p7p1 + tcpdump: verbose output suppressed, use -v or -vv for full protocol decode + listening on p7p1, link-type EN10MB (Ethernet), capture size 65535 bytes + + IP 192.168.111.2 > 192.168.222.2: AH(spi=0x00000200,seq=0x6): ESP(spi=0x00000201,seq=0x6), length 88 + IP 192.168.222.2 > 192.168.111.2: AH(spi=0x00000300,seq=0x7a): ESP(spi=0x00000301,seq=0x7a), length 88 + IP 192.168.111.2 > 192.168.222.2: AH(spi=0x00000200,seq=0x7): ESP(spi=0x00000201,seq=0x7), length 88 + IP 192.168.222.2 > 192.168.111.2: AH(spi=0x00000300,seq=0x7b): ESP(spi=0x00000301,seq=0x7b), length 88 + +6. Standalone Loopback Tests + +BASH batch files are now included to run several simple loopback tests that +do not require any packet IO. The scripts create internal "loopback" (not +real Linux loopback interfaces but simply ODP queues) as opposed to packet +interfaces. diff --git a/test/ipsec/odp_example_ipsec.c b/test/ipsec/odp_example_ipsec.c new file mode 100644 index 0000000..16fd14e --- /dev/null +++ b/test/ipsec/odp_example_ipsec.c @@ -0,0 +1,3120 @@ +/* Copyright (c) 2013, Linaro Limited + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * @file + * + * @example odp_example_ipsec.c ODP basic packet IO cross connect with IPsec test application + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define boolean bool +#define TRUE 1 +#define FALSE 0 + +#define MAX_WORKERS 32 +#define SHM_PKT_POOL_SIZE (1 * 1024 * 4096) +#define SHM_PKT_POOL_BUF_SIZE 4096 +#define MAX_PKT_BURST 16 + +#define MAX_DB 32 + + +#define LOOP_DEQ_MULTIPLE 0 /**< enable multi packet dequeue */ +#define MAX_LOOPBACK 10 + +#define MSG_POOL_SIZE (1024 * 1024) /**< Message pool size */ +#define MSG_POOL_BUF_SIZE (1024) + +#define STREAM_MAGIC 0xBABE01234567CAFE + +/** + * Parsed command line application arguments + */ +typedef struct { + int core_count; + int if_count; /**< Number of interfaces to be used */ + char **if_names; /**< Array of pointers to interface names */ + int type; /**< Packet IO type */ + int fanout; /**< Packet IO fanout */ + int async; /**< Crypto API preferred mode (1 = async) */ + odp_buffer_pool_t pool; /**< Buffer pool for packet IO */ +} appl_args_t; + +/** + * Thread specific arguments (currently none, leave as placeholder) + */ +typedef struct { +} thread_args_t; + +/** + * Grouping of both parsed CL args and thread specific args - alloc together + */ +typedef struct { + /** Application (parsed) arguments */ + appl_args_t appl; + /** Thread specific arguments */ + thread_args_t thread[MAX_WORKERS]; +} args_t; + +/** + * Stream packet header + */ +typedef struct ODP_PACKED stream_pkt_hdr_s { + uint64be_t magic; /**< Stream magic value for verification */ + uint8_t data[0]; /**< Incrementing data stream */ +} stream_pkt_hdr_t; + +/* helper funcs */ +static void parse_args(int argc, char *argv[], appl_args_t *appl_args); +static void print_info(char *progname, appl_args_t *appl_args); +static void usage(char *progname); + +/** Global pointer to args */ +static args_t *args; + +/** Buffer pool for crypto session creation completion events */ +static odp_buffer_pool_t msg_pool = ODP_BUFFER_POOL_INVALID; + +/** ATOMIC queue for IPsec sequence number assignment */ +static odp_queue_t seqnumq; + +/** ORDERED queue (eventually) for per packet crypto API completion events */ +static odp_queue_t completionq; + +/** IPv4 helpers for data length and uint8t pointer */ +#define ipv4_data_len(ip) (odp_be_to_cpu_16(ip->tot_len) - sizeof(odp_ipv4hdr_t)) +#define ipv4_data_p(ip) ((uint8_t *)((odp_ipv4hdr_t *)ip + 1)) + +/** Helper for calculating encode length using data length and block size */ +#define ESP_ENCODE_LEN(x, b) ((((x) + (b - 1)) / b) * b) + +/** Get rid of path in filename - only for unix-type paths using '/' */ +#define NO_PATH(file_name) (strrchr((file_name), '/') ? \ + strrchr((file_name), '/') + 1 : (file_name)) + +/** Synchronize threads before packet processing begins */ +static odp_barrier_t sync_barrier; + +/** + * IP address range (subnet) + */ +typedef struct ip_addr_range_s { + uint32_t addr; /**< IP address */ + uint32_t mask; /**< mask, 1 indicates bits are valid */ +} ip_addr_range_t; + +/** + * Packet processing states/steps + */ +typedef enum { + PKT_STATE_INPUT_VERIFY, /**< Verify IPv4 and ETH */ + PKT_STATE_IPSEC_IN_CLASSIFY, /**< Initiate input IPsec */ + PKT_STATE_IPSEC_IN_FINISH, /**< Finish input IPsec */ + PKT_STATE_ROUTE_LOOKUP, /**< Use DST IP to find output IF */ + PKT_STATE_IPSEC_OUT_CLASSIFY, /**< Intiate output IPsec */ + PKT_STATE_IPSEC_OUT_SEQ, /**< Assign IPsec sequence numbers */ + PKT_STATE_IPSEC_OUT_FINISH, /**< Finish output IPsec */ + PKT_STATE_TRANSMIT, /**< Send packet to output IF queue */ +} pkt_state_e; + +/** + * Packet processing result codes + */ +typedef enum { + PKT_CONTINUE, /**< No events posted, keep processing */ + PKT_POSTED, /**< Event posted, stop processing */ + PKT_DROP, /**< Reason to drop detected, stop processing */ + PKT_DONE /**< Finished with packet, stop processing */ +} pkt_disposition_e; + +/** + * Per packet IPsec processing context + */ +typedef struct { + uint8_t ip_tos; /**< Saved IP TOS value */ + uint16_t ip_frag_offset; /**< Saved IP flags value */ + uint8_t ip_ttl; /**< Saved IP TTL value */ + int hdr_len; /**< Length of IPsec headers */ + int trl_len; /**< Length of IPsec trailers */ + uint16_t ah_offset; /**< Offset of AH header from buffer start */ + uint16_t esp_offset; /**< Offset of ESP header from buffer start */ + + /* Output only */ + struct odp_crypto_op_params params; /**< Parameters for crypto call */ + uint32_t *ah_seq; /**< AH sequence number location */ + uint32_t *esp_seq; /**< ESP sequence number location */ +} ipsec_ctx_t; + +/** + * Per packet processing context + */ +typedef union { + struct { + pkt_state_e state; /**< Next processing step */ + ipsec_ctx_t ipsec; /**< IPsec specific context */ + odp_queue_t outq; /**< transmit queue */ + }; + uint8_t pad[256]; /**< Ensure no overlap with crypto */ +} pkt_ctx_t; + +/** + * Adjust IPv4 length + * + * @param ip Pointer to IPv4 header + * @param adj Signed adjustment value + */ +static +void ipv4_adjust_len(odp_ipv4hdr_t *ip, int adj) +{ + ip->tot_len = odp_cpu_to_be_16(odp_be_to_cpu_16(ip->tot_len) + adj); +} + +/** + * Get per packet processing context from packet buffer + * + * This is a hack for the moment to place it at the end of the + * buffer. Note that the crypto library also has a similar hack + * so we space our packet context 256 bytes from the end to prevent + * overlap. + * + * @param buf Buffer associated with packet + * + * @return pointer to context area + */ +static +pkt_ctx_t *get_ctx_from_buf(odp_buffer_t buf) +{ + uint8_t *temp; + pkt_ctx_t *ctx; + + /* + * Setup packet context, + */ + temp = odp_buffer_addr(buf); + temp += odp_buffer_size(buf); + temp -= sizeof(*ctx); + ctx = (pkt_ctx_t *)(void *)temp; + return ctx; +} + +/** + * Check IPv4 address against a range/subnet + * + * @param addr IPv4 address to check + * @param range Pointer to address range to check against + * + * @return 1 if match else 0 + */ +static +int match_ip_range(uint32_t addr, ip_addr_range_t *range) +{ + return (range->addr == (addr & range->mask)); +} + +/** + * Query MAC address associated with an interface + * + * @param intf String name of the interface + * @param src_mac MAC address used by the interface + * + * @return 0 if successful else -1 + */ +static +int query_mac_address(char *intf, uint8_t *src_mac) +{ + int sd; + struct ifreq ifr; + + /* Get a socket descriptor */ + sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sd < 0) { + ODP_ERR("Error: socket() failed for %s\n", intf); + return -1; + } + + /* Use ioctl() to look up interface name and get its MAC address */ + memset(&ifr, 0, sizeof(ifr)); + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", intf); + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) { + ODP_ERR("Error: ioctl() failed for %s\n", intf); + return -1; + } + memcpy(src_mac, ifr.ifr_hwaddr.sa_data, 6); + + /* Fini */ + close(sd); + + return 0; +} + +/** + * Parse text string representing a key into ODP key structure + * + * @param keystring Pointer to key string to convert + * @param key Pointer to ODP key structure to populate + * @param cipher TRUE indicates cipher key else authentication + * @param alg Cipher/authentication algorithm associated with the key + * + * @return 0 if successful else -1 + */ +static +int parse_key_string(char *keystring, + odp_key_t *key, + boolean cipher, + odp_crypto_alg_t alg) +{ + /* Brute force */ + if (cipher && (alg.cipher == ODP_CIPHER_ALG_3DES_CBC)) { + if (48 == strlen(keystring)) { + int idx; + char temp[3]; + + /* K1 */ + for (idx = 0; idx < 8; idx++) { + temp[0] = *keystring++; + temp[1] = *keystring++; + temp[2] = 0; + key->des.k1[idx] = strtol(temp, NULL, 16); + } + + /* K2 */ + for (idx = 0; idx < 8; idx++) { + temp[0] = *keystring++; + temp[1] = *keystring++; + temp[2] = 0; + key->des.k2[idx] = strtol(temp, NULL, 16); + } + + /* K3 */ + for (idx = 0; idx < 8; idx++) { + temp[0] = *keystring++; + temp[1] = *keystring++; + temp[2] = 0; + key->des.k3[idx] = strtol(temp, NULL, 16); + } + return 0; + } + } + if (!cipher && (alg.auth == ODP_AUTH_ALG_MD5_96)) { + if (32 == strlen(keystring)) { + int idx; + char temp[3]; + + for (idx = 0; idx < 16; idx++) { + temp[0] = *keystring++; + temp[1] = *keystring++; + temp[2] = 0; + key->md5.key[idx] = strtol(temp, NULL, 16); + } + return 0; + } + } + return -1; +} + +/** + * Parse text string representing a MAC address into byte araray + * + * String is of the format "XX.XX.XX.XX.XX.XX" where XX is hexadecimal + * + * @param macaddress Pointer to MAC address string to convert + * @param mac Pointer to MAC address byte array to populate + * + * @return 0 if successful else -1 + */ +static +int parse_mac_string(char *macaddress, uint8_t *mac) +{ + int macwords[6]; + int converted; + + converted = sscanf(macaddress, + "%x.%x.%x.%x.%x.%x", + &macwords[0], &macwords[1], &macwords[2], + &macwords[3], &macwords[4], &macwords[5]); + if (6 != converted) + return -1; + + mac[0] = macwords[0]; + mac[1] = macwords[1]; + mac[2] = macwords[2]; + mac[3] = macwords[3]; + mac[4] = macwords[4]; + mac[5] = macwords[5]; + + return 0; +} + +/** + * Parse text string representing an IPv4 address or subnet + * + * String is of the format "XXX.XXX.XXX.XXX(/W)" where + * "XXX" is decimal value and "/W" is optional subnet length + * + * @param ipaddress Pointer to IP address/subnet string to convert + * @param addr Pointer to return IPv4 address + * @param mask Pointer (optional) to return IPv4 mask + * + * @return 0 if successful else -1 + */ +static +int parse_ipv4_string(char *ipaddress, uint32_t *addr, uint32_t *mask) +{ + int b[4]; + int qualifier = 32; + int converted; + + if (strchr(ipaddress, '/')) { + converted = sscanf(ipaddress, "%d.%d.%d.%d/%d", + &b[3], &b[2], &b[1], &b[0], + &qualifier); + if (5 != converted) + return -1; + } else { + converted = sscanf(ipaddress, "%d.%d.%d.%d", + &b[3], &b[2], &b[1], &b[0]); + if (4 != converted) + return -1; + } + + if ((b[0] > 255) || (b[1] > 255) || (b[2] > 255) || (b[3] > 255)) + return -1; + if (!qualifier || (qualifier > 32)) + return -1; + + *addr = b[0] | b[1] << 8 | b[2] << 16 | b[3] << 24; + if (mask) + *mask = ~(0xFFFFFFFF & ((1ULL << (32 - qualifier)) - 1)); + + return 0; +} + +/** + * Generate text string representing IPv4 address + * + * @param b Pointer to buffer to store string + * @param addr IPv4 address + * + * @return Pointer to supplied buffer + */ +static +char *ipv4_addr_str(char *b, uint32_t addr) +{ + sprintf(b, "%03d.%03d.%03d.%03d", + 0xFF & ((addr) >> 24), + 0xFF & ((addr) >> 16), + 0xFF & ((addr) >> 8), + 0xFF & ((addr) >> 0)); + return b; +} + +/** + * Generate text string representing IPv4 range/subnet, output + * in "XXX.XXX.XXX.XXX/W" format + * + * @param b Pointer to buffer to store string + * @param range Pointer to IPv4 address range + * + * @return Pointer to supplied buffer + */ +static +char *ipv4_subnet_str(char *b, ip_addr_range_t *range) +{ + int idx; + int len; + + for (idx = 0; idx < 32; idx++) + if (range->mask & (1 << idx)) + break; + len = 32 - idx; + + sprintf(b, "%03d.%03d.%03d.%03d/%d", + 0xFF & ((range->addr) >> 24), + 0xFF & ((range->addr) >> 16), + 0xFF & ((range->addr) >> 8), + 0xFF & ((range->addr) >> 0), + len); + return b; +} + +/** + * Generate text string representing MAC address + * + * @param b Pointer to buffer to store string + * @param mac Pointer to MAC address + * + * @return Pointer to supplied buffer + */ +static +char *mac_addr_str(char *b, uint8_t *mac) +{ + sprintf(b, "%02X.%02X.%02X.%02X.%02X.%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return b; +} + +/** + * Parse loop interface index + * + * @param b Pointer to buffer to parse + * + * @return interface index (0 to (MAX_LOOPBACK - 1)) else -1 + */ +static +int loop_if_index(char *b) +{ + int ret; + int idx; + + /* Derive loopback interface index */ + ret = sscanf(b, "loop%d", &idx); + if ((1 != ret) || (idx >= MAX_LOOPBACK)) + return -1; + return idx; +} + +/** + * Locate IPsec headers (AH and/or ESP) in packet + * + * @param ip Pointer to packets IPv4 header + * @param ah_p Pointer to location to return AH header pointer + * @param esp_p Pointer to location to return ESP header pointer + * + * @return length of IPsec headers found + */ +static +int locate_ipsec_headers(odp_ipv4hdr_t *ip, + odp_ahhdr_t **ah_p, + odp_esphdr_t **esp_p) +{ + uint8_t *in = ipv4_data_p(ip); + odp_ahhdr_t *ah = NULL; + odp_esphdr_t *esp = NULL; + + if (ODP_IPPROTO_AH == ip->proto) { + ah = (odp_ahhdr_t *)in; + in += ((ah)->ah_len + 2) * 4; + if (ODP_IPPROTO_ESP == ah->next_header) { + esp = (odp_esphdr_t *)in; + in += sizeof(odp_esphdr_t); + } + } else if (ODP_IPPROTO_ESP == ip->proto) { + esp = (odp_esphdr_t *)in; + in += sizeof(odp_esphdr_t); + } + + *ah_p = ah; + *esp_p = esp; + return in - (ipv4_data_p(ip)); +} + +/** + * Loopback database entry structure + */ +typedef struct loopback_db_entry_s { + odp_queue_t inq_def; + odp_queue_t outq_def; + uint8_t mac[6]; +} loopback_db_entry_t; + +typedef struct loopback_db_s { + loopback_db_entry_t intf[MAX_LOOPBACK]; +} loopback_db_t; + +static loopback_db_t *loopback_db; + +/** Initialize loopback database global control structure */ +static +void init_loopback_db(void) +{ + int idx; + + loopback_db = odp_shm_reserve("loopback_db", + sizeof(loopback_db_t), + ODP_CACHE_LINE_SIZE); + if (loopback_db == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(loopback_db, 0, sizeof(*loopback_db)); + + for (idx = 0; idx < MAX_LOOPBACK; idx++) { + loopback_db->intf[idx].inq_def = ODP_QUEUE_INVALID; + loopback_db->intf[idx].outq_def = ODP_QUEUE_INVALID; + } +} +/** + * Security Assocation (SA) data base entry + */ +typedef struct sa_db_entry_s { + struct sa_db_entry_s *next; /**< Next entry on list */ + boolean cipher; /**< TRUE if cipher else auth */ + uint32_t src_ip; /**< Source IPv4 address */ + uint32_t dst_ip; /**< Desitnation IPv4 address */ + uint32_t spi; /**< Security Parameter Index */ + odp_crypto_alg_t alg; /**< Cipher/auth algorithm */ + odp_key_t key; /**< Cipher/auth key */ + uint32_t block_len; /**< Cipher block length */ + uint32_t iv_len; /**< Initialization Vector length */ + uint32_t icv_len; /**< Integrity Check Value length */ +} sa_db_entry_t; + +/** + * Security Assocation (SA) data base global structure + */ +typedef struct sa_db_s { + uint32_t index; /**< Index of next available entry */ + sa_db_entry_t *list; /**< List of active entries */ + sa_db_entry_t array[MAX_DB]; /**< Entry storage */ +} sa_db_t; + +/** Global pointer to sa db */ +static sa_db_t *sa_db; + +/** Initialize SA database global control structure */ +static +void init_sa_db(void) +{ + sa_db = odp_shm_reserve("shm_sa_db", + sizeof(sa_db_t), + ODP_CACHE_LINE_SIZE); + if (sa_db == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(sa_db, 0, sizeof(*sa_db)); +} + +/** + * Create an SA DB entry + * + * String is of the format "SrcIP:DstIP:Alg:SPI:Key" + * + * @param input Pointer to string describing SA + * @param cipher TRUE if cipher else FALSE for auth + * + * @return 0 if successful else -1 + */ +static +int create_sa_db_entry(char *input, boolean cipher) +{ + int pos; + char *local, *str, *save; + sa_db_entry_t *entry = &sa_db->array[sa_db->index]; + + /* Verify we have a good entry */ + if (MAX_DB <= sa_db->index) + return -1; + + /* Make a local copy */ + local = malloc(strlen(input) + 1); + if (local == NULL) + return -1; + strcpy(local, input); + + /* Set cipher versus auth */ + entry->cipher = cipher; + + /* count the number of tokens separated by ',' */ + for (str = local, save = NULL, pos = 0;; str = NULL, pos++) { + char *token = strtok_r(str, ":", &save); + + /* Check for no more tokens */ + if (token == NULL) + break; + + /* Parse based on postion */ + switch (pos) { + case 0: + parse_ipv4_string(token, &entry->src_ip, NULL); + break; + case 1: + parse_ipv4_string(token, &entry->dst_ip, NULL); + break; + case 2: + if (cipher) { + if (0 == strcmp(token, "3des")) { + entry->alg.cipher = + ODP_CIPHER_ALG_3DES_CBC; + entry->block_len = 8; + entry->iv_len = 8; + } else { + entry->alg.cipher = ODP_CIPHER_ALG_NULL; + } + } else { + if (0 == strcmp(token, "md5")) { + entry->alg.cipher = + ODP_AUTH_ALG_MD5_96; + entry->icv_len = 12; + } else { + entry->alg.auth = ODP_AUTH_ALG_NULL; + } + } + break; + case 3: + entry->spi = strtol(token, NULL, 16); + break; + case 4: + parse_key_string(token, + &entry->key, + cipher, + entry->alg); + break; + default: + return -1; + } + } + + /* Verify all positions filled */ + if (5 != pos) + return -1; + + /* Add route to the list */ + sa_db->index++; + entry->next = sa_db->list; + sa_db->list = entry; + + return 0; +} + +/** + * Display the SA DB + */ +static +void dump_sa_db(void) +{ + sa_db_entry_t *entry; + + printf("\n" + "Security association table\n" + "--------------------------\n"); + + for (entry = sa_db->list; NULL != entry; entry = entry->next) { + uint32_t idx; + char src_ip_str[32]; + char dst_ip_str[32]; + uint8_t *p = (uint8_t *)&entry->key; + + + printf(" %s %s %s %X %d ", + entry->cipher ? "esp" : "ah ", + ipv4_addr_str(src_ip_str, entry->src_ip), + ipv4_addr_str(dst_ip_str, entry->dst_ip), + entry->spi, + entry->cipher ? + (int)entry->alg.cipher : + (int)entry->alg.auth); + + /* Brute force key display */ + for (idx = 0; idx < sizeof(odp_key_t); idx++) + printf("%02X", *p++); + + printf("\n"); + } +} + +/** + * Find a matching SA DB entry + * + * @param src Pointer to source subnet/range + * @param dst Pointer to destination subnet/range + * @param cipher TRUE if cipher else FALSE for auth + * + * @return pointer to SA DB entry else NULL + */ +static +sa_db_entry_t *find_sa_db_entry(ip_addr_range_t *src, + ip_addr_range_t *dst, + boolean cipher) +{ + sa_db_entry_t *entry = NULL; + + /* Scan all entries and return first match */ + for (entry = sa_db->list; NULL != entry; entry = entry->next) { + if (cipher != entry->cipher) + continue; + if (!match_ip_range(entry->src_ip, src)) + continue; + if (!match_ip_range(entry->dst_ip, dst)) + continue; + break; + } + return entry; +} + +/** + * Security Policy (SP) data base entry + */ +typedef struct sp_db_entry_s { + struct sp_db_entry_s *next; /**< Next entry on list */ + ip_addr_range_t src_subnet; /**< Source IPv4 subnet/range */ + ip_addr_range_t dst_subnet; /**< Destination IPv4 subnet/range */ + boolean input; /**< Direction when applied */ + boolean esp; /**< Enable cipher (ESP) */ + boolean ah; /**< Enable authentication (AH) */ +} sp_db_entry_t; + +/** + * Security Policy (SP) data base global structure + */ +typedef struct sp_db_s { + uint32_t index; /**< Index of next available entry */ + sp_db_entry_t *list; /**< List of active entries */ + sp_db_entry_t array[MAX_DB]; /**< Entry storage */ +} sp_db_t; + +/** Global pointer to sp db */ +static sp_db_t *sp_db; + +/** Initialize SP database global control structure */ +static +void init_sp_db(void) +{ + sp_db = odp_shm_reserve("shm_sp_db", + sizeof(sp_db_t), + ODP_CACHE_LINE_SIZE); + if (sp_db == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(sp_db, 0, sizeof(*sp_db)); +} + +/** + * Create an SP DB entry + * + * String is of the format "SrcSubNet:DstSubNet:(in|out):(ah|esp|both)" + * + * @param input Pointer to string describing SP + * + * @return 0 if successful else -1 + */ +static +int create_sp_db_entry(char *input) +{ + int pos; + char *local, *str, *save; + sp_db_entry_t *entry = &sp_db->array[sp_db->index]; + + /* Verify we have a good entry */ + if (MAX_DB <= sp_db->index) + return -1; + + /* Make a local copy */ + local = malloc(strlen(input) + 1); + if (local == NULL) + return -1; + strcpy(local, input); + + /* count the number of tokens separated by ',' */ + for (str = local, save = NULL, pos = 0;; str = NULL, pos++) { + char *token = strtok_r(str, ":", &save); + + /* Check for no more tokens */ + if (token == NULL) + break; + + /* Parse based on postion */ + switch (pos) { + case 0: + parse_ipv4_string(token, + &entry->src_subnet.addr, + &entry->src_subnet.mask); + break; + case 1: + parse_ipv4_string(token, + &entry->dst_subnet.addr, + &entry->dst_subnet.mask); + break; + case 2: + if (0 == strcmp(token, "in")) + entry->input = TRUE; + else + entry->input = FALSE; + break; + case 3: + if (0 == strcmp(token, "esp")) { + entry->esp = TRUE; + } else if (0 == strcmp(token, "ah")) { + entry->ah = TRUE; + } else if (0 == strcmp(token, "both")) { + entry->esp = TRUE; + entry->ah = TRUE; + } + break; + default: + return -1; + } + } + + /* Verify all positions filled */ + if (4 != pos) + return -1; + + /* Add route to the list */ + sp_db->index++; + entry->next = sp_db->list; + sp_db->list = entry; + + return 0; +} + +/** + * Display one SP DB entry + * + * @param entry Pointer to entry to display + */ +static +void dump_sp_db_entry(sp_db_entry_t *entry) +{ + char src_subnet_str[32]; + char dst_subnet_str[32]; + + printf(" %s %s %s %s:%s\n", + ipv4_subnet_str(src_subnet_str, &entry->src_subnet), + ipv4_subnet_str(dst_subnet_str, &entry->dst_subnet), + entry->input ? "in" : "out", + entry->esp ? "esp" : "none", + entry->ah ? "ah" : "none"); +} + +/** + * Display the SP DB + */ +static +void dump_sp_db(void) +{ + sp_db_entry_t *entry; + + printf("\n" + "Security policy table\n" + "---------------------\n"); + + for (entry = sp_db->list; NULL != entry; entry = entry->next) + dump_sp_db_entry(entry); +} + +/** + * IPsec cache data base entry + */ +typedef struct ipsec_cache_entry_s { + struct ipsec_cache_entry_s *next; /**< Next entry on list */ + uint32_t src_ip; /**< Source v4 address */ + uint32_t dst_ip; /**< Destination v4 address */ + struct { + enum odp_cipher_alg alg; /**< Cipher algorithm */ + uint32_t spi; /**< Cipher SPI */ + uint32_t block_len; /**< Cipher block length */ + uint32_t iv_len; /**< Cipher IV length */ + odp_key_t key; /**< Cipher key */ + } esp; + struct { + enum odp_auth_alg alg; /**< Auth algorithm */ + uint32_t spi; /**< Auth SPI */ + uint32_t icv_len; /**< Auth ICV length */ + odp_key_t key; /**< Auth key */ + } ah; + + /* Per SA state */ + struct { + odp_crypto_session_t session; /**< Crypto session handle */ + uint32_t esp_seq; /**< ESP TX sequence number */ + uint32_t ah_seq; /**< AH TX sequence number */ + uint8_t iv[32]; /**< ESP IV storage */ + } state; +} ipsec_cache_entry_t; + +/** + * IPsec cache data base global structure + */ +typedef struct ipsec_cache_s { + uint32_t index; /**< Index of next available entry */ + ipsec_cache_entry_t *in_list; /**< List of active input entries*/ + ipsec_cache_entry_t *out_list; /**< List of active output entries*/ + ipsec_cache_entry_t array[MAX_DB]; /**< Entry storage */ +} ipsec_cache_t; + +/** Global pointer to ipsec_cache db */ +static ipsec_cache_t *ipsec_cache; + +/** Initialize IPsec cache */ +static +void init_ipsec_cache(void) +{ + ipsec_cache = odp_shm_reserve("shm_ipsec_cache", + sizeof(ipsec_cache_t), + ODP_CACHE_LINE_SIZE); + if (ipsec_cache == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(ipsec_cache, 0, sizeof(*ipsec_cache)); +} + +/** + * Encode cipher and auth flags into valid cypto mode value + * + * @param cipher Cipher is required + * @param auth Authentication is required + * + * @return enum representing mode + */ +static +enum odp_crypto_combination derive_comb(boolean cipher, boolean auth) +{ + if (cipher && auth) + return ODP_CRYPTO_AUTH_CIPHERTEXT; + else if (cipher) + return ODP_CRYPTO_CIPHER_ONLY; + return ODP_CRYPTO_AUTH_ONLY; +} + +/** + * Create an entry in the IPsec cache + * + * @param cipher_sa Cipher SA DB entry pointer + * @param auth_sa Auth SA DB entry pointer + * @param async Crypto API preferred mode is async + * @param in Direction (input versus output) + * + * @return 0 if successful else -1 + */ +static +int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa, + sa_db_entry_t *auth_sa, + boolean async, + boolean in) +{ + odp_crypto_rc_e rc; + struct odp_crypto_session_params params; + odp_buffer_t compl_event = ODP_BUFFER_INVALID; + ipsec_cache_entry_t *entry; + enum odp_crypto_ses_create_err ses_create_rc; + odp_crypto_session_t session; + + /* Verify we have a good entry */ + entry = &ipsec_cache->array[ipsec_cache->index]; + if (MAX_DB <= ipsec_cache->index) + return -1; + + /* Setup parameters and call crypto library to create session */ + params.op = (in) ? ODP_CRYPTO_OP_DECODE : ODP_CRYPTO_OP_ENCODE; + params.comb = derive_comb((NULL != cipher_sa), (NULL != auth_sa)); + if (async) { + params.pref_mode = ODP_CRYPTO_ASYNC; + params.compl_queue = completionq; + } else { + params.pref_mode = ODP_CRYPTO_SYNC; + params.compl_queue = ODP_QUEUE_INVALID; + } + + /* Cipher */ + if (cipher_sa) { + params.cipher_alg = cipher_sa->alg.cipher; + params.cipher_key = &cipher_sa->key; + params.iv = entry->state.iv; + params.iv_len = cipher_sa->iv_len; + } else { + params.cipher_alg = ODP_CIPHER_ALG_NULL; + params.iv = NULL; + params.iv_len = 0; + } + + /* Auth */ + if (auth_sa) { + params.auth_alg = auth_sa->alg.auth; + params.auth_key = &auth_sa->key; + } else { + params.auth_alg = ODP_AUTH_ALG_NULL; + } + + /* Generate an IV */ + if (params.iv_len) + odp_hw_random_get(params.iv, ¶ms.iv_len, 1); + + /* Synchronous session create for now */ + compl_event = odp_buffer_alloc(msg_pool); + rc = odp_crypto_session_create(¶ms, compl_event, ODP_QUEUE_INVALID); + if (ODP_CRYPTO_OP_OK != rc) + return -1; + odp_crypto_get_ses_create_compl_status(compl_event, &ses_create_rc); + if (ODP_CRYPTO_SES_CREATE_OK != ses_create_rc) + return -1; + odp_crypto_get_ses_create_compl_session(compl_event, &session); + odp_buffer_free(compl_event); + + /* Copy remainder */ + if (cipher_sa) { + entry->src_ip = cipher_sa->src_ip; + entry->dst_ip = cipher_sa->dst_ip; + entry->esp.alg = cipher_sa->alg.cipher; + entry->esp.spi = cipher_sa->spi; + entry->esp.block_len = cipher_sa->block_len; + entry->esp.iv_len = cipher_sa->iv_len; + memcpy(&entry->esp.key, &cipher_sa->key, sizeof(odp_key_t)); + } + if (auth_sa) { + entry->src_ip = auth_sa->src_ip; + entry->dst_ip = auth_sa->dst_ip; + entry->ah.alg = auth_sa->alg.auth; + entry->ah.spi = auth_sa->spi; + entry->ah.icv_len = auth_sa->icv_len; + memcpy(&entry->ah.key, &auth_sa->key, sizeof(odp_key_t)); + } + + /* Initialize state */ + entry->state.esp_seq = 0; + entry->state.ah_seq = 0; + entry->state.session = session; + + /* Add entry to the appropriate list */ + ipsec_cache->index++; + if (in) { + entry->next = ipsec_cache->in_list; + ipsec_cache->in_list = entry; + } else { + entry->next = ipsec_cache->out_list; + ipsec_cache->out_list = entry; + } + + return 0; +} + +/** + * Find a matching IPsec cache entry for input packet + * + * @param src_ip Source IPv4 address + * @param dst_ip Destination IPv4 address + * @param ah Pointer to AH header in packet else NULL + * @param esp Pointer to ESP header in packet else NULL + * + * @return pointer to IPsec cache entry else NULL + */ +static +ipsec_cache_entry_t *find_ipsec_cache_entry_in(uint32_t src_ip, + uint32_t dst_ip, + odp_ahhdr_t *ah, + odp_esphdr_t *esp) +{ + ipsec_cache_entry_t *entry = ipsec_cache->in_list; + + /* Look for a hit */ + for (; NULL != entry; entry = entry->next) { + if ((entry->src_ip != src_ip) || (entry->dst_ip != dst_ip)) + continue; + if (ah && + ((!entry->ah.alg) || + (entry->ah.spi != odp_be_to_cpu_32(ah->spi)))) + continue; + if (esp && + ((!entry->esp.alg) || + (entry->esp.spi != odp_be_to_cpu_32(esp->spi)))) + continue; + break; + } + + return entry; +} + +/** + * Find a matching IPsec cache entry for output packet + * + * @param src_ip Source IPv4 address + * @param dst_ip Destination IPv4 address + * @param proto IPv4 protocol (currently all protocols match) + * + * @return pointer to IPsec cache entry else NULL + */ +static +ipsec_cache_entry_t *find_ipsec_cache_entry_out(uint32_t src_ip, + uint32_t dst_ip, + uint8_t proto ODP_UNUSED) +{ + ipsec_cache_entry_t *entry = ipsec_cache->out_list; + + /* Look for a hit */ + for (; NULL != entry; entry = entry->next) { + if ((entry->src_ip == src_ip) && (entry->dst_ip == dst_ip)) + break; + } + return entry; +} + +/** + * Forwarding data base entry + */ +typedef struct fwd_db_entry_s { + struct fwd_db_entry_s *next; /**< Next entry on list */ + char *oif; /**< Output interface name */ + odp_queue_t queue; /**< Output transmit queue */ + uint8_t src_mac[6]; /**< Output source MAC */ + uint8_t dst_mac[6]; /**< Output destination MAC */ + ip_addr_range_t subnet; /**< Subnet for this router */ +} fwd_db_entry_t; + +/** + * Forwarding data base global structure + */ +typedef struct fwd_db_s { + uint32_t index; /**< Next available entry */ + fwd_db_entry_t *list; /**< List of active routes */ + fwd_db_entry_t array[MAX_DB]; /**< Entry storage */ +} fwd_db_t; + +/** Global pointer to fwd db */ +static fwd_db_t *fwd_db; + +/** Initialize FWD DB */ +static +void init_fwd_db(void) +{ + fwd_db = odp_shm_reserve("shm_fwd_db", + sizeof(fwd_db_t), + ODP_CACHE_LINE_SIZE); + if (fwd_db == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(fwd_db, 0, sizeof(*fwd_db)); +} + +/** + * Create a forwarding database entry + * + * String is of the format "SubNet:Intf:NextHopMAC" + * + * @param input Pointer to string describing route + * + * @return 0 if successful else -1 + */ +static +int create_fwd_db_entry(char *input) +{ + int pos; + char *local, *str, *save; + fwd_db_entry_t *entry = &fwd_db->array[fwd_db->index]; + + /* Verify we haven't run out of space */ + if (MAX_DB <= fwd_db->index) + return -1; + + /* Make a local copy */ + local = malloc(strlen(input) + 1); + if (local == NULL) + return -1; + strcpy(local, input); + + /* count the number of tokens separated by ',' */ + for (str = local, save = NULL, pos = 0;; str = NULL, pos++) { + char *token = strtok_r(str, ":", &save); + + /* Check for no more tokens */ + if (token == NULL) + break; + + /* Parse based on postion */ + switch (pos) { + case 0: + parse_ipv4_string(token, + &entry->subnet.addr, + &entry->subnet.mask); + break; + case 1: + entry->oif = token; + break; + case 2: + parse_mac_string(token, entry->dst_mac); + break; + default: + return -1; + } + } + + /* Verify all positions filled */ + if (3 != pos) + return -1; + + /* Reset queue to invalid */ + entry->queue = ODP_QUEUE_INVALID; + + /* Add route to the list */ + fwd_db->index++; + entry->next = fwd_db->list; + fwd_db->list = entry; + + return 0; +} + +/** + * Scan FWD DB entries and resolve output queue and source MAC address + * + * @param intf Interface name string + * @param outq Output queue for packet transmit + * @param mac MAC address of this interface + */ +static +void resolve_fwd_db(char *intf, odp_queue_t outq, uint8_t *mac) +{ + fwd_db_entry_t *entry; + + /* Walk the list and attempt to set output queue and MAC */ + for (entry = fwd_db->list; NULL != entry; entry = entry->next) { + if (strcmp(intf, entry->oif)) + continue; + + entry->queue = outq; + memcpy(entry->src_mac, mac, 6); + } +} + +/** + * Display one fowarding database entry + * + * @param entry Pointer to entry to display + */ +static +void dump_fwd_db_entry(fwd_db_entry_t *entry) +{ + char subnet_str[32]; + char mac_str[32]; + + printf(" %s %s %s\n", + ipv4_subnet_str(subnet_str, &entry->subnet), + entry->oif, + mac_addr_str(mac_str, entry->dst_mac)); +} + +/** + * Display the forwarding database + */ +static +void dump_fwd_db(void) +{ + fwd_db_entry_t *entry; + + printf("\n" + "Routing table\n" + "-------------\n"); + + for (entry = fwd_db->list; NULL != entry; entry = entry->next) + dump_fwd_db_entry(entry); +} + +/** + * Find a matching forwarding database entry + * + * @param dst_ip Destination IPv4 address + * + * @return pointer to forwarding DB entry else NULL + */ +static +fwd_db_entry_t *find_fwd_db_entry(uint32_t dst_ip) +{ + fwd_db_entry_t *entry; + + for (entry = fwd_db->list; NULL != entry; entry = entry->next) + if (entry->subnet.addr == (dst_ip & entry->subnet.mask)) + break; + return entry; +} + +/** + * Stream database entry structure + */ +typedef struct stream_db_entry_s { + struct stream_db_entry_s *next; /**< Next entry on list */ + int id; /**< Stream ID */ + uint32_t src_ip; /**< Source IPv4 address */ + uint32_t dst_ip; /**< Destination IPv4 address */ + int count; /**< Packet count */ + uint length; /**< Packet payload length */ + uint32_t created; /**< Number successfully created */ + uint32_t verified; /**< Number successfully verified */ + struct { + int loop; /**< Input loop interface index */ + uint32_t ah_seq; /**< AH sequence number if present */ + uint32_t esp_seq; /**< ESP sequence number if present */ + ipsec_cache_entry_t *entry; /**< IPsec to apply on input */ + } input; + struct { + int loop; /**< Output loop interface index */ + ipsec_cache_entry_t *entry; /**t IPsec to verify on output */ + } output; +} stream_db_entry_t; + +typedef struct stream_db_s { + uint32_t index; /**< Index of next available entry */ + stream_db_entry_t *list; /**< List of active entries */ + stream_db_entry_t array[MAX_DB]; /**< Entry storage */ +} stream_db_t; + +static stream_db_t *stream_db; + +/** Initialize stream database global control structure */ +static +void init_stream_db(void) +{ + stream_db = odp_shm_reserve("stream_db", + sizeof(stream_db_t), + ODP_CACHE_LINE_SIZE); + if (stream_db == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(stream_db, 0, sizeof(*stream_db)); +} + +/** + * Create an stream DB entry + * + * String is of the format "SrcIP:DstIP:InInt:OutIntf:Count:Length" + * + * @param input Pointer to string describing stream + * + * @return 0 if successful else -1 + */ +static +int create_stream_db_entry(char *input) +{ + int pos; + char *local, *str, *save; + stream_db_entry_t *entry = &stream_db->array[stream_db->index]; + + /* Verify we have a good entry */ + if (MAX_DB <= stream_db->index) + return -1; + + /* Make a local copy */ + local = malloc(strlen(input) + 1); + if (local == NULL) + return -1; + strcpy(local, input); + + /* count the number of tokens separated by ',' */ + for (str = local, save = NULL, pos = 0;; str = NULL, pos++) { + char *token = strtok_r(str, ":", &save); + + /* Check for no more tokens */ + if (token == NULL) + break; + + /* Parse based on postion */ + switch (pos) { + case 0: + parse_ipv4_string(token, &entry->src_ip, NULL); + break; + case 1: + parse_ipv4_string(token, &entry->dst_ip, NULL); + break; + case 2: + entry->input.loop = loop_if_index(token); + if (entry->input.loop < 0) { + ODP_ERR("Error: stream must have input loop\n"); + exit(EXIT_FAILURE); + } + break; + case 3: + entry->output.loop = loop_if_index(token); + break; + case 4: + entry->count = atoi(token); + break; + case 5: + entry->length = atoi(token); + if (entry->length < sizeof(stream_pkt_hdr_t)) + entry->length = 0; + else + entry->length -= sizeof(stream_pkt_hdr_t); + break; + default: + return -1; + } + } + + /* Verify all positions filled */ + if (6 != pos) + return -1; + + /* Add stream to the list */ + entry->id = stream_db->index++; + entry->next = stream_db->list; + stream_db->list = entry; + + return 0; +} + +/** + * Create IPv4 packet for stream + * + * Create one ICMP test packet based on the stream structure. If an input + * IPsec cache entry is associated with the stream, build a packet that should + * successfully match that entry and be correctly decoded by it. + * + * @param stream Stream DB entry + * @param pkt_pool Packet buffer pool to allocate from + * + * @return packet else ODP_PACKET_INVALID + */ +static +odp_packet_t create_ipv4_packet(stream_db_entry_t *stream, + odp_buffer_pool_t pkt_pool) +{ + ipsec_cache_entry_t *entry = stream->input.entry; + odp_buffer_t bfr; + odp_packet_t pkt; + uint8_t *base; + uint8_t *data; + uint8_t *dmac; + odp_ethhdr_t *eth; + odp_ipv4hdr_t *ip; + odp_ahhdr_t *ah = NULL; + odp_esphdr_t *esp = NULL; + odp_icmphdr_t *icmp; + stream_pkt_hdr_t *test; + uint i; + + /* Get destination MAC address to use */ + dmac = loopback_db->intf[stream->input.loop].mac; + + /* Get buffer */ + bfr = odp_buffer_alloc(pkt_pool); + if (ODP_BUFFER_INVALID == bfr) + return ODP_PACKET_INVALID; + pkt = odp_packet_from_buffer(bfr); + odp_packet_init(pkt); + base = odp_packet_start(pkt); + data = odp_packet_start(pkt); + + /* Ethernet */ + odp_packet_set_l2_offset(pkt, data - base); + eth = (odp_ethhdr_t *)data; + data += sizeof(*eth); + + memset((char *)eth->src.addr, (0x80 | stream->id), ODP_ETHADDR_LEN); + memcpy((char *)eth->dst.addr, dmac, ODP_ETHADDR_LEN); + eth->type = odp_cpu_to_be_16(ODP_ETHTYPE_IPV4); + + /* IPv4 */ + odp_packet_set_l3_offset(pkt, data - base); + ip = (odp_ipv4hdr_t *)data; + data += sizeof(*ip); + odp_packet_set_l4_offset(pkt, data - base); + + /* Wait until almost finished to fill in mutable fields */ + memset((char *)ip, 0, sizeof(*ip)); + ip->ver_ihl = 0x45; + ip->proto = ODP_IPPROTO_ICMP; + ip->id = odp_cpu_to_be_16(stream->id); + ip->src_addr = odp_cpu_to_be_32(stream->src_ip); + ip->dst_addr = odp_cpu_to_be_32(stream->dst_ip); + + /* AH (if specified) */ + if (entry && (ODP_AUTH_ALG_NULL != entry->ah.alg)) { + if (ODP_AUTH_ALG_MD5_96 != entry->ah.alg) + abort(); + + ah = (odp_ahhdr_t *)data; + data += sizeof(*ah); + data += entry->ah.icv_len; + + memset((char *)ah, 0, sizeof(*ah) + entry->ah.icv_len); + ah->ah_len = 1 + (entry->ah.icv_len / 4); + ah->spi = odp_cpu_to_be_32(entry->ah.spi); + ah->seq_no = odp_cpu_to_be_32(stream->input.ah_seq++); + } + + /* ESP (if specified) */ + if (entry && (ODP_CIPHER_ALG_NULL != entry->esp.alg)) { + if (ODP_CIPHER_ALG_3DES_CBC != entry->esp.alg) + abort(); + + esp = (odp_esphdr_t *)data; + data += sizeof(*esp); + data += entry->esp.iv_len; + + esp->spi = odp_cpu_to_be_32(entry->esp.spi); + esp->seq_no = odp_cpu_to_be_32(stream->input.esp_seq++); + RAND_bytes(esp->iv, 8); + } + + /* ICMP header so we can see it on wireshark */ + icmp = (odp_icmphdr_t *)data; + data += sizeof(*icmp); + icmp->type = ICMP_ECHO; + icmp->code = 0; + icmp->un.echo.id = odp_cpu_to_be_16(0x1234); + icmp->un.echo.sequence = odp_cpu_to_be_16(stream->created); + + /* Packet payload of incrementing bytes */ + test = (stream_pkt_hdr_t *)data; + data += sizeof(*test); + test->magic = odp_cpu_to_be_64(STREAM_MAGIC); + for (i = 0; i < stream->length; i++) + *data++ = (uint8_t)i; + + /* Close ICMP */ + icmp->chksum = 0; + icmp->chksum = odp_chksum(icmp, data - (uint8_t *)icmp); + + /* Close ESP if specified */ + if (esp) { + int payload_len = data - (uint8_t *)icmp; + int encrypt_len; + odp_esptrl_t *esp_t; + DES_key_schedule ks1, ks2, ks3; + uint8_t iv[8]; + + memcpy(iv, esp->iv, sizeof(iv)); + + encrypt_len = ESP_ENCODE_LEN(payload_len + sizeof(*esp_t), + entry->esp.block_len); + memset(data, 0, encrypt_len - payload_len); + data += encrypt_len - payload_len; + + esp_t = (odp_esptrl_t *)(data) - 1; + esp_t->pad_len = encrypt_len - payload_len - sizeof(*esp_t); + esp_t->next_header = ip->proto; + ip->proto = ODP_IPPROTO_ESP; + + DES_set_key(&entry->esp.key.des.k1, &ks1); + DES_set_key(&entry->esp.key.des.k2, &ks2); + DES_set_key(&entry->esp.key.des.k3, &ks3); + + DES_ede3_cbc_encrypt((uint8_t *)icmp, + (uint8_t *)icmp, + encrypt_len, + &ks1, + &ks2, + &ks3, + (DES_cblock *)iv, + 1); + } + + /* Since ESP can pad we can now fix IP length */ + ip->tot_len = odp_cpu_to_be_16(data - (uint8_t *)ip); + odp_packet_set_len(pkt, data - base); + + /* Close AH if specified */ + if (ah) { + uint8_t hash[EVP_MAX_MD_SIZE]; + uint32_t hash_len = 12; + int auth_len = data - (uint8_t *)ip; + + ah->next_header = ip->proto; + ip->proto = ODP_IPPROTO_AH; + + HMAC(EVP_md5(), + entry->ah.key.md5.key, + 16, + (uint8_t *)ip, + auth_len, + hash, + &hash_len); + + memcpy(ah->icv, hash, 12); + } + + /* Now fill in final IP header fields */ + ip->ttl = 64; + ip->tos = 0; + ip->frag_offset = 0; + ip->chksum = 0; + odp_ipv4_csum_update(pkt); + return pkt; +} + +/** + * Resolve the stream DB against the IPsec input and output caches + * + * For each stream, look the source and destination IP address up in the + * input and output IPsec caches. If a hit is found, store the hit in + * the stream DB to be used when creating packets. + */ +static +void resolve_stream_db(void) +{ + stream_db_entry_t *stream = NULL; + + /* For each stream look for input and output IPsec entries */ + for (stream = stream_db->list; NULL != stream; stream = stream->next) { + ipsec_cache_entry_t *entry; + + /* Lookup input entry */ + entry = find_ipsec_cache_entry_in(stream->src_ip, + stream->dst_ip, + NULL, + NULL); + stream->input.entry = entry; + + /* Lookup output entry */ + entry = find_ipsec_cache_entry_out(stream->src_ip, + stream->dst_ip, + 0); + stream->output.entry = entry; + } +} + +/** + * Create input packets based on the stream DB + * + * Create input packets based on the configured streams and enqueue them + * into loop interface input queues. Once packet processing starts these + * packets will be remomved and processed as if they had come from a normal + * packet interface. + * + * @return number of streams successfully processed + */ +static +int create_stream_db_inputs(void) +{ + int created = 0; + odp_buffer_pool_t pkt_pool; + stream_db_entry_t *stream = NULL; + + /* Lookup the packet pool */ + pkt_pool = odp_buffer_pool_lookup("packet_pool"); + if (pkt_pool == ODP_BUFFER_POOL_INVALID) { + ODP_ERR("Error: pkt_pool not found\n"); + exit(EXIT_FAILURE); + } + + /* For each stream create corresponding input packets */ + for (stream = stream_db->list; NULL != stream; stream = stream->next) { + int count; + odp_queue_t queue; + + queue = loopback_db->intf[stream->input.loop].inq_def; + + for (count = stream->count; count > 0; count--) { + odp_packet_t pkt; + + pkt = create_ipv4_packet(stream, pkt_pool); + if (ODP_PACKET_INVALID == pkt) { + printf("Packet buffers exhausted\n"); + break; + } + stream->created++; + odp_queue_enq(queue, pkt); + + /* Count this stream when we create first packet */ + if (1 == stream->created) + created++; + } + } + + return created; +} + +/** + * Verify an IPv4 packet received on a loop output queue + * + * TODO: Better error checking, add counters, add tracing, + * add order verification + * + * @param stream Stream to verify the packet against + * @param pkt Packet to verify + * + * @return TRUE if packet verifies else FALSE + */ +static +boolean verify_ipv4_packet(stream_db_entry_t *stream, + odp_packet_t pkt) +{ + ipsec_cache_entry_t *entry = stream->output.entry; + uint8_t *data; + odp_ipv4hdr_t *ip; + odp_ahhdr_t *ah = NULL; + odp_esphdr_t *esp = NULL; + int hdr_len; + odp_icmphdr_t *icmp; + stream_pkt_hdr_t *test; + + /* Basic IPv4 verify (add checksum verification) */ + data = odp_packet_l3(pkt); + ip = (odp_ipv4hdr_t *)data; + data += sizeof(*ip); + if (0x45 != ip->ver_ihl) + return FALSE; + if (stream->src_ip != odp_be_to_cpu_32(ip->src_addr)) + return FALSE; + if (stream->dst_ip != odp_be_to_cpu_32(ip->dst_addr)) + return FALSE; + + /* Find IPsec headers if any and compare against entry */ + hdr_len = locate_ipsec_headers(ip, &ah, &esp); + if (ah) { + if (!entry) + return FALSE; + if (ODP_AUTH_ALG_NULL == entry->ah.alg) + return FALSE; + if (odp_be_to_cpu_32(ah->spi) != entry->ah.spi) + return FALSE; + if (ODP_AUTH_ALG_MD5_96 != entry->ah.alg) + abort(); + } else { + if (entry && (ODP_AUTH_ALG_NULL != entry->ah.alg)) + return FALSE; + } + if (esp) { + if (!entry) + return FALSE; + if (ODP_CIPHER_ALG_NULL == entry->esp.alg) + return FALSE; + if (odp_be_to_cpu_32(esp->spi) != entry->esp.spi) + return FALSE; + if (ODP_CIPHER_ALG_3DES_CBC != entry->esp.alg) + abort(); + hdr_len += entry->esp.iv_len; + } else { + if (entry && (ODP_CIPHER_ALG_NULL != entry->esp.alg)) + return FALSE; + } + data += hdr_len; + + /* Verify authentication (if present) */ + if (ah) { + uint8_t ip_tos; + uint8_t ip_ttl; + uint16_t ip_frag_offset; + uint8_t icv[12]; + uint8_t hash[EVP_MAX_MD_SIZE]; + uint32_t hash_len = 12; + + /* Save/clear mutable fields */ + ip_tos = ip->tos; + ip_ttl = ip->ttl; + ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset); + ip->tos = 0; + ip->ttl = 0; + ip->frag_offset = 0; + ip->chksum = 0; + memcpy(icv, ah->icv, 12); + memset(ah->icv, 0, 12); + + /* Calculate HMAC and compare */ + HMAC(EVP_md5(), + entry->ah.key.md5.key, + 16, + (uint8_t *)ip, + odp_be_to_cpu_16(ip->tot_len), + hash, + &hash_len); + + if (0 != memcmp(icv, hash, sizeof(icv))) + return FALSE; + + ip->proto = ah->next_header; + ip->tos = ip_tos; + ip->ttl = ip_ttl; + ip->frag_offset = odp_cpu_to_be_16(ip_frag_offset); + } + + /* Decipher if present */ + if (esp) { + odp_esptrl_t *esp_t; + DES_key_schedule ks1, ks2, ks3; + uint8_t iv[8]; + int encrypt_len = ipv4_data_len(ip) - hdr_len; + + memcpy(iv, esp->iv, sizeof(iv)); + + DES_set_key(&entry->esp.key.des.k1, &ks1); + DES_set_key(&entry->esp.key.des.k2, &ks2); + DES_set_key(&entry->esp.key.des.k3, &ks3); + + DES_ede3_cbc_encrypt((uint8_t *)data, + (uint8_t *)data, + encrypt_len, + &ks1, + &ks2, + &ks3, + (DES_cblock *)iv, + 0); + + esp_t = (odp_esptrl_t *)(data + encrypt_len) - 1; + ip->proto = esp_t->next_header; + } + + /* Verify ICMP packet */ + if (ODP_IPPROTO_ICMP != ip->proto) + return FALSE; + + /* Verify ICMP header */ + icmp = (odp_icmphdr_t *)data; + data += sizeof(*icmp); + if (ICMP_ECHO != icmp->type) + return FALSE; + if (0x1234 != odp_be_to_cpu_16(icmp->un.echo.id)) + return FALSE; + + /* Now check our packet */ + test = (stream_pkt_hdr_t *)data; + if (STREAM_MAGIC != odp_be_to_cpu_64(test->magic)) + return FALSE; + + return TRUE; +} + +/** + * Verify stream DB outputs + * + * For each stream, poll the output loop interface queue and verify + * any packets found on it + * + * @return TRUE if all packets on all streams verified else FALSE + */ +static +boolean verify_stream_db_outputs(void) +{ + boolean done = TRUE; + stream_db_entry_t *stream = NULL; + + /* For each stream look for output packets */ + for (stream = stream_db->list; NULL != stream; stream = stream->next) { + int idx; + int count; + odp_queue_t queue; + odp_buffer_t buf_tbl[32]; + + queue = loopback_db->intf[stream->output.loop].outq_def; + + if (ODP_QUEUE_INVALID == queue) + continue; + + for (;;) { +#if LOOP_DEQ_MULTIPLE + count = odp_queue_deq_multi(queue, buf_tbl, 32); +#else + buf_tbl[0] = odp_queue_deq(queue); + count = (buf_tbl[0] != ODP_BUFFER_INVALID) ? 1 : 0; +#endif + if (!count) + break; + for (idx = 0; idx < count; idx++) { + boolean good; + odp_packet_t pkt; + + pkt = odp_packet_from_buffer(buf_tbl[idx]); + + good = verify_ipv4_packet(stream, pkt); + if (good) + stream->verified++; + } + } + + printf("Stream %d %d\n", stream->created, stream->verified); + + if (stream->created != stream->verified) + done = FALSE; + } + return done; +} + +/** + * IPsec pre argument processing intialization + */ +static +void ipsec_init_pre(void) +{ + odp_queue_param_t qparam; + void *pool_base; + + /* + * Create queues + * + * - completion queue (should eventually be ORDERED) + * - sequence number queue (must be ATOMIC) + */ + qparam.sched.prio = ODP_SCHED_PRIO_HIGHEST; + qparam.sched.sync = ODP_SCHED_SYNC_ATOMIC; + qparam.sched.group = ODP_SCHED_GROUP_DEFAULT; + + completionq = odp_queue_create("completion", + ODP_QUEUE_TYPE_SCHED, + &qparam); + if (completionq == ODP_QUEUE_INVALID) { + ODP_ERR("Error: completion queue creation failed\n"); + exit(EXIT_FAILURE); + } + + qparam.sched.prio = ODP_SCHED_PRIO_HIGHEST; + qparam.sched.sync = ODP_SCHED_SYNC_ATOMIC; + qparam.sched.group = ODP_SCHED_GROUP_DEFAULT; + + seqnumq = odp_queue_create("seqnum", + ODP_QUEUE_TYPE_SCHED, + &qparam); + if (seqnumq == ODP_QUEUE_INVALID) { + ODP_ERR("Error: sequence number queue creation failed\n"); + exit(EXIT_FAILURE); + } + + /* Create session creation message pool */ + pool_base = odp_shm_reserve("shm_msg_pool", + MSG_POOL_SIZE, ODP_CACHE_LINE_SIZE); + + msg_pool = odp_buffer_pool_create("msg_pool", pool_base, + MSG_POOL_SIZE, + MSG_POOL_BUF_SIZE, + ODP_CACHE_LINE_SIZE, + ODP_BUFFER_TYPE_RAW); + + if (msg_pool == ODP_BUFFER_POOL_INVALID) { + ODP_ERR("Error: message pool create failed.\n"); + exit(EXIT_FAILURE); + } + + /* Initialize our data bases */ + init_sp_db(); + init_sa_db(); + init_ipsec_cache(); +} + +/** + * IPsec post argument processing intialization + * + * Resolve SP DB with SA DB and create corresponding IPsec cache entries + * + * @param async Mode to use when invoking per packet crypto API + */ +static +void ipsec_init_post(boolean async) +{ + sp_db_entry_t *entry; + + /* Attempt to find appropriate SA for each SP */ + for (entry = sp_db->list; NULL != entry; entry = entry->next) { + sa_db_entry_t *cipher_sa = NULL; + sa_db_entry_t *auth_sa = NULL; + + if (entry->esp) + cipher_sa = find_sa_db_entry(&entry->src_subnet, + &entry->dst_subnet, + 1); + if (entry->ah) + auth_sa = find_sa_db_entry(&entry->src_subnet, + &entry->dst_subnet, + 0); + + if (cipher_sa || auth_sa) + create_ipsec_cache_entry(cipher_sa, + auth_sa, + async, + entry->input); + else { + printf(" WARNING: SA not found for SP\n"); + dump_sp_db_entry(entry); + } + } +} + +/** + * Initialize loopback + * + * Initialize ODP queues to create our own idea of loopbacks, which allow + * testing without physical interfaces. Interface name string will be of + * the format "loopX" where X is the decimal number of the interface. + * + * @param intf Loopback interface name string + */ +static +void initialize_loop(char *intf) +{ + int idx; + loopback_db_entry_t *entry; + odp_queue_t outq_def; + odp_queue_t inq_def; + char queue_name[ODP_QUEUE_NAME_LEN]; + odp_queue_param_t qparam; + char mac_str[32]; + + /* Derive loopback interface index */ + idx = loop_if_index(intf); + if (idx < 0) { + ODP_ERR("Error: loopback \"%s\" invalid\n", intf); + exit(EXIT_FAILURE); + } + entry = &loopback_db->intf[idx]; + + /* Dummy MAC address */ + memset(entry->mac, (0xF0 | idx), sizeof(entry->mac)); + + /* Create input queue */ + qparam.sched.prio = ODP_SCHED_PRIO_DEFAULT; + qparam.sched.sync = ODP_SCHED_SYNC_ATOMIC; + qparam.sched.group = ODP_SCHED_GROUP_DEFAULT; + snprintf(queue_name, sizeof(queue_name), "%i-loop_inq_def", idx); + queue_name[ODP_QUEUE_NAME_LEN - 1] = '\0'; + + inq_def = odp_queue_create(queue_name, ODP_QUEUE_TYPE_SCHED, &qparam); + if (inq_def == ODP_QUEUE_INVALID) { + ODP_ERR("Error: input queue creation failed for %s\n", intf); + exit(EXIT_FAILURE); + } + entry->inq_def = inq_def; + + /* Create output queue */ + qparam.sched.prio = ODP_SCHED_PRIO_DEFAULT; + qparam.sched.sync = ODP_SCHED_SYNC_ATOMIC; + qparam.sched.group = ODP_SCHED_GROUP_DEFAULT; + snprintf(queue_name, sizeof(queue_name), "%i-loop_outq_def", idx); + queue_name[ODP_QUEUE_NAME_LEN - 1] = '\0'; + + outq_def = odp_queue_create(queue_name, ODP_QUEUE_TYPE_POLL, &qparam); + if (outq_def == ODP_QUEUE_INVALID) { + ODP_ERR("Error: output queue creation failed for %s\n", intf); + exit(EXIT_FAILURE); + } + entry->outq_def = outq_def; + + printf("Created loop:%02i, queue mode (ATOMIC queues)\n" + " default loop%02i-INPUT queue:%u\n" + " default loop%02i-OUTPUT queue:%u\n" + " source mac address %s\n", + idx, idx, inq_def, idx, outq_def, + mac_addr_str(mac_str, entry->mac)); + + /* Resolve any routes using this interface for output */ + resolve_fwd_db(intf, outq_def, entry->mac); +} + +/** + * Initialize interface + * + * Initialize ODP pktio and queues, query MAC address and update + * forwarding database. + * + * @param intf Interface name string + * @param type Packet IO type (BASIC, MMSG, MMAP) + * @param fanout Packet IO fanout + */ +static +void initialize_intf(char *intf, int type, int fanout) +{ + odp_buffer_pool_t pkt_pool; + odp_pktio_t pktio; + odp_queue_t outq_def; + odp_queue_t inq_def; + char inq_name[ODP_QUEUE_NAME_LEN]; + odp_queue_param_t qparam; + int ret; + odp_pktio_params_t params; + socket_params_t *sock_params = ¶ms.sock_params; + uint8_t src_mac[6]; + char src_mac_str[32]; + + /* Lookup the packet pool */ + pkt_pool = odp_buffer_pool_lookup("packet_pool"); + if (pkt_pool == ODP_BUFFER_POOL_INVALID) { + ODP_ERR("Error: pkt_pool not found\n"); + exit(EXIT_FAILURE); + } + + /* + * Open a packet IO instance for thread and get default output queue + */ + sock_params->type = type; + sock_params->fanout = fanout; + pktio = odp_pktio_open(intf, pkt_pool, ¶ms); + if (pktio == ODP_PKTIO_INVALID) { + ODP_ERR("Error: pktio create failed for %s\n", intf); + exit(EXIT_FAILURE); + } + outq_def = odp_pktio_outq_getdef(pktio); + + /* + * Create and set the default INPUT queue associated with the 'pktio' + * resource + */ + qparam.sched.prio = ODP_SCHED_PRIO_DEFAULT; + qparam.sched.sync = ODP_SCHED_SYNC_ATOMIC; + qparam.sched.group = ODP_SCHED_GROUP_DEFAULT; + snprintf(inq_name, sizeof(inq_name), "%i-pktio_inq_def", (int)pktio); + inq_name[ODP_QUEUE_NAME_LEN - 1] = '\0'; + + inq_def = odp_queue_create(inq_name, ODP_QUEUE_TYPE_PKTIN, &qparam); + if (inq_def == ODP_QUEUE_INVALID) { + ODP_ERR("Error: pktio queue creation failed for %s\n", intf); + exit(EXIT_FAILURE); + } + + ret = odp_pktio_inq_setdef(pktio, inq_def); + if (ret != 0) { + ODP_ERR("Error: default input-Q setup for %s\n", intf); + exit(EXIT_FAILURE); + } + + /* read the source MAC address for this interface */ + query_mac_address(intf, src_mac); + + printf("Created pktio:%02i, queue mode (ATOMIC queues)\n" + " default pktio%02i-INPUT queue:%u\n" + " source mac address %s\n", + pktio, pktio, inq_def, mac_addr_str(src_mac_str, src_mac)); + + /* Resolve any routes using this interface for output */ + resolve_fwd_db(intf, outq_def, src_mac); +} + +/** + * Verify crypto operation completed successfully + * + * @param status Pointer to cryto completion structure + * + * @return TRUE if all OK else FALSE + */ +static +boolean is_crypto_compl_status_ok(struct odp_crypto_compl_status *status) +{ + if (status->alg_err != ODP_CRYPTO_ALG_ERR_NONE) + return FALSE; + if (status->hw_err != ODP_CRYPTO_HW_ERR_NONE) + return FALSE; + return TRUE; +} + +/** + * Packet Processing - Input verification + * + * @param pkt Packet to inspect + * @param ctx Packet process context (not used) + * + * @return PKT_CONTINUE if good, supported packet else PKT_DROP + */ +static +pkt_disposition_e input_verify(odp_packet_t pkt, pkt_ctx_t *ctx ODP_UNUSED) +{ + if (odp_unlikely(odp_packet_error(pkt))) + return PKT_DROP; + + /* + * TODO: for stream packets figure out how to set these flags + * + * if (!odp_packet_inflag_eth(pkt)) + * return PKT_DROP; + * + * if (!odp_packet_inflag_ipv4(pkt)) + * return PKT_DROP; + */ + + return PKT_CONTINUE; +} + +/** + * Packet Processing - Route lookup in forwarding database + * + * @param pkt Packet to route + * @param ctx Packet process context + * + * @return PKT_CONTINUE if route found else PKT_DROP + */ +static +pkt_disposition_e route_fwd_db(odp_packet_t pkt, pkt_ctx_t *ctx) +{ + odp_ipv4hdr_t *ip = (odp_ipv4hdr_t *)odp_packet_l3(pkt); + fwd_db_entry_t *entry; + + entry = find_fwd_db_entry(odp_be_to_cpu_32(ip->dst_addr)); + + if (entry) { + odp_ethhdr_t *eth = (odp_ethhdr_t *)odp_packet_l2(pkt); + + memcpy(ð->dst, entry->dst_mac, 6); + memcpy(ð->src, entry->src_mac, 6); + ctx->outq = entry->queue; + + return PKT_CONTINUE; + } + + return PKT_DROP; +} + +/** + * Packet Processing - Input IPsec packet classification + * + * Verify the received packet has IPsec headers and a match + * in the IPsec cache, if so issue crypto request else skip + * input crypto. + * + * @param pkt Packet to classify + * @param ctx Packet process context + * @param skip Pointer to return "skip" indication + * + * @return PKT_CONTINUE if done else PKT_POSTED + */ +static +pkt_disposition_e do_ipsec_in_classify(odp_packet_t pkt, + pkt_ctx_t *ctx, + boolean *skip) +{ + uint8_t *buf = odp_packet_buf_addr(pkt); + odp_ipv4hdr_t *ip = (odp_ipv4hdr_t *)odp_packet_l3(pkt); + int hdr_len; + odp_ahhdr_t *ah = NULL; + odp_esphdr_t *esp = NULL; + ipsec_cache_entry_t *entry; + struct odp_crypto_op_params params; + odp_crypto_rc_e rc; + + /* Default to skip IPsec */ + *skip = TRUE; + + /* Check IP header for IPSec protocols and look it up */ + hdr_len = locate_ipsec_headers(ip, &ah, &esp); + if (!ah && !esp) + return PKT_CONTINUE; + entry = find_ipsec_cache_entry_in(odp_be_to_cpu_32(ip->src_addr), + odp_be_to_cpu_32(ip->dst_addr), + ah, + esp); + if (!entry) + return PKT_CONTINUE; + + /* Account for configured ESP IV length in packet */ + hdr_len += entry->esp.iv_len; + + /* Initialize parameters block */ + memset(¶ms, 0, sizeof(params)); + params.session = entry->state.session; + params.pkt = pkt; + params.out_pkt = pkt; + + /*Save everything to context */ + ctx->ipsec.ip_tos = ip->tos; + ctx->ipsec.ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset); + ctx->ipsec.ip_ttl = ip->ttl; + ctx->ipsec.ah_offset = ah ? ((uint8_t *)ah) - buf : 0; + ctx->ipsec.esp_offset = esp ? ((uint8_t *)esp) - buf : 0; + ctx->ipsec.hdr_len = hdr_len; + ctx->ipsec.trl_len = 0; + + /*If authenticating, zero the mutable fields build the request */ + if (ah) { + ip->chksum = 0; + ip->tos = 0; + ip->frag_offset = 0; + ip->ttl = 0; + + params.auth_range.offset = ((uint8_t *)ip) - buf; + params.auth_range.length = odp_be_to_cpu_16(ip->tot_len); + params.hash_result_offset = ah->icv - buf; + } + + /* If deciphering build request */ + if (esp) { + params.cipher_range.offset = ipv4_data_p(ip) + hdr_len - buf; + params.cipher_range.length = ipv4_data_len(ip) - hdr_len; + params.override_iv_ptr = esp->iv; + } + + /* Issue crypto request */ + *skip = FALSE; + rc = odp_crypto_operation(¶ms, odp_buffer_from_packet(pkt)); + if (ODP_CRYPTO_OP_ERROR == rc) + abort(); + if (ODP_CRYPTO_OP_POSTED == rc) + return PKT_POSTED; + return PKT_CONTINUE; +} + +/** + * Packet Processing - Input IPsec packet processing cleanup + * + * @param pkt Packet to handle + * @param ctx Packet process context + * + * @return PKT_CONTINUE if successful else PKT_DROP + */ +static +pkt_disposition_e do_ipsec_in_finish(odp_packet_t pkt, + pkt_ctx_t *ctx) +{ + odp_buffer_t event; + struct odp_crypto_compl_status cipher_rc, auth_rc; + odp_ipv4hdr_t *ip = (odp_ipv4hdr_t *)odp_packet_l3(pkt); + int hdr_len = ctx->ipsec.hdr_len; + int trl_len = 0; + + /* Check crypto result */ + event = odp_buffer_from_packet(pkt); + odp_crypto_get_operation_compl_status(event, &cipher_rc, &auth_rc); + if (!is_crypto_compl_status_ok(&cipher_rc)) + return PKT_DROP; + if (!is_crypto_compl_status_ok(&auth_rc)) + return PKT_DROP; + + /* + * Finish auth + */ + if (ctx->ipsec.ah_offset) { + uint8_t *buf = odp_packet_buf_addr(pkt); + odp_ahhdr_t *ah; + + ah = (odp_ahhdr_t *)(ctx->ipsec.ah_offset + buf); + ip->proto = ah->next_header; + } + + /* + * Finish cipher by finding ESP trailer and processing + * + * NOTE: ESP authentication ICV not supported + */ + if (ctx->ipsec.esp_offset) { + uint8_t *eop = (uint8_t *)(ip) + odp_be_to_cpu_16(ip->tot_len); + odp_esptrl_t *esp_t = (odp_esptrl_t *)(eop) - 1; + + ip->proto = esp_t->next_header; + trl_len += esp_t->pad_len + sizeof(*esp_t); + } + + /* Finalize the IPv4 header */ + ipv4_adjust_len(ip, -(hdr_len + trl_len)); + ip->ttl = ctx->ipsec.ip_ttl; + ip->tos = ctx->ipsec.ip_tos; + ip->frag_offset = odp_cpu_to_be_16(ctx->ipsec.ip_frag_offset); + ip->chksum = 0; + odp_ipv4_csum_update(pkt); + + /* Correct the packet length and move payload into position */ + odp_packet_set_len(pkt, odp_packet_get_len(pkt) - (hdr_len + trl_len)); + memmove(ipv4_data_p(ip), + ipv4_data_p(ip) + hdr_len, + odp_be_to_cpu_16(ip->tot_len)); + + /* Fall through to next state */ + return PKT_CONTINUE; +} + +/** + * Packet Processing - Output IPsec packet classification + * + * Verify the outbound packet has a match in the IPsec cache, + * if so issue prepend IPsec headers and prepare parameters + * for crypto API call. Post the packet to ATOMIC queue so + * that sequence numbers can be applied in packet order as + * the next processing step. + * + * @param pkt Packet to classify + * @param ctx Packet process context + * @param skip Pointer to return "skip" indication + * + * @return PKT_CONTINUE if done else PKT_POSTED + */ +static +pkt_disposition_e do_ipsec_out_classify(odp_packet_t pkt, + pkt_ctx_t *ctx, + boolean *skip) +{ + uint8_t *buf = odp_packet_buf_addr(pkt); + odp_ipv4hdr_t *ip = (odp_ipv4hdr_t *)odp_packet_l3(pkt); + uint16_t ip_data_len = ipv4_data_len(ip); + uint8_t *ip_data = ipv4_data_p(ip); + ipsec_cache_entry_t *entry; + struct odp_crypto_op_params params; + int hdr_len = 0; + int trl_len = 0; + odp_ahhdr_t *ah = NULL; + odp_esphdr_t *esp = NULL; + + /* Default to skip IPsec */ + *skip = TRUE; + + /* Find record */ + entry = find_ipsec_cache_entry_out(odp_be_to_cpu_32(ip->src_addr), + odp_be_to_cpu_32(ip->dst_addr), + ip->proto); + if (!entry) + return PKT_CONTINUE; + + /* Save IPv4 stuff */ + ctx->ipsec.ip_tos = ip->tos; + ctx->ipsec.ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset); + ctx->ipsec.ip_ttl = ip->ttl; + + /* Initialize parameters block */ + memset(¶ms, 0, sizeof(params)); + params.session = entry->state.session; + params.pkt = pkt; + params.out_pkt = pkt; + + /* Compute ah and esp, determine length of headers, move the data */ + if (entry->ah.alg) { + ah = (odp_ahhdr_t *)(ip_data); + hdr_len += sizeof(odp_ahhdr_t); + hdr_len += entry->ah.icv_len; + } + if (entry->esp.alg) { + esp = (odp_esphdr_t *)(ip_data + hdr_len); + hdr_len += sizeof(odp_esphdr_t); + hdr_len += entry->esp.iv_len; + } + memmove(ip_data + hdr_len, ip_data, ip_data_len); + ip_data += hdr_len; + + /* For cipher, compute encrypt length, build headers and request */ + if (esp) { + uint32_t encrypt_len; + odp_esptrl_t *esp_t; + + encrypt_len = ESP_ENCODE_LEN(ip_data_len + sizeof(*esp_t), + entry->esp.block_len); + trl_len = encrypt_len - ip_data_len; + + esp->spi = odp_cpu_to_be_32(entry->esp.spi); + memcpy(esp + 1, entry->state.iv, entry->esp.iv_len); + + esp_t = (odp_esptrl_t *)(ip_data + encrypt_len) - 1; + esp_t->pad_len = trl_len - sizeof(*esp_t); + esp_t->next_header = ip->proto; + ip->proto = ODP_IPPROTO_ESP; + + params.cipher_range.offset = ip_data - buf; + params.cipher_range.length = encrypt_len; + } + + /* For authentication, build header clear mutables and build request */ + if (ah) { + memset(ah, 0, sizeof(*ah) + entry->ah.icv_len); + ah->spi = odp_cpu_to_be_32(entry->ah.spi); + ah->ah_len = 1 + (entry->ah.icv_len / 4); + ah->next_header = ip->proto; + ip->proto = ODP_IPPROTO_AH; + + ip->chksum = 0; + ip->tos = 0; + ip->frag_offset = 0; + ip->ttl = 0; + + params.auth_range.offset = ((uint8_t *)ip) - buf; + params.auth_range.length = + odp_be_to_cpu_16(ip->tot_len) + (hdr_len + trl_len); + params.hash_result_offset = ah->icv - buf; + } + + /* Set IPv4 length before authentication */ + ipv4_adjust_len(ip, hdr_len + trl_len); + + /* Save remaining context */ + ctx->ipsec.hdr_len = hdr_len; + ctx->ipsec.trl_len = trl_len; + ctx->ipsec.ah_offset = ah ? ((uint8_t *)ah) - buf : 0; + ctx->ipsec.esp_offset = esp ? ((uint8_t *)esp) - buf : 0; + ctx->ipsec.ah_seq = &entry->state.ah_seq; + ctx->ipsec.esp_seq = &entry->state.esp_seq; + memcpy(&ctx->ipsec.params, ¶ms, sizeof(params)); + + /* Send packet to the atmoic queue to assign sequence numbers */ + *skip = FALSE; + odp_queue_enq(seqnumq, odp_buffer_from_packet(pkt)); + return PKT_POSTED; +} + +/** + * Packet Processing - Output IPsec packet sequence number assignment + * + * Assign the necessary sequence numbers and then issue the crypto API call + * + * @param pkt Packet to handle + * @param ctx Packet process context + * + * @return PKT_CONTINUE if done else PKT_POSTED + */ +static +pkt_disposition_e do_ipsec_out_seq(odp_packet_t pkt, + pkt_ctx_t *ctx) +{ + uint8_t *buf = odp_packet_buf_addr(pkt); + odp_crypto_rc_e rc; + + /* We were dispatched from atomic queue, assign sequence numbers */ + if (ctx->ipsec.ah_offset) { + odp_ahhdr_t *ah; + + ah = (odp_ahhdr_t *)(ctx->ipsec.ah_offset + buf); + ah->seq_no = odp_cpu_to_be_32((*ctx->ipsec.ah_seq)++); + } + if (ctx->ipsec.esp_offset) { + odp_esphdr_t *esp; + + esp = (odp_esphdr_t *)(ctx->ipsec.esp_offset + buf); + esp->seq_no = odp_cpu_to_be_32((*ctx->ipsec.esp_seq)++); + } + + /* Issue crypto request */ + rc = odp_crypto_operation(&ctx->ipsec.params, + odp_buffer_from_packet(pkt)); + if (ODP_CRYPTO_OP_ERROR == rc) + abort(); + if (ODP_CRYPTO_OP_POSTED == rc) + return PKT_POSTED; + return PKT_CONTINUE; +} + +/** + * Packet Processing - Output IPsec packet processing cleanup + * + * @param pkt Packet to handle + * @param ctx Packet process context + * + * @return PKT_CONTINUE if successful else PKT_DROP + */ +static +pkt_disposition_e do_ipsec_out_finish(odp_packet_t pkt, + pkt_ctx_t *ctx) +{ + odp_buffer_t event; + struct odp_crypto_compl_status cipher_rc, auth_rc; + odp_ipv4hdr_t *ip = (odp_ipv4hdr_t *)odp_packet_l3(pkt); + int hdr_len = ctx->ipsec.hdr_len; + int trl_len = ctx->ipsec.trl_len; + + /* Check crypto result */ + event = odp_buffer_from_packet(pkt); + odp_crypto_get_operation_compl_status(event, &cipher_rc, &auth_rc); + if (!is_crypto_compl_status_ok(&cipher_rc)) + return PKT_DROP; + if (!is_crypto_compl_status_ok(&auth_rc)) + return PKT_DROP; + + /* Finalize the IPv4 header */ + ip->ttl = ctx->ipsec.ip_ttl; + ip->tos = ctx->ipsec.ip_tos; + ip->frag_offset = odp_cpu_to_be_16(ctx->ipsec.ip_frag_offset); + ip->chksum = 0; + odp_ipv4_csum_update(pkt); + odp_packet_set_len(pkt, odp_packet_get_len(pkt) + (hdr_len + trl_len)); + + /* Fall through to next state */ + return PKT_CONTINUE; +} + +/** + * Packet IO worker thread + * + * Loop calling odp_schedule to obtain packets from one of three sources, + * and continue processing the packet based on the state stored in its + * per packet context. + * + * - Input interfaces (i.e. new work) + * - Sequence number assignment queue + * - Per packet crypto API completion queue + * + * @param arg thread arguments of type 'thread_args_t *' + * + * @return NULL (should never return) + */ +static +void *pktio_thread(void *arg ODP_UNUSED) +{ + int thr; + odp_packet_t pkt; + odp_buffer_t buf; + unsigned long pkt_cnt = 0; + + thr = odp_thread_id(); + + printf("Pktio thread [%02i] starts\n", thr); + + odp_barrier_sync(&sync_barrier); + + /* Loop packets */ + for (;;) { + pkt_disposition_e rc; + pkt_ctx_t *ctx; + odp_queue_t dispatchq; + + /* Use schedule to get buf from any input queue */ + buf = odp_schedule(&dispatchq, ODP_SCHED_WAIT); + pkt = odp_packet_from_buffer(buf); + ctx = get_ctx_from_buf(buf); + + /* Determine new work versus completion or sequence number */ + if ((completionq != dispatchq) && (seqnumq != dispatchq)) { + memset(ctx, 0, sizeof(*ctx)); + ctx->state = PKT_STATE_INPUT_VERIFY; + } + + /* + * We now have a packet and its associated context. Loop here + * executing processing based on the current state value stored + * in the context as long as the processing return code + * indicates PKT_CONTINUE. + * + * For other return codes: + * + * o PKT_DONE - finished with the packet + * o PKT_DROP - something incorrect about the packet, drop it + * o PKT_POSTED - packet/event has been queued for later + */ + do { + boolean skip; + + switch (ctx->state) { + case PKT_STATE_INPUT_VERIFY: + + rc = input_verify(pkt, ctx); + ctx->state = PKT_STATE_IPSEC_IN_CLASSIFY; + break; + + case PKT_STATE_IPSEC_IN_CLASSIFY: + + rc = do_ipsec_in_classify(pkt, ctx, &skip); + ctx->state = (skip) ? + PKT_STATE_ROUTE_LOOKUP : + PKT_STATE_IPSEC_IN_FINISH; + break; + + case PKT_STATE_IPSEC_IN_FINISH: + + rc = do_ipsec_in_finish(pkt, ctx); + ctx->state = PKT_STATE_ROUTE_LOOKUP; + break; + + case PKT_STATE_ROUTE_LOOKUP: + + rc = route_fwd_db(pkt, ctx); + ctx->state = PKT_STATE_IPSEC_OUT_CLASSIFY; + break; + + case PKT_STATE_IPSEC_OUT_CLASSIFY: + + rc = do_ipsec_out_classify(pkt, ctx, &skip); + ctx->state = (skip) ? + PKT_STATE_TRANSMIT : + PKT_STATE_IPSEC_OUT_SEQ; + break; + + case PKT_STATE_IPSEC_OUT_SEQ: + + rc = do_ipsec_out_seq(pkt, ctx); + ctx->state = PKT_STATE_IPSEC_OUT_FINISH; + break; + + case PKT_STATE_IPSEC_OUT_FINISH: + + rc = do_ipsec_out_finish(pkt, ctx); + ctx->state = PKT_STATE_TRANSMIT; + break; + + case PKT_STATE_TRANSMIT: + + odp_queue_enq(ctx->outq, buf); + rc = PKT_DONE; + break; + + default: + rc = PKT_DROP; + break; + } + } while (PKT_CONTINUE == rc); + + /* Check for drop */ + if (PKT_DROP == rc) + odp_packet_free(pkt); + + /* Print packet counts every once in a while */ + if (PKT_DONE == rc) { + if (odp_unlikely(pkt_cnt++ % 1000 == 0)) { + printf(" [%02i] pkt_cnt:%lu\n", thr, pkt_cnt); + fflush(NULL); + } + } + } + + /* unreachable */ + return NULL; +} + +/** + * ODP ipsec example main function + */ +int +main(int argc, char *argv[]) +{ + odp_linux_pthread_t thread_tbl[MAX_WORKERS]; + odp_buffer_pool_t pool; + int thr_id; + int num_workers; + void *pool_base; + int i; + int first_core; + int core_count; + int stream_count; + + /* Init ODP before calling anything else */ + if (odp_init_global()) { + ODP_ERR("Error: ODP global init failed.\n"); + exit(EXIT_FAILURE); + } + odp_crypto_init(32); + + /* Reserve memory for args from shared mem */ + args = odp_shm_reserve("shm_args", sizeof(args_t), ODP_CACHE_LINE_SIZE); + if (args == NULL) { + ODP_ERR("Error: shared mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + memset(args, 0, sizeof(*args)); + + /* Must init our databases before parsing args */ + ipsec_init_pre(); + init_fwd_db(); + init_loopback_db(); + init_stream_db(); + + /* Parse and store the application arguments */ + parse_args(argc, argv, &args->appl); + + /* Print both system and application information */ + print_info(NO_PATH(argv[0]), &args->appl); + + core_count = odp_sys_core_count(); + num_workers = core_count; + + if (args->appl.core_count) + num_workers = args->appl.core_count; + + if (num_workers > MAX_WORKERS) + num_workers = MAX_WORKERS; + + printf("Num worker threads: %i\n", num_workers); + + /* Create a barrier to synchronize thread startup */ + odp_barrier_init_count(&sync_barrier, num_workers); + + /* + * By default core #0 runs Linux kernel background tasks. + * Start mapping thread from core #1 + */ + first_core = (core_count == 1) ? 0 : 1; + printf("First core: %i\n\n", first_core); + + /* Init this thread */ + thr_id = odp_thread_create(0); + odp_init_local(thr_id); + + /* Create packet buffer pool */ + pool_base = odp_shm_reserve("shm_packet_pool", + SHM_PKT_POOL_SIZE, ODP_CACHE_LINE_SIZE); + if (pool_base == NULL) { + ODP_ERR("Error: packet pool mem alloc failed.\n"); + exit(EXIT_FAILURE); + } + + pool = odp_buffer_pool_create("packet_pool", pool_base, + SHM_PKT_POOL_SIZE, + SHM_PKT_POOL_BUF_SIZE, + ODP_CACHE_LINE_SIZE, + ODP_BUFFER_TYPE_PACKET); + if (pool == ODP_BUFFER_POOL_INVALID) { + ODP_ERR("Error: packet pool create failed.\n"); + exit(EXIT_FAILURE); + } + + /* Populate our IPsec cache */ + printf("Using %s mode for crypto API\n\n", + (args->appl.async) ? "ASYNC" : "SYNC"); + ipsec_init_post(args->appl.async); + + /* Initialize interfaces (which resolves FWD DB entries */ + for (i = 0; i < args->appl.if_count; i++) { + if (!strncmp("loop", args->appl.if_names[i], strlen("loop"))) + initialize_loop(args->appl.if_names[i]); + else + initialize_intf(args->appl.if_names[i], + args->appl.type, + args->appl.fanout); + } + + /* If we have test streams build them before starting workers */ + resolve_stream_db(); + stream_count = create_stream_db_inputs(); + + /* + * Create and init worker threads + */ + memset(thread_tbl, 0, sizeof(thread_tbl)); + for (i = 0; i < num_workers; ++i) { + int core; + + core = (first_core + i) % core_count; + + /* + * Create threads one-by-one instead of all-at-once, + * because each thread might get different arguments. + * Calls odp_thread_create(cpu) for each thread + */ + odp_linux_pthread_create(thread_tbl, 1, core, pktio_thread, + &args->thread[i]); + } + + /* + * If there are streams attempt to verify them else + * wait indefinitely + */ + if (stream_count) { + boolean done; + + do { + done = verify_stream_db_outputs(); + sleep(1); + } while (!done); + printf("All received\n"); + } else { + odp_linux_pthread_join(thread_tbl, num_workers); + } + + printf("Exit\n\n"); + + return 0; +} + +/** + * Parse and store the command line arguments + * + * @param argc argument count + * @param argv[] argument vector + * @param appl_args Store application arguments here + */ +static void parse_args(int argc, char *argv[], appl_args_t *appl_args) +{ + int opt; + int long_index; + char *names, *str, *token, *save; + size_t len; + int i; + static struct option longopts[] = { + {"count", required_argument, NULL, 'c'}, + {"interface", required_argument, NULL, 'i'}, /* return 'i' */ + {"mode", required_argument, NULL, 'm'}, /* return 'm' */ + {"route", required_argument, NULL, 'r'}, /* return 'r' */ + {"policy", required_argument, NULL, 'p'}, /* return 'p' */ + {"ah", required_argument, NULL, 'a'}, /* return 'a' */ + {"esp", required_argument, NULL, 'e'}, /* return 'e' */ + {"stream", required_argument, NULL, 's'}, /* return 's' */ + {"help", no_argument, NULL, 'h'}, /* return 'h' */ + {NULL, 0, NULL, 0} + }; + + appl_args->type = 3; /* 3: ODP_PKTIO_TYPE_SOCKET_MMAP */ + appl_args->fanout = 0; /* turn off fanout by default for mmap */ + appl_args->async = 0; /* turn off async crypto API by default */ + + while (1) { + opt = getopt_long(argc, argv, "+c:i:m:t:f:h:r:p:a:e:s:", + longopts, &long_index); + + if (opt == -1) + break; /* No more options */ + + switch (opt) { + case 'c': + appl_args->core_count = atoi(optarg); + break; + /* parse packet-io interface names */ + case 'i': + len = strlen(optarg); + if (len == 0) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + len += 1; /* add room for '\0' */ + + names = malloc(len); + if (names == NULL) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + /* count the number of tokens separated by ',' */ + strcpy(names, optarg); + for (str = names, i = 0;; str = NULL, i++) { + token = strtok_r(str, ",", &save); + if (token == NULL) + break; + } + appl_args->if_count = i; + + if (appl_args->if_count == 0) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + /* allocate storage for the if names */ + appl_args->if_names = + calloc(appl_args->if_count, sizeof(char *)); + + /* store the if names (reset names string) */ + strcpy(names, optarg); + for (str = names, i = 0;; str = NULL, i++) { + token = strtok_r(str, ",", &save); + if (token == NULL) + break; + appl_args->if_names[i] = token; + } + break; + + case 't': + appl_args->type = atoi(optarg); + break; + + case 'f': + appl_args->fanout = atoi(optarg); + break; + + case 'm': + appl_args->async = atoi(optarg); + break; + + case 'r': + create_fwd_db_entry(optarg); + break; + + case 'p': + create_sp_db_entry(optarg); + break; + + case 'a': + create_sa_db_entry(optarg, FALSE); + break; + + case 'e': + create_sa_db_entry(optarg, TRUE); + break; + + case 's': + create_stream_db_entry(optarg); + break; + + case 'h': + usage(argv[0]); + exit(EXIT_SUCCESS); + break; + + default: + break; + } + } + + if (appl_args->if_count == 0) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + optind = 1; /* reset 'extern optind' from the getopt lib */ +} + +/** + * Print system and application info + */ +static void print_info(char *progname, appl_args_t *appl_args) +{ + int i; + + printf("\n" + "ODP system info\n" + "---------------\n" + "ODP API version: %s\n" + "CPU model: %s\n" + "CPU freq (hz): %"PRIu64"\n" + "Cache line size: %i\n" + "Core count: %i\n" + "\n", + odp_version_api_str(), odp_sys_cpu_model_str(), odp_sys_cpu_hz(), + odp_sys_cache_line_size(), odp_sys_core_count()); + + printf("Running ODP appl: \"%s\"\n" + "-----------------\n" + "IF-count: %i\n" + "Using IFs: ", + progname, appl_args->if_count); + for (i = 0; i < appl_args->if_count; ++i) + printf(" %s", appl_args->if_names[i]); + + printf("\n"); + + dump_fwd_db(); + dump_sp_db(); + dump_sa_db(); + printf("\n\n"); + fflush(NULL); +} + +/** + * Prinf usage information + */ +static void usage(char *progname) +{ + printf("\n" + "Usage: %s OPTIONS\n" + " E.g. %s -i eth1,eth2,eth3 -m 0\n" + "\n" + "OpenDataPlane example application.\n" + "\n" + "Mandatory OPTIONS:\n" + " -i, --interface Eth interfaces (comma-separated, no spaces)\n" + " -t, --type 1: ODP_PKTIO_TYPE_SOCKET_BASIC\n" + " 2: ODP_PKTIO_TYPE_SOCKET_MMSG\n" + " 3: ODP_PKTIO_TYPE_SOCKET_MMAP\n" + " 4: ODP_PKTIO_TYPE_NETMAP\n" + " Default: 3: ODP_PKTIO_TYPE_SOCKET_MMAP\n" + " -f, --fanout 0: off 1: on (Default 1: on)\n" + " -m, --mode 0: sync 1: async crypto API (default 0)\n" + "\n" + "Routing / IPSec OPTIONS:\n" + " -r, --route SubNet:Intf:NextHopMAC\n" + " -p, --policy SrcSubNet:DstSubNet:(in|out):(ah|esp|both)\n" + " -e, --esp SrcIP:DstIP:(3des|null):SPI:Key192\n" + " -a, --ah SrcIP:DstIP:(md5|null):SPI:Key128\n" + "\n" + " Where: NextHopMAC is raw hex/dot notation, i.e. 03.BA.44.9A.CE.02\n" + " IP is decimal/dot notation, i.e. 192.168.1.1\n" + " SubNet is decimal/dot/slash notation, i.e 192.168.0.0/16\n" + " SPI is raw hex, 32 bits\n" + " KeyXXX is raw hex, XXX bits long\n" + "\n" + " Examples:\n" + " -r 192.168.222.0/24:p8p1:08.00.27.F5.8B.DB\n" + " -p 192.168.111.0/24:192.168.222.0/24:out:esp\n" + " -e 192.168.111.2:192.168.222.2:3des:201:656c8523255ccc23a66c1917aa0cf30991fce83532a4b224\n" + " -a 192.168.111.2:192.168.222.2:md5:201:a731649644c5dee92cbd9c2e7e188ee6\n" + "\n" + "Optional OPTIONS\n" + " -c, --count Core count.\n" + " -h, --help Display help and exit.\n" + "\n", NO_PATH(progname), NO_PATH(progname) + ); +} diff --git a/test/ipsec/run_ah_in.sh b/test/ipsec/run_ah_in.sh new file mode 100755 index 0000000..1618071 --- /dev/null +++ b/test/ipsec/run_ah_in.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Test input AH +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +# +./odp_ipsec -i loop1,loop2 \ +-r 192.168.111.2/32:loop1:08.00.27.76.B5.E0 \ +-p 192.168.222.0/24:192.168.111.0/24:in:ah \ +-a 192.168.222.2:192.168.111.2:md5:300:27f6d123d7077b361662fc6e451f65d8 \ +-s 192.168.222.2:192.168.111.2:loop2:loop1:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_ah_out.sh b/test/ipsec/run_ah_out.sh new file mode 100755 index 0000000..d61b52a --- /dev/null +++ b/test/ipsec/run_ah_out.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Test output AH +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode + ./odp_ipsec -i loop1,loop2 \ +-r 192.168.222.2/32:loop2:08.00.27.F5.8B.DB \ +-p 192.168.111.0/24:192.168.222.0/24:out:ah \ +-a 192.168.111.2:192.168.222.2:md5:200:a731649644c5dee92cbd9c2e7e188ee6 \ +-s 192.168.111.2:192.168.222.2:loop1:loop2:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_both_in.sh b/test/ipsec/run_both_in.sh new file mode 100755 index 0000000..3b27409 --- /dev/null +++ b/test/ipsec/run_both_in.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Test AH and ESP input +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +./odp_ipsec -i loop1,loop2 \ +-r 192.168.111.2/32:loop1:08.00.27.76.B5.E0 \ +-p 192.168.222.0/24:192.168.111.0/24:in:both \ +-a 192.168.222.2:192.168.111.2:md5:300:27f6d123d7077b361662fc6e451f65d8 \ +-e 192.168.222.2:192.168.111.2:\ +3des:301:c966199f24d095f3990a320d749056401e82b26570320292 \ +-s 192.168.222.2:192.168.111.2:loop2:loop1:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_both_out.sh b/test/ipsec/run_both_out.sh new file mode 100755 index 0000000..37bb1e4 --- /dev/null +++ b/test/ipsec/run_both_out.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Test AH and ESP output +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +./odp_ipsec -i loop1,loop2 \ +-r 192.168.222.2/32:loop2:08.00.27.F5.8B.DB \ +-p 192.168.111.0/24:192.168.222.0/24:out:both \ +-e 192.168.111.2:192.168.222.2:\ +3des:201:656c8523255ccc23a66c1917aa0cf30991fce83532a4b224 \ +-a 192.168.111.2:192.168.222.2:md5:200:a731649644c5dee92cbd9c2e7e188ee6 \ +-s 192.168.111.2:192.168.222.2:loop1:loop2:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_esp_in.sh b/test/ipsec/run_esp_in.sh new file mode 100755 index 0000000..a9a0db3 --- /dev/null +++ b/test/ipsec/run_esp_in.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Test input ESP +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +./odp_ipsec -i loop1,loop2 \ +-r 192.168.111.2/32:loop1:08.00.27.76.B5.E0 \ +-p 192.168.222.0/24:192.168.111.0/24:in:esp \ +-e 192.168.222.2:192.168.111.2:\ +3des:301:c966199f24d095f3990a320d749056401e82b26570320292 \ +-s 192.168.222.2:192.168.111.2:loop2:loop1:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_esp_out.sh b/test/ipsec/run_esp_out.sh new file mode 100755 index 0000000..f87311c --- /dev/null +++ b/test/ipsec/run_esp_out.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Test output ESP +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +./odp_ipsec -i loop1,loop2 \ +-r 192.168.222.2/32:loop2:08.00.27.F5.8B.DB \ +-p 192.168.111.0/24:192.168.222.0/24:out:esp \ +-e 192.168.111.2:192.168.222.2:\ +3des:201:656c8523255ccc23a66c1917aa0cf30991fce83532a4b224 \ +-s 192.168.111.2:192.168.222.2:loop1:loop2:10:100 \ +-c 2 -f 0 -m 0 diff --git a/test/ipsec/run_simple.sh b/test/ipsec/run_simple.sh new file mode 100755 index 0000000..1d80fa9 --- /dev/null +++ b/test/ipsec/run_simple.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# +# Simple router test +# - 2 loop interfaces +# - 10 packets +# - SYNC API mode +./odp_ipsec -i loop1,loop2 \ +-r 192.168.222.2/32:loop2:08.00.27.F5.8B.DB \ +-s 192.168.111.2:192.168.222.2:loop1:loop2:10:100 \ +-c 2 -f 0 -m 0