From 4e88e041ad47bf422bbb2a0940f77aba11ea2178 Mon Sep 17 00:00:00 2001 From: Gabriel Oginski Date: Wed, 29 Jun 2022 12:54:30 +0000 Subject: vpp-swan: Add plugin for vpp-swan Added plugin vpp-swan is a plugin that helps offloading Strongswan IPsec ESP process from Linux Kernel to VPP. Type: feature Signed-off-by: Gabriel Oginski Change-Id: Iec77945892453fac1890d3c49d7d86fc6b09c893 --- extras/strongswan/vpp_sswan/Makefile | 108 ++ extras/strongswan/vpp_sswan/README.rst | 128 ++ extras/strongswan/vpp_sswan/kernel-vpp.conf | 7 + extras/strongswan/vpp_sswan/kernel_vpp_ipsec.c | 1751 +++++++++++++++++++++++ extras/strongswan/vpp_sswan/kernel_vpp_ipsec.h | 41 + extras/strongswan/vpp_sswan/kernel_vpp_net.c | 752 ++++++++++ extras/strongswan/vpp_sswan/kernel_vpp_net.h | 41 + extras/strongswan/vpp_sswan/kernel_vpp_plugin.c | 103 ++ extras/strongswan/vpp_sswan/kernel_vpp_plugin.h | 34 + extras/strongswan/vpp_sswan/kernel_vpp_shared.c | 622 ++++++++ extras/strongswan/vpp_sswan/kernel_vpp_shared.h | 85 ++ extras/strongswan/vpp_sswan/swanctl.conf | 35 + 12 files changed, 3707 insertions(+) create mode 100644 extras/strongswan/vpp_sswan/Makefile create mode 100644 extras/strongswan/vpp_sswan/README.rst create mode 100644 extras/strongswan/vpp_sswan/kernel-vpp.conf create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_ipsec.c create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_ipsec.h create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_net.c create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_net.h create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_plugin.c create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_plugin.h create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_shared.c create mode 100644 extras/strongswan/vpp_sswan/kernel_vpp_shared.h create mode 100644 extras/strongswan/vpp_sswan/swanctl.conf (limited to 'extras/strongswan/vpp_sswan') diff --git a/extras/strongswan/vpp_sswan/Makefile b/extras/strongswan/vpp_sswan/Makefile new file mode 100644 index 00000000000..cb642c8669f --- /dev/null +++ b/extras/strongswan/vpp_sswan/Makefile @@ -0,0 +1,108 @@ +# the directory to the strongSwan sources +SWANDIR=${CURDIR}/../../../build-root/build-vpp-native/external/sswan +# location of config.h +CONFIGH=$(SWANDIR)/config.h +# default install prefix: /usr/local or /usr +PREFIX=/usr +# location of the installed strongSwan libraries +SWANLIBS=$(PREFIX)/lib/ipsec/ +# location of the strongSwan plugins +SWANPLUGINS=$(PREFIX)/lib/ipsec/plugins +# location of the strongSwan archive +SWANARCHIVE=${CURDIR}/../../../build/external/downloads +# default install configuration files: +PREFIX_SYS_CONF=/etc +# target location of the plugin config snippet: $(PREFIX)/etc/strongswan.d/charon/ or /etc/strongswan.d/charon/ +PLUGINCONF=$(PREFIX_SYS_CONF)/strongswan.d/charon/ +# location of the VPP libraries +VPPLIBS=$(CURDIR)/../../../build-root/install-vpp-native/vpp/lib/x86_64-linux-gnu +# the directory to the VPP sources +VPPDIR=../../../build-root/install-vpp-native/vpp/include + +TARGET=libstrongswan-kernel-vpp.so + +# tested only with 5.9.5 and 5.9.6 version of strongSwan +VERSION_SSWAN=5.9.6 + +CFLAGS=-O2 -g -Wall -Wextra -fpic + +CFLAGS_I=-include $(CONFIGH) \ + -I$(SWANDIR)/src/libstrongswan \ + -I$(SWANDIR)/src/libcharon + +LDFLAGS= -lvppinfra \ + -lvlibmemoryclient \ + -lvppapiclient \ + -lsvm \ + -lvlib + +VERSION_VPP=$(shell (dpkg -s vpp | grep Version) | grep -Po '(?<=Version: )\d\d.\d\d') + +# check if VPP is installed +ifneq ($(shell test "$(shell ldconfig -p | grep vppinfra.so | awk 'NR==1{print $$1;}')" && echo "yes"), yes) +# check if VPPDIR exists +ifeq ($(shell test -d $(VPPDIR) && echo "yes"), yes) + CFLAGS_I += -I$(VPPDIR) +endif +# check if VPPLIBS exists +ifeq ($(shell test -d $(VPPLIBS) && echo "yes"), yes) + LDFLAGS += -L$(VPPLIBS) + LDFLAGS += -Wl,-rpath=$(VPPLIBS) +endif +endif + +SOURCES=$(wildcard *.c) +OBJECTS=$(SOURCES:.c=.o) + +all: pull-swan $(TARGET) + +pull-swan: + @if [ -d "${SWANDIR}" ]; then \ + rm -rf ${SWANDIR} ; \ + fi + @if ! [ -f "${SWANARCHIVE}/strongswan-${VERSION_SSWAN}.tar.gz" ]; then \ + curl -o ${SWANARCHIVE}/strongswan-${VERSION_SSWAN}.tar.gz -LO https://github.com/strongswan/strongswan/archive/${VERSION_SSWAN}.tar.gz; \ + fi + @if ! [ -d "${CURDIR}/../../../build-root/build-vpp-native/external/" ]; then \ + mkdir ${CURDIR}/../../../build-root/build-vpp-native/external; \ + fi + tar -zxof ${SWANARCHIVE}/strongswan-${VERSION_SSWAN}.tar.gz -C ${CURDIR}/../../../build-root/build-vpp-native/external/ + mv ${CURDIR}/../../../build-root/build-vpp-native/external/strongswan-${VERSION_SSWAN} ${SWANDIR} + cd ${SWANDIR} && ./autogen.sh + cd ${SWANDIR} && ./configure --prefix=${PREFIX} --sysconfdir=${PREFIX_SYS_CONF} --enable-libipsec --enable-systemd --enable-swanctl --disable-gmp --enable-openssl + cd ${SWANDIR} && make -j$(nproc) +# cd ${SWANDIR} && sudo make install + +# check if VPP is installed +ifneq ($(shell test "$(shell ldconfig -p | grep vppinfra.so | awk 'NR==1{print $$1;}')" && echo "yes"), yes) + $(info INFO: Not found installed VPP - checking if locally VPP exists) +# check if VPPDIR exists +ifneq ($(shell test -d $(VPPDIR) && echo "yes"), yes) + $(error ERROR: Not found installed VPP and locally VPP - please install or build) +else +# check if VPPLIBS exists +ifneq ($(shell test -d $(VPPLIBS) && echo "yes"), yes) + $(error ERROR: directory $(VPPLIBS) - doesn't exists, please compile VPP before build this) +else + $(info INFO: Found locally VPP) +endif +endif +else + $(info INFO: Found installed VPP in version: $(VERSION_VPP)) +endif + +$(TARGET): $(OBJECTS) + gcc $(CFLAGS) -shared -DPIC $(OBJECTS) $(LDFLAGS) -Wl,-soname -Wl,$(TARGET) -o $@ + cp $(TARGET) ${SWANDIR} + +%.o: %.c + gcc $(CFLAGS) $(CFLAGS_I) -c $< -o $@ $(LDFLAGS) + +install: + cp $(TARGET) $(SWANPLUGINS)/$(TARGET) + cp kernel-vpp.conf $(PLUGINCONF) + +clean: + rm -f *.so *.o + +.PHONY: clean install all \ No newline at end of file diff --git a/extras/strongswan/vpp_sswan/README.rst b/extras/strongswan/vpp_sswan/README.rst new file mode 100644 index 00000000000..4def8383c2a --- /dev/null +++ b/extras/strongswan/vpp_sswan/README.rst @@ -0,0 +1,128 @@ +.. _vpp_sswan_doc: + +VPP-SSWAN +======================= + +``VPP-SSWAN`` is a StrongSwan plugin that helps offloading Strongswan IPsec ESP +process from Linux Kernel to ``VPP``. + +The kernel-vpp plugin is an interface to the IPsec and networking backend for +`VPP `__ platform using the +`VPP C API `__. +It provides address and routing lookup functionality and installs routes for +IPsec traffic. +It installs and maintains Security Associations and Policies to the +`VPP IPsec `__. + +Prerequisites +------------- + +``VPP`` in release mode should be built before compiling ``vpp-swan plugin``. +The dependencies of ``StrongSwan`` should be installed before building +``VPP-SSWAN``. In addition ``libsystemd-dev`` should be installed. + +Build VPP Strongswan Plugin +------------- + +The following list of things will be done to build ``vpp-swan plugin``: + +- download strongswan source code to: +``path/to/vpp/build/external/downloads`` + +- unzip source code strongswan to: +``path/to/vpp/build-root/build-vpp-native/external/sswan`` + +- check if you have installed packages: ``libsystemd-dev`` on your OS + +- configure strongswan by: +``./configure --prefix=/usr --sysconfdir=/etc --enable-libipsec +--enable-systemd --enable-swanctl --disable-gmp --enable-openssl`` + +- compile strongswan in: +``path/to/vpp/build-root/build-vpp-native/external/sswan`` + +- compile ``vpp-swan plugin`` by: + +:: + + ./make all + +- if everything it ok, copy the compiled ``vpp-swan plugin`` to: +``/usr/lib/ipsec/plugins`` + +Build/install Strongswan +------------- + +It is recommended to use ``Strongswan`` in version ``5.9.6`` or ``5.9.5`` +installed from this script, due to configuration Strongswan that is required. +Only version ``5.9.5`` and ``5.9.6`` was tested with this plugin. + +To install the built Strongswan, please execute the following command: + +:: + + path/to/vpp/build-root/build-vpp-native/external/sswan/sudo make install + +Insert plugin in runtime mode +------------- + +After builded this plugin and also installed Strongswan you can loaded plugin +into Strongswan directory by: + +:: + + ./make install + +Or you can do manually copy ``libstrongswan-kernel-vpp.so`` into: +``/usr/lib/ipsec/plugins`` and also ``kernel-vpp.conf`` into: ``/etc/strongswan.d/charon/`` + +And also you should restart Strongswan by: + +:: + + systemctl restart strongswan.service + +Configuration Strongswan +------------- +In ``swanctl.conf`` file you can find example configuration to initialize +connections between two endpoints. + +Copy this file into: ``/etc/swanctl/conf.d/swanctl.conf`` + +Configuration VPP +------------- + +In your ``startup.conf`` add these following commands: + +:: + + plugins { + plugin linux_cp_plugin.so { enable } + plugin ikev2_plugin.so { disable } + } + + linux-cp { + lcp-sync + } + +To enable ``CP Plugin`` and disable ``IKEv2`` plugin. + +These following commands executed in ``VPP``: + +:: + + lcp create eth2 host-if eth2 + set interface state eth2 up + set interface ip address eth2 192.168.0.2/24 + set int state eth1 up + set int ip addr eth1 192.168.200.1/24 + +To create interface by ``CP Plugin`` and also setup two ethernet interfaces. + +Misc +------------- +This plugin is based on: +`https://github.com/matfabia/strongswan +`__ + +Author: Matus Fabian \ No newline at end of file diff --git a/extras/strongswan/vpp_sswan/kernel-vpp.conf b/extras/strongswan/vpp_sswan/kernel-vpp.conf new file mode 100644 index 00000000000..71f50b5bf08 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel-vpp.conf @@ -0,0 +1,7 @@ +kernel-vpp { + + # Whether to load the plugin. Can also be an integer to increase the + # priority of this plugin. + load = yes + +} diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.c b/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.c new file mode 100644 index 00000000000..7849dd6b898 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.c @@ -0,0 +1,1751 @@ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define vl_typedefs +#define vl_endianfun +/* Include the (first) vlib-api API definition layer */ +#include +/* Include the current layer (third) vpp API definition layer */ +#include +#include + +#include +#include +#include +#include +#include +#include +#undef vl_typedefs +#undef vl_endianfun + +#include "kernel_vpp_ipsec.h" +#include "kernel_vpp_shared.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PRIO_BASE 384 + +u32 natt_port; + +/** + * One and only instance of the daemon. + */ +daemon_t *charon; + +typedef struct private_kernel_vpp_ipsec_t private_kernel_vpp_ipsec_t; + +/** + * Private variables of kernel_vpp_ipsec class. + */ +struct private_kernel_vpp_ipsec_t +{ + + /** + * Public interface + */ + kernel_vpp_ipsec_t public; + + /** + * Next security association database entry ID to allocate + */ + refcount_t next_sad_id; + + /** + * Next security policy database entry ID to allocate + */ + refcount_t next_spd_id; + + /** + * Mutex to lock access to installed policies + */ + mutex_t *mutex; + + /** + * Hash table of instaled SA, as kernel_ipsec_sa_id_t => sa_t + */ + hashtable_t *sas; + + /** + * Hash table of security policy databases, as nterface => spd_t + */ + hashtable_t *spds; + + /** + * Linked list of installed routes + */ + linked_list_t *routes; + + /** + * Next SPI to allocate + */ + refcount_t nextspi; + + /** + * Mix value to distribute SPI allocation randomly + */ + uint32_t mixspi; + + /** + * Whether to install routes along policies + */ + bool install_routes; +}; + +/** + * Security association entry + */ +typedef struct +{ + /** VPP SA ID */ + uint32_t sa_id; + uint32_t stat_index; +} sa_t; + +/** + * Security policy database + */ +typedef struct +{ + /** VPP SPD ID */ + uint32_t spd_id; + /** Networking interface ID restricting policy */ + uint32_t sw_if_index; + /** Policy count for this SPD */ + refcount_t policy_num; +} spd_t; + +/** + * Installed route + */ +typedef struct +{ + /** Name of the interface the route is bound to */ + char *if_name; + /** Gateway of route */ + host_t *gateway; + /** Destination network of route */ + host_t *dst_net; + /** Prefix length of dst_net */ + uint8_t prefixlen; + /** References for route */ + refcount_t refs; +} route_entry_t; + +#define htonll(x) \ + ((1 == htonl (1)) ? \ + (x) : \ + ((uint64_t) htonl ((x) &0xFFFFFFFF) << 32) | htonl ((x) >> 32)) +#define ntohll(x) \ + ((1 == ntohl (1)) ? \ + (x) : \ + ((uint64_t) ntohl ((x) &0xFFFFFFFF) << 32) | ntohl ((x) >> 32)) + +CALLBACK (route_equals, bool, route_entry_t *a, va_list args) +{ + host_t *dst_net, *gateway; + uint8_t *prefixlen; + char *if_name; + + VA_ARGS_VGET (args, if_name, gateway, dst_net, prefixlen); + + return a->if_name && if_name && streq (a->if_name, if_name) && + a->gateway->ip_equals (a->gateway, gateway) && + a->dst_net->ip_equals (a->dst_net, dst_net) && + a->prefixlen == *prefixlen; +} + +/** + * Clean up a route entry + */ +static void +route_destroy (route_entry_t *this) +{ + this->dst_net->destroy (this->dst_net); + this->gateway->destroy (this->gateway); + free (this->if_name); + free (this); +} + +static uint32_t get_sw_if_index (); + +static int +set_arp (char *ipStr, char *if_name, bool add) +{ + char *out = NULL; + int out_len = 0; + vl_api_ip_neighbor_add_del_t *mp; + vl_api_ip_neighbor_add_del_reply_t *rmp; + int rc = SUCCESS; + uint32_t sw_if_index = ~0; + + FILE *fp; + int nread = 0; + ssize_t len = 0; + char *buffer = NULL; + char buf[2][20]; + char *file = "/proc/net/arp"; + unsigned char mac[8] = { + 0, + }; + uint32_t addr; + + if (if_name == NULL || ipStr == NULL) + { + DBG2 (DBG_KNL, "para is null\n"); + rc = FAILED; + } + DBG2 (DBG_KNL, "from kernel read mac\n"); + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + sw_if_index = get_sw_if_index (if_name); + if (sw_if_index == ~0) + { + DBG1 (DBG_KNL, "sw_if_index for %s not found", if_name); + goto error; + } + + fp = fopen (file, "rb"); + while ((nread = getline (&buffer, &len, fp)) != -1) + { + sscanf (buffer, "%s %*s %*s %s %*s %*s", &buf[0], &buf[1]); + inet_aton (&buf[0], &addr); + + if (addr == *((u32 *) (ipStr))) + { + sscanf (buf[1], "%02x:%02x:%02x:%02x:%02x:%02x", &mac[0], &mac[1], + &mac[2], &mac[3], &mac[4], &mac[5]); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ip_neighbor_add_del_0607c257"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = add; + memcpy (mp->neighbor.ip_address.un.ip4, (u8 *) &addr, sizeof (addr)); + mp->neighbor.ip_address.af = 0; + memcpy (mp->neighbor.mac_address, mac, 6); + mp->neighbor.sw_if_index = htonl (sw_if_index); + mp->neighbor.flags = 1; + + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s neighbor entry", + add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s neighbor add rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + fclose (fp); + return rc; + } + } + return rc; + +error: + free (out); + vl_msg_api_free (mp); + return rc; +} + +static int +add_Route (char *ipAddr, int len, char *mask, char *gateWay) +{ + int fd; + int rc = SUCCESS; + struct sockaddr_in _sin; + struct sockaddr_in *sin = &_sin; + struct rtentry rt; + + do + { + fd = socket (AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + DBG2 (DBG_KNL, "addRoute: socket error\n"); + rc = FAILED; + break; + } + memset (&rt, 0, sizeof (struct rtentry)); + memset (sin, 0, sizeof (struct sockaddr_in)); + sin->sin_family = AF_INET; + sin->sin_port = 0; + + if (inet_aton (gateWay, &sin->sin_addr) < 0) + { + rc = FAILED; + break; + } + memcpy (&rt.rt_gateway, sin, sizeof (struct sockaddr_in)); + + ((struct sockaddr_in *) &rt.rt_dst)->sin_family = AF_INET; + memcpy (&((struct sockaddr_in *) &rt.rt_dst)->sin_addr, ipAddr, len); + + ((struct sockaddr_in *) &rt.rt_genmask)->sin_family = AF_INET; + if (inet_aton (mask, + &((struct sockaddr_in *) &rt.rt_genmask)->sin_addr) < 0) + { + rc = FAILED; + break; + } + rt.rt_flags = RTF_GATEWAY; + if (ioctl (fd, SIOCADDRT, &rt) < 0) + { + rc = FAILED; + } + } + while (0); + + close (fd); + return rc; +} + +static int +set_address (u32 ipAddr, u32 sw_if_index, bool add) +{ + char *out = NULL; + int out_len = 0; + vl_api_sw_interface_add_del_address_t *mp; + vl_api_sw_interface_add_del_address_reply_t *rmp; + + int rc = SUCCESS; + + uint32_t addr; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "sw_interface_add_del_address_5463d73b"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = add; + memcpy (mp->prefix.address.un.ip4, (u8 *) &ipAddr, sizeof (ipAddr)); + mp->prefix.len = 24; + mp->sw_if_index = sw_if_index; + + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG2 (DBG_KNL, "vac %s neighbor entry", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG2 (DBG_KNL, "%s neighbor add rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + return rc; + +error: + free (out); + vl_msg_api_free (mp); + return rc; +} + +/** + * (Un)-install a single route + */ +static void +manage_route (private_kernel_vpp_ipsec_t *this, bool add, + traffic_selector_t *dst_ts, host_t *src, host_t *dst) +{ + host_t *dst_net, *gateway; + uint8_t prefixlen; + char *if_name; + route_entry_t *route; + bool route_exist = FALSE; + + char *netmask = "255.255.255.0"; + char *tap_gateway = "1.1.1.1"; + int arp_rc = 0; + if (dst->is_anyaddr (dst)) + { + return; + } + gateway = + charon->kernel->get_nexthop (charon->kernel, dst, -1, NULL, &if_name); + dst_ts->to_subnet (dst_ts, &dst_net, &prefixlen); + if (!if_name) + { + if (src->is_anyaddr (src)) + { + return; + } + if (!charon->kernel->get_interface (charon->kernel, src, &if_name)) + { + return; + } + } + route_exist = + this->routes->find_first (this->routes, route_equals, (void **) &route, + if_name, gateway, dst_net, &prefixlen); + if (add) + { + DBG2 (DBG_KNL, "installing route: %H/%d via %H dev %s", dst_net, + prefixlen, gateway, if_name); + if (route_exist) + { + unsigned int refs_num = ref_get (&route->refs); + DBG2 (DBG_KNL, "add route but it exist %d", refs_num); + } + else + { + INIT (route, .if_name = strdup (if_name), + .gateway = gateway->clone (gateway), + .dst_net = dst_net->clone (dst_net), .prefixlen = prefixlen, + .refs = 1, ); + this->routes->insert_last (this->routes, route); + charon->kernel->add_route (charon->kernel, + dst_net->get_address (dst_net), prefixlen, + gateway, dst, if_name, 1); + } + + add_Route (dst_net->get_address (dst_net).ptr, + dst_net->get_address (dst_net).len, netmask, tap_gateway); + + arp_rc = set_arp (gateway->get_address (gateway).ptr, if_name, TRUE); + if (arp_rc) + DBG2 (DBG_KNL, "arpGet success!\n"); + } + else + { + DBG2 (DBG_KNL, "uninstalling route: %H/%d via %H dev %s", dst_net, + prefixlen, gateway, if_name); + if (!route_exist) + { + DBG2 (DBG_KNL, "del route but it not exist"); + return; + } + if (ref_put (&route->refs)) + { + this->routes->remove (this->routes, route, NULL); + route_destroy (route); + charon->kernel->del_route (charon->kernel, + dst_net->get_address (dst_net), prefixlen, + gateway, dst, if_name, 1); + } + } +} + +/** + * Hash function for IPsec SA + */ +static u_int +sa_hash (kernel_ipsec_sa_id_t *sa) +{ + return chunk_hash_inc ( + sa->src->get_address (sa->src), + chunk_hash_inc ( + sa->dst->get_address (sa->dst), + chunk_hash_inc (chunk_from_thing (sa->spi), + chunk_hash (chunk_from_thing (sa->proto))))); +} + +/** + * Equality function for IPsec SA + */ +static bool +sa_equals (kernel_ipsec_sa_id_t *sa, kernel_ipsec_sa_id_t *other_sa) +{ + return sa->src->ip_equals (sa->src, other_sa->src) && + sa->dst->ip_equals (sa->dst, other_sa->dst) && + sa->spi == other_sa->spi && sa->proto == other_sa->proto; +} + +/** + * Hash function for interface + */ +static u_int +interface_hash (char *interface) +{ + return chunk_hash (chunk_from_str (interface)); +} + +/** + * Equality function for interface + */ +static bool +interface_equals (char *interface1, char *interface2) +{ + return streq (interface1, interface2); +} + +/** + * Map an integer x with a one-to-one function using quadratic residues + */ +static u_int +permute (u_int x, u_int p) +{ + u_int qr; + + x = x % p; + qr = ((uint64_t) x * x) % p; + if (x <= p / 2) + { + return qr; + } + return p - qr; +} + +/** + * Initialize seeds for SPI generation + */ +static bool +init_spi (private_kernel_vpp_ipsec_t *this) +{ + bool ok = TRUE; + rng_t *rng; + + rng = lib->crypto->create_rng (lib->crypto, RNG_STRONG); + if (!rng) + { + return FALSE; + } + ok = + rng->get_bytes (rng, sizeof (this->nextspi), (uint8_t *) &this->nextspi); + if (ok) + { + ok = + rng->get_bytes (rng, sizeof (this->mixspi), (uint8_t *) &this->mixspi); + } + rng->destroy (rng); + return ok; +} + +/** + * Calculate policy priority + */ +static uint32_t +calculate_priority (policy_priority_t policy_priority, traffic_selector_t *src, + traffic_selector_t *dst) +{ + uint32_t priority = PRIO_BASE; + uint16_t port; + uint8_t mask, proto; + host_t *net; + + switch (policy_priority) + { + case POLICY_PRIORITY_FALLBACK: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_ROUTED: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_DEFAULT: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_PASS: + break; + } + /* calculate priority based on selector size, small size = high prio */ + src->to_subnet (src, &net, &mask); + priority -= mask; + proto = src->get_protocol (src); + port = net->get_port (net); + net->destroy (net); + + dst->to_subnet (dst, &net, &mask); + priority -= mask; + proto = max (proto, dst->get_protocol (dst)); + port = max (port, net->get_port (net)); + net->destroy (net); + + priority <<= 2; /* make some room for the two flags */ + priority += port ? 0 : 2; + priority += proto ? 0 : 1; + return priority; +} + +/** + * Get sw_if_index from interface name + */ +static uint32_t +get_sw_if_index (char *interface) +{ + char *out = NULL; + int out_len, name_filter_len = 0, msg_len = 0; + vl_api_sw_interface_dump_t *mp; + vl_api_sw_interface_details_t *rmp; + uint32_t sw_if_index = ~0; + + name_filter_len = strlen (interface); + msg_len = sizeof (*mp) + name_filter_len; + mp = vl_msg_api_alloc (msg_len); + clib_memset (mp, 0, msg_len); + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "sw_interface_dump_aa610c27"); + mp->_vl_msg_id = htons (msg_id); + mp->name_filter_valid = TRUE; + mp->name_filter.length = htonl (name_filter_len); + memcpy ((char *) mp->name_filter.buf, interface, name_filter_len); + + if (vac->send (vac, (char *) mp, msg_len, &out, &out_len)) + { + goto error; + } + if (!out_len) + { + goto error; + } + rmp = (vl_api_sw_interface_details_t *) out; + sw_if_index = ntohl (rmp->sw_if_index); + +error: + free (out); + vl_msg_api_free (mp); + return sw_if_index; +} +/** + * (Un)-install a security policy database + */ +static status_t +spd_add_del (bool add, uint32_t spd_id) +{ + char *out = NULL; + int out_len; + vl_api_ipsec_spd_add_del_t *mp; + vl_api_ipsec_spd_add_del_reply_t *rmp; + status_t rv = FAILED; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ipsec_spd_add_del_20e89a95"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = add; + mp->spd_id = htonl (spd_id); + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + rv = SUCCESS; + +error: + free (out); + vl_msg_api_free (mp); + return rv; +} + +/** + * Enable or disable SPD on an insterface + */ +static status_t +interface_add_del_spd (bool add, uint32_t spd_id, uint32_t sw_if_index) +{ + char *out = NULL; + int out_len; + vl_api_ipsec_interface_add_del_spd_t *mp; + vl_api_ipsec_interface_add_del_spd_reply_t *rmp; + status_t rv = FAILED; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_interface_add_del_spd_80f80cbb"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = add; + mp->spd_id = htonl (spd_id); + mp->sw_if_index = htonl (sw_if_index); + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s interface SPD failed", + add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s interface SPD failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + rv = SUCCESS; + +error: + free (out); + vl_msg_api_free (mp); + return rv; +} + +static int +bypass_all (bool add, uint32_t spd_id, uint32_t sa_id) +{ + vl_api_ipsec_spd_entry_add_del_t *mp; + vl_api_ipsec_spd_entry_add_del_reply_t *rmp; + char *out = NULL; + int out_len; + status_t rv = FAILED; + + DBG2 (DBG_KNL, "bypass_all [%s] spd_id %d sa_id %d", add ? "ADD" : "DEL", + spd_id, sa_id); + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_spd_entry_add_del_338b7411"); + mp->_vl_msg_id = ntohs (msg_id); + mp->is_add = add; + mp->entry.sa_id = ntohl (sa_id); + mp->entry.spd_id = ntohl (spd_id); + mp->entry.priority = ntohl (INT_MAX - POLICY_PRIORITY_PASS - 1); + mp->entry.is_outbound = 0; + mp->entry.policy = ntohl (IPSEC_API_SPD_ACTION_BYPASS); + memset (mp->entry.local_address_stop.un.ip6, 0xFF, 16); + memset (mp->entry.remote_address_stop.un.ip6, 0xFF, 16); + mp->entry.remote_port_start = mp->entry.local_port_start = ntohs (0); + mp->entry.remote_port_stop = mp->entry.local_port_stop = ntohs (0xFFFF); + mp->entry.protocol = IP_API_PROTO_ESP; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + mp->entry.is_outbound = 1; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + mp->entry.is_outbound = 0; + mp->entry.protocol = IP_API_PROTO_AH; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + mp->entry.is_outbound = 1; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + + rv = SUCCESS; + +error: + if (out) + free (out); + vl_msg_api_free (mp); + + return rv; +} + +static int +bypass_port (bool add, uint32_t spd_id, uint32_t sa_id, uint16_t port) +{ + vl_api_ipsec_spd_entry_add_del_t *mp; + vl_api_ipsec_spd_entry_add_del_reply_t *rmp; + char *out = NULL; + int out_len; + status_t rv = FAILED; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_spd_entry_add_del_338b7411"); + mp->_vl_msg_id = ntohs (msg_id); + mp->is_add = add; + mp->entry.sa_id = ntohl (sa_id); + mp->entry.spd_id = ntohl (spd_id); + mp->entry.priority = ntohl (INT_MAX - POLICY_PRIORITY_PASS - 1); + mp->entry.policy = ntohl (IPSEC_API_SPD_ACTION_BYPASS); + memset (mp->entry.local_address_stop.un.ip6, 0xFF, 16); + memset (mp->entry.remote_address_stop.un.ip6, 0xFF, 16); + mp->entry.is_outbound = 0; + mp->entry.remote_port_start = mp->entry.local_port_start = ntohs (0); + mp->entry.remote_port_stop = mp->entry.local_port_stop = ntohs (0xFFFF); + mp->entry.protocol = IP_API_PROTO_HOPOPT; + + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + mp->entry.is_outbound = 1; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + rv = SUCCESS; + +error: + if (out) + free (out); + vl_msg_api_free (mp); + + return rv; +} + +/** + * Add or remove a bypass policy + */ +static status_t +manage_bypass (bool add, uint32_t spd_id, uint32_t sa_id) +{ + uint16_t port; + status_t rv; + + bypass_all (add, spd_id, sa_id); + + port = + lib->settings->get_int (lib->settings, "%s.port", IKEV2_UDP_PORT, lib->ns); + + if (port) + { + rv = bypass_port (add, spd_id, sa_id, port); + if (rv != SUCCESS) + { + return rv; + } + } + + port = lib->settings->get_int (lib->settings, "%s.port_nat_t", + IKEV2_NATT_PORT, lib->ns); + if (port) + { + rv = bypass_port (add, spd_id, sa_id, port); + if (rv != SUCCESS) + { + return rv; + } + } + + return SUCCESS; +} + +/** + * Add or remove a policy + */ +static status_t +manage_policy (private_kernel_vpp_ipsec_t *this, bool add, + kernel_ipsec_policy_id_t *id, + kernel_ipsec_manage_policy_t *data) +{ + spd_t *spd; + char *out = NULL, *interface = NULL; + int out_len; + uint32_t sw_if_index, spd_id = ~0, sad_id = ~0; + status_t rv = FAILED; + uint32_t priority, auto_priority; + chunk_t src_from, src_to, dst_from, dst_to; + host_t *src, *dst, *addr; + vl_api_ipsec_spd_entry_add_del_t *mp; + vl_api_ipsec_spd_entry_add_del_reply_t *rmp; + bool n_spd = false; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + + this->mutex->lock (this->mutex); + if (!id->interface) + { + addr = id->dir == POLICY_IN ? data->dst : data->src; + for (int i = 0; i < 5; i++) + { + if (!charon->kernel->get_interface (charon->kernel, addr, + &interface)) + { + DBG1 (DBG_KNL, "policy no interface %H", addr); + interface = NULL; + sleep (1); + } + + if (interface) + { + DBG1 (DBG_KNL, "policy have interface %H", addr); + break; + } + } + if (!interface) + goto error; + id->interface = interface; + } + + DBG2 (DBG_KNL, "manage policy [%s] interface [%s]", add ? "ADD" : "DEL", + id->interface); + + spd = this->spds->get (this->spds, id->interface); + if (!spd) + { + if (!add) + { + DBG1 (DBG_KNL, "SPD for %s not found, should not be deleted", + id->interface); + goto error; + } + sw_if_index = get_sw_if_index (id->interface); + DBG1 (DBG_KNL, "firstly created, spd for %s found sw_if_index is %d", + id->interface, sw_if_index); + if (sw_if_index == ~0) + { + DBG1 (DBG_KNL, "sw_if_index for %s not found", id->interface); + goto error; + } + spd_id = ref_get (&this->next_spd_id); + if (spd_add_del (TRUE, spd_id)) + { + DBG1 (DBG_KNL, "spd_add_del %d failed!!!!!", spd_id); + goto error; + } + if (interface_add_del_spd (TRUE, spd_id, sw_if_index)) + { + DBG1 (DBG_KNL, "interface_add_del_spd %d %d failed!!!!!", spd_id, + sw_if_index); + goto error; + } + INIT (spd, .spd_id = spd_id, .sw_if_index = sw_if_index, + .policy_num = 0, ); + this->spds->put (this->spds, id->interface, spd); + n_spd = true; + } + + auto_priority = calculate_priority (data->prio, id->src_ts, id->dst_ts); + priority = data->manual_prio ? data->manual_prio : auto_priority; + + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_spd_entry_add_del_338b7411"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = add; + mp->entry.spd_id = htonl (spd->spd_id); + mp->entry.priority = htonl (INT_MAX - POLICY_PRIORITY_PASS); + mp->entry.is_outbound = id->dir == POLICY_OUT; + switch (data->type) + { + case POLICY_IPSEC: + mp->entry.policy = htonl (IPSEC_API_SPD_ACTION_PROTECT); + break; + case POLICY_PASS: + mp->entry.policy = htonl (IPSEC_API_SPD_ACTION_BYPASS); + break; + case POLICY_DROP: + mp->entry.policy = htonl (IPSEC_API_SPD_ACTION_DISCARD); + break; + } + if ((data->type == POLICY_IPSEC) && data->sa) + { + kernel_ipsec_sa_id_t id = { + .src = data->src, + .dst = data->dst, + .proto = data->sa->esp.use ? IPPROTO_ESP : IPPROTO_AH, + .spi = data->sa->esp.use ? data->sa->esp.spi : data->sa->ah.spi, + }; + sa_t *sa = NULL; + sa = this->sas->get (this->sas, &id); + if (!sa) + { + DBG1 (DBG_KNL, "SA ID not found"); + goto error; + } + sad_id = sa->sa_id; + if (n_spd) + { + if (manage_bypass (TRUE, spd_id, ~0)) + { + DBG1 (DBG_KNL, "manage_bypass %d failed!!!!", spd_id); + goto error; + } + } + } + + mp->entry.sa_id = htonl (sad_id); + + bool is_ipv6 = false; + if (id->src_ts->get_type (id->src_ts) == TS_IPV6_ADDR_RANGE) + { + is_ipv6 = true; + mp->entry.local_address_start.af = htonl (ADDRESS_IP6); + mp->entry.local_address_stop.af = htonl (ADDRESS_IP6); + mp->entry.remote_address_start.af = htonl (ADDRESS_IP6); + mp->entry.remote_address_stop.af = htonl (ADDRESS_IP6); + } + else + { + mp->entry.local_address_start.af = htonl (ADDRESS_IP4); + mp->entry.local_address_stop.af = htonl (ADDRESS_IP4); + mp->entry.remote_address_start.af = htonl (ADDRESS_IP4); + mp->entry.remote_address_stop.af = htonl (ADDRESS_IP4); + } + mp->entry.protocol = id->src_ts->get_protocol (id->src_ts); + + if (id->dir == POLICY_OUT) + { + src_from = id->src_ts->get_from_address (id->src_ts); + src_to = id->src_ts->get_to_address (id->src_ts); + src = host_create_from_chunk (is_ipv6 ? AF_INET6 : AF_INET, src_to, 0); + dst_from = id->dst_ts->get_from_address (id->dst_ts); + dst_to = id->dst_ts->get_to_address (id->dst_ts); + dst = host_create_from_chunk (is_ipv6 ? AF_INET6 : AF_INET, dst_to, 0); + } + else + { + dst_from = id->src_ts->get_from_address (id->src_ts); + dst_to = id->src_ts->get_to_address (id->src_ts); + dst = host_create_from_chunk (is_ipv6 ? AF_INET6 : AF_INET, dst_from, 0); + src_from = id->dst_ts->get_from_address (id->dst_ts); + src_to = id->dst_ts->get_to_address (id->dst_ts); + src = host_create_from_chunk (is_ipv6 ? AF_INET6 : AF_INET, src_from, 0); + } + + if (src->is_anyaddr (src) && dst->is_anyaddr (dst)) + { + memset (mp->entry.local_address_stop.un.ip6, 0xFF, 16); + memset (mp->entry.remote_address_stop.un.ip6, 0xFF, 16); + } + else + { + memcpy (is_ipv6 ? mp->entry.local_address_start.un.ip6 : + mp->entry.local_address_start.un.ip4, + src_from.ptr, src_from.len); + memcpy (is_ipv6 ? mp->entry.local_address_stop.un.ip6 : + mp->entry.local_address_stop.un.ip4, + src_to.ptr, src_to.len); + memcpy (is_ipv6 ? mp->entry.remote_address_start.un.ip6 : + mp->entry.remote_address_start.un.ip4, + dst_from.ptr, dst_from.len); + memcpy (is_ipv6 ? mp->entry.remote_address_stop.un.ip6 : + mp->entry.remote_address_stop.un.ip4, + dst_to.ptr, dst_to.len); + } + mp->entry.local_port_start = htons (id->src_ts->get_from_port (id->src_ts)); + mp->entry.local_port_stop = htons (id->src_ts->get_to_port (id->src_ts)); + mp->entry.remote_port_start = htons (id->dst_ts->get_from_port (id->dst_ts)); + mp->entry.remote_port_stop = htons (id->dst_ts->get_to_port (id->dst_ts)); + + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac %s SPD entry failed", add ? "adding" : "removing"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s SPD entry failed rv:%d", add ? "add" : "remove", + ntohl (rmp->retval)); + goto error; + } + + if (add) + { + ref_get (&spd->policy_num); + } + else + { + if (ref_put (&spd->policy_num)) + { + DBG1 ( + DBG_KNL, + "policy_num's ref is 0, delete spd_id %d sw_if_index %d sad_id %x", + spd->spd_id, spd->sw_if_index, sad_id); + interface_add_del_spd (FALSE, spd->spd_id, spd->sw_if_index); + manage_bypass (FALSE, spd->spd_id, sad_id); + spd_add_del (FALSE, spd->spd_id); + this->spds->remove (this->spds, id->interface); + } + } + + if (this->install_routes && id->dir == POLICY_OUT && !mp->entry.protocol) + { + if (data->type == POLICY_IPSEC && data->sa->mode != MODE_TRANSPORT) + { + manage_route (this, add, id->dst_ts, data->src, data->dst); + } + } + rv = SUCCESS; +error: + free (out); + vl_msg_api_free (mp); + this->mutex->unlock (this->mutex); + return rv; +} + +METHOD (kernel_ipsec_t, get_features, kernel_feature_t, + private_kernel_vpp_ipsec_t *this) +{ + return KERNEL_ESP_V3_TFC; +} + +METHOD (kernel_ipsec_t, get_spi, status_t, private_kernel_vpp_ipsec_t *this, + host_t *src, host_t *dst, uint8_t protocol, uint32_t *spi) +{ + static const u_int p = 268435399, offset = 0xc0000000; + + *spi = htonl (offset + permute (ref_get (&this->nextspi) ^ this->mixspi, p)); + return SUCCESS; +} + +METHOD (kernel_ipsec_t, get_cpi, status_t, private_kernel_vpp_ipsec_t *this, + host_t *src, host_t *dst, uint16_t *cpi) +{ + DBG1 (DBG_KNL, "get_cpi is not supported!!!!!!!!!!!!!!!!!!!!!!!!"); + return NOT_SUPPORTED; +} + +/** + * Helper struct for expiration events + */ +typedef struct +{ + + private_kernel_vpp_ipsec_t *manager; + + kernel_ipsec_sa_id_t *sa_id; + + /** + * 0 if this is a hard expire, otherwise the offset in s (soft->hard) + */ + uint32_t hard_offset; + +} vpp_sa_expired_t; + +/** + * Callback for expiration events + */ +static job_requeue_t +sa_expired (vpp_sa_expired_t *expired) +{ + private_kernel_vpp_ipsec_t *this = expired->manager; + sa_t *sa; + kernel_ipsec_sa_id_t *id = expired->sa_id; + + this->mutex->lock (this->mutex); + sa = this->sas->get (this->sas, id); + + if (sa) + { + charon->kernel->expire (charon->kernel, id->proto, id->spi, id->dst, + FALSE); + } + + free (id); + this->mutex->unlock (this->mutex); + return JOB_REQUEUE_NONE; +} + +/** + * Schedule a job to handle IPsec SA expiration + */ +static void +schedule_expiration (private_kernel_vpp_ipsec_t *this, + kernel_ipsec_add_sa_t *entry, + kernel_ipsec_sa_id_t *entry2) +{ + lifetime_cfg_t *lifetime = entry->lifetime; + vpp_sa_expired_t *expired; + callback_job_t *job; + uint32_t timeout; + kernel_ipsec_sa_id_t *id; + + if (!lifetime->time.life) + { /* no expiration at all */ + return; + } + + INIT (id, .src = entry2->src->clone (entry2->src), + .dst = entry2->dst->clone (entry2->dst), .spi = entry2->spi, + .proto = entry2->proto, ); + + INIT (expired, .manager = this, .sa_id = id, ); + + /* schedule a rekey first, a hard timeout will be scheduled then, if any */ + expired->hard_offset = lifetime->time.life - lifetime->time.rekey; + timeout = lifetime->time.rekey; + + if (lifetime->time.life <= lifetime->time.rekey || lifetime->time.rekey == 0) + { /* no rekey, schedule hard timeout */ + expired->hard_offset = 0; + timeout = lifetime->time.life; + } + + job = callback_job_create ((callback_job_cb_t) sa_expired, expired, + (callback_job_cleanup_t) free, NULL); + lib->scheduler->schedule_job (lib->scheduler, (job_t *) job, timeout); +} + +METHOD (kernel_ipsec_t, add_sa, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_sa_id_t *id, kernel_ipsec_add_sa_t *data) +{ + char *out = NULL; + int out_len; + vl_api_ipsec_sad_entry_add_del_t *mp; + vl_api_ipsec_sad_entry_add_del_reply_t *rmp; + uint32_t sad_id = ref_get (&this->next_sad_id); + uint8_t ca = 0, ia = 0; + status_t rv = FAILED; + chunk_t src, dst; + kernel_ipsec_sa_id_t *sa_id; + sa_t *sa; + int key_len = data->enc_key.len; + + if ((data->enc_alg == ENCR_AES_CTR) || + (data->enc_alg == ENCR_AES_GCM_ICV8) || + (data->enc_alg == ENCR_AES_GCM_ICV12) || + (data->enc_alg == ENCR_AES_GCM_ICV16)) + { + static const int SALT_SIZE = + 4; /* See how enc_size is calculated at keymat_v2.derive_child_keys */ + key_len = key_len - SALT_SIZE; + } + natt_port = lib->settings->get_int ( + lib->settings, "%s.plugins.socket-default.natt", IKEV2_NATT_PORT, lib->ns); + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_sad_entry_add_del_ab64b5c6"); + mp->_vl_msg_id = htons (msg_id); + mp->is_add = 1; + mp->entry.sad_id = htonl (sad_id); + mp->entry.spi = id->spi; + mp->entry.protocol = id->proto == IPPROTO_ESP ? htonl (IPSEC_API_PROTO_ESP) : + htonl (IPSEC_API_PROTO_AH); + + switch (data->enc_alg) + { + case ENCR_NULL: + ca = IPSEC_API_CRYPTO_ALG_NONE; + break; + case ENCR_AES_CBC: + switch (key_len * 8) + { + case 128: + ca = IPSEC_API_CRYPTO_ALG_AES_CBC_128; + break; + case 192: + ca = IPSEC_API_CRYPTO_ALG_AES_CBC_192; + break; + case 256: + ca = IPSEC_API_CRYPTO_ALG_AES_CBC_256; + break; + default: + DBG1 (DBG_KNL, "Key length %d is not supported by VPP!", + key_len * 8); + goto error; + } + break; + case ENCR_AES_CTR: + switch (key_len * 8) + { + case 128: + ca = IPSEC_API_CRYPTO_ALG_AES_CTR_128; + break; + case 192: + ca = IPSEC_API_CRYPTO_ALG_AES_CTR_192; + break; + case 256: + ca = IPSEC_API_CRYPTO_ALG_AES_CTR_256; + break; + default: + DBG1 (DBG_KNL, "Key length %d is not supported by VPP!", + key_len * 8); + goto error; + } + break; + case ENCR_AES_GCM_ICV8: + case ENCR_AES_GCM_ICV12: + case ENCR_AES_GCM_ICV16: + switch (key_len * 8) + { + case 128: + ca = IPSEC_API_CRYPTO_ALG_AES_GCM_128; + break; + case 192: + ca = IPSEC_API_CRYPTO_ALG_AES_GCM_192; + break; + case 256: + ca = IPSEC_API_CRYPTO_ALG_AES_GCM_256; + break; + default: + DBG1 (DBG_KNL, "Key length %d is not supported by VPP!", + key_len * 8); + goto error; + } + break; + case ENCR_DES: + ca = IPSEC_API_CRYPTO_ALG_DES_CBC; + break; + case ENCR_3DES: + ca = IPSEC_API_CRYPTO_ALG_3DES_CBC; + break; + default: + DBG1 (DBG_KNL, "algorithm %N not supported by VPP!", + encryption_algorithm_names, data->enc_alg); + goto error; + } + mp->entry.crypto_algorithm = htonl (ca); + mp->entry.crypto_key.length = key_len < 128 ? key_len : 128; + memcpy (mp->entry.crypto_key.data, data->enc_key.ptr, + mp->entry.crypto_key.length); + + /* copy salt for AEAD algorithms */ + if ((data->enc_alg == ENCR_AES_CTR) || + (data->enc_alg == ENCR_AES_GCM_ICV8) || + (data->enc_alg == ENCR_AES_GCM_ICV12) || + (data->enc_alg == ENCR_AES_GCM_ICV16)) + { + memcpy (&mp->entry.salt, data->enc_key.ptr + mp->entry.crypto_key.length, + 4); + } + + switch (data->int_alg) + { + case AUTH_UNDEFINED: + ia = IPSEC_API_INTEG_ALG_NONE; + break; + case AUTH_HMAC_MD5_96: + ia = IPSEC_API_INTEG_ALG_MD5_96; + break; + case AUTH_HMAC_SHA1_96: + ia = IPSEC_API_INTEG_ALG_SHA1_96; + break; + case AUTH_HMAC_SHA2_256_96: + ia = IPSEC_API_INTEG_ALG_SHA_256_96; + break; + case AUTH_HMAC_SHA2_256_128: + ia = IPSEC_API_INTEG_ALG_SHA_256_128; + break; + case AUTH_HMAC_SHA2_384_192: + ia = IPSEC_API_INTEG_ALG_SHA_384_192; + break; + case AUTH_HMAC_SHA2_512_256: + ia = IPSEC_API_INTEG_ALG_SHA_512_256; + break; + default: + DBG1 (DBG_KNL, "algorithm %N not supported by VPP!", + integrity_algorithm_names, data->int_alg); + goto error; + break; + } + mp->entry.integrity_algorithm = htonl (ia); + mp->entry.integrity_key.length = + data->int_key.len < 128 ? data->int_key.len : 128; + memcpy (mp->entry.integrity_key.data, data->int_key.ptr, + mp->entry.integrity_key.length); + + int flags = IPSEC_API_SAD_FLAG_NONE; + if (data->esn) + flags |= IPSEC_API_SAD_FLAG_USE_ESN; + if (data->mode == MODE_TUNNEL) + { + if (id->src->get_family (id->src) == AF_INET6) + flags |= IPSEC_API_SAD_FLAG_IS_TUNNEL_V6; + else + flags |= IPSEC_API_SAD_FLAG_IS_TUNNEL; + } + if (data->encap) + { + DBG1 (DBG_KNL, "UDP encap!!!!!!!!!!!!!!!!!!!!"); + flags |= IPSEC_API_SAD_FLAG_UDP_ENCAP; + } + mp->entry.flags = htonl (flags); + + bool is_ipv6 = false; + if (id->src->get_family (id->src) == AF_INET6) + { + is_ipv6 = true; + mp->entry.tunnel_src.af = htonl (ADDRESS_IP6); + mp->entry.tunnel_dst.af = htonl (ADDRESS_IP6); + } + else + { + mp->entry.tunnel_src.af = htonl (ADDRESS_IP4); + mp->entry.tunnel_dst.af = htonl (ADDRESS_IP4); + } + src = id->src->get_address (id->src); + memcpy (is_ipv6 ? mp->entry.tunnel_src.un.ip6 : mp->entry.tunnel_src.un.ip4, + src.ptr, src.len); + dst = id->dst->get_address (id->dst); + memcpy (is_ipv6 ? mp->entry.tunnel_dst.un.ip6 : mp->entry.tunnel_dst.un.ip4, + dst.ptr, dst.len); + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac adding SA failed"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "add SA failed rv:%d", ntohl (rmp->retval)); + goto error; + } + + this->mutex->lock (this->mutex); + INIT (sa_id, .src = id->src->clone (id->src), + .dst = id->dst->clone (id->dst), .spi = id->spi, .proto = id->proto, ); + INIT (sa, .sa_id = sad_id, .stat_index = ntohl (rmp->stat_index), ); + DBG4 (DBG_KNL, "put sa by its sa_id %x !!!!!!", sad_id); + this->sas->put (this->sas, sa_id, sa); + schedule_expiration (this, data, id); + this->mutex->unlock (this->mutex); + rv = SUCCESS; + +error: + free (out); + vl_msg_api_free (mp); + return rv; +} + +METHOD (kernel_ipsec_t, update_sa, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_sa_id_t *id, kernel_ipsec_update_sa_t *data) +{ + DBG1 (DBG_KNL, + "update sa not supported!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return NOT_SUPPORTED; +} + +METHOD (kernel_ipsec_t, query_sa, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_sa_id_t *id, kernel_ipsec_query_sa_t *data, + uint64_t *bytes, uint64_t *packets, time_t *time) +{ + status_t rv = FAILED; + sa_t *sa; + u32 *dir; + int i, k; + stat_segment_data_t *res; + u8 **pattern = 0; + uint64_t res_bytes = 0; + uint64_t res_packets = 0; + + this->mutex->lock (this->mutex); + sa = this->sas->get (this->sas, id); + this->mutex->unlock (this->mutex); + if (!sa) + { + DBG1 (DBG_KNL, "SA not found"); + return NOT_FOUND; + } + + int rv_stat = stat_segment_connect ("/run/vpp/stats.sock"); + if (rv_stat != 0) + { + DBG1 (DBG_KNL, "Not connecting with stats segmentation"); + return NOT_FOUND; + } + vec_add1 (pattern, (u8 *) "/net/ipsec/sa"); + dir = stat_segment_ls ((u8 **) pattern); + res = stat_segment_dump (dir); + /* i-loop for each results find by pattern - here two: + * 1. /net/ipsec/sa + * 2. /net/ipsec/sa/lost + */ + for (i = 0; i < vec_len (res); i++) + { + switch (res[i].type) + { + /* type for how many packets are lost */ + case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE: + if (res[i].simple_counter_vec == 0) + continue; + break; + /* type for counter for each SA */ + case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: + if (res[i].combined_counter_vec == 0) + continue; + /* k-loop for each threads - that you run VPP */ + for (k = 0; k < vec_len (res[i].combined_counter_vec); k++) + { + if (sa->stat_index <= vec_len (res[i].combined_counter_vec[k])) + { + DBG4 (DBG_KNL, "Thread: %d, Packets: %lu, Bytes: %lu", k, + res[i].combined_counter_vec[k][sa->stat_index].packets, + res[i].combined_counter_vec[k][sa->stat_index].bytes); + res_bytes += + res[i].combined_counter_vec[k][sa->stat_index].bytes; + res_packets += + res[i].combined_counter_vec[k][sa->stat_index].packets; + } + } + break; + case STAT_DIR_TYPE_NAME_VECTOR: + if (res[i].name_vector == 0) + continue; + break; + } + } + stat_segment_data_free (res); + stat_segment_disconnect (); + + vec_free (pattern); + vec_free (dir); + + if (bytes) + { + *bytes = res_bytes; + } + if (packets) + { + *packets = res_packets; + } + if (time) + { + *time = 0; + } + + rv = SUCCESS; + return rv; +} + +METHOD (kernel_ipsec_t, del_sa, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_sa_id_t *id, kernel_ipsec_del_sa_t *data) +{ + char *out = NULL; + int out_len; + vl_api_ipsec_sad_entry_add_del_t *mp; + vl_api_ipsec_sad_entry_add_del_reply_t *rmp; + status_t rv = FAILED; + sa_t *sa; + + this->mutex->lock (this->mutex); + sa = this->sas->get (this->sas, id); + if (!sa) + { + DBG1 (DBG_KNL, "SA not found"); + rv = NOT_FOUND; + goto error; + } + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + mp->is_add = 0; + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_sad_entry_add_del_ab64b5c6"); + mp->_vl_msg_id = htons (msg_id); + mp->entry.sad_id = htonl (sa->sa_id); + + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "vac removing SA failed"); + goto error; + } + rmp = (void *) out; + if (rmp->retval) + { + DBG1 (DBG_KNL, "del SA failed rv:%d", ntohl (rmp->retval)); + goto error; + } + + vl_msg_api_free (mp); + this->sas->remove (this->sas, id); + rv = SUCCESS; +error: + free (out); + this->mutex->unlock (this->mutex); + return rv; +} + +METHOD (kernel_ipsec_t, flush_sas, status_t, private_kernel_vpp_ipsec_t *this) +{ + enumerator_t *enumerator; + int out_len; + char *out; + vl_api_ipsec_sad_entry_add_del_t *mp; + sa_t *sa = NULL; + + this->mutex->lock (this->mutex); + enumerator = this->sas->create_enumerator (this->sas); + while (enumerator->enumerate (enumerator, sa, NULL)) + { + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "ipsec_sad_entry_add_del_ab64b5c6"); + mp->_vl_msg_id = htons (msg_id); + mp->entry.sad_id = htonl (sa->sa_id); + mp->is_add = 0; + if (vac->send (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG1 (DBG_KNL, "flush_sas failed!!!!"); + return FALSE; + } + free (out); + vl_msg_api_free (mp); + this->sas->remove_at (this->sas, enumerator); + } + enumerator->destroy (enumerator); + this->mutex->unlock (this->mutex); + + return SUCCESS; +} + +METHOD (kernel_ipsec_t, add_policy, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_policy_id_t *id, kernel_ipsec_manage_policy_t *data) +{ + return manage_policy (this, TRUE, id, data); +} + +METHOD (kernel_ipsec_t, query_policy, status_t, + private_kernel_vpp_ipsec_t *this, kernel_ipsec_policy_id_t *id, + kernel_ipsec_query_policy_t *data, time_t *use_time) +{ + return NOT_SUPPORTED; +} + +METHOD (kernel_ipsec_t, del_policy, status_t, private_kernel_vpp_ipsec_t *this, + kernel_ipsec_policy_id_t *id, kernel_ipsec_manage_policy_t *data) +{ + return manage_policy (this, FALSE, id, data); +} + +METHOD (kernel_ipsec_t, flush_policies, status_t, + private_kernel_vpp_ipsec_t *this) +{ + return NOT_SUPPORTED; +} + +METHOD (kernel_ipsec_t, bypass_socket, bool, private_kernel_vpp_ipsec_t *this, + int fd, int family) +{ + return FALSE; +} + +METHOD (kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_vpp_ipsec_t *this, int fd, int family, u_int16_t port) +{ + DBG1 (DBG_KNL, "enable_udp_decap not supported!!!!!!!!!!!!!!!!!!!!!!!!!"); + return FALSE; +} + +METHOD (kernel_ipsec_t, destroy, void, private_kernel_vpp_ipsec_t *this) +{ + this->mutex->destroy (this->mutex); + this->sas->destroy (this->sas); + this->spds->destroy (this->spds); + this->routes->destroy (this->routes); + free (this); +} + +kernel_vpp_ipsec_t * +kernel_vpp_ipsec_create () +{ + private_kernel_vpp_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_features = _get_features, + .get_spi = _get_spi, + .get_cpi = _get_cpi, + .add_sa = _add_sa, + .update_sa = _update_sa, + .query_sa = _query_sa, + .del_sa = _del_sa, + .flush_sas = _flush_sas, + .add_policy = _add_policy, + .query_policy = _query_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .bypass_socket = _bypass_socket, + .enable_udp_decap = _enable_udp_decap, + .destroy = _destroy, + }, + }, + .next_sad_id = 0, + .next_spd_id = 0, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .sas = hashtable_create((hashtable_hash_t)sa_hash, + (hashtable_equals_t)sa_equals, 32), + .spds = hashtable_create((hashtable_hash_t)interface_hash, + (hashtable_equals_t)interface_equals, 4), + .routes = linked_list_create(), + .install_routes = lib->settings->get_bool(lib->settings, + "%s.install_routes", TRUE, lib->ns), + ); + + if (!init_spi (this)) + { + destroy (this); + return NULL; + } + + return &this->public; +} diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.h b/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.h new file mode 100644 index 00000000000..64669881574 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_ipsec.h @@ -0,0 +1,41 @@ +#ifndef KERNEL_VPP_IPSEC_H_ +#define KERNEL_VPP_IPSEC_H_ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +typedef struct kernel_vpp_ipsec_t kernel_vpp_ipsec_t; + +/** + * Implementation of the kernel ipsec interface using Netlink. + */ +struct kernel_vpp_ipsec_t +{ + + /** + * Implements kernel_ipsec_t interface + */ + kernel_ipsec_t interface; +}; + +/** + * Create a vpp kernel ipsec interface instance. + * + * @return kernel_vpp_ipsec_t instance + */ +kernel_vpp_ipsec_t *kernel_vpp_ipsec_create (); + +#endif /** KERNEL_VPP_IPSEC_H_ @}*/ diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_net.c b/extras/strongswan/vpp_sswan/kernel_vpp_net.c new file mode 100644 index 00000000000..02f002a3183 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_net.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#define vl_typedefs +#define vl_endianfun +/* Include the (first) vlib-api API definition layer */ +#include +/* Include the current layer (third) vpp API definition layer */ +#include +#include + +#include +#include +#include +#include +#include +#include +#undef vl_typedefs +#undef vl_endianfun + +#include "kernel_vpp_net.h" +#include "kernel_vpp_shared.h" + +typedef struct private_kernel_vpp_net_t private_kernel_vpp_net_t; + +/** + * Private data of kernel_vpp_net implementation. + */ +struct private_kernel_vpp_net_t +{ + + /** + * Public interface. + */ + kernel_vpp_net_t public; + + /** + * Mutex to access interface list + */ + mutex_t *mutex; + + /** + * Known interfaces, as iface_t + */ + linked_list_t *ifaces; + + /** + * Inteface update thread + */ + thread_t *net_update; + + /** + * TRUE if interface events enabled + */ + bool events_on; +}; + +/** + * Interface entry + */ +typedef struct +{ + /** interface index */ + uint32_t index; + /** interface name */ + char if_name[64]; + /** list of known addresses, as host_t */ + linked_list_t *addrs; + /** TRUE if up */ + bool up; +} iface_t; + +/** + * Address enumerator + */ +typedef struct +{ + /** implements enumerator_t */ + enumerator_t public; + /** what kind of address should we enumerate? */ + kernel_address_type_t which; + /** enumerator over interfaces */ + enumerator_t *ifaces; + /** current enumerator over addresses, or NULL */ + enumerator_t *addrs; + /** mutex to unlock on destruction */ + mutex_t *mutex; +} addr_enumerator_t; + +/** + * FIB path entry + */ +typedef struct +{ + chunk_t next_hop; + uint32_t sw_if_index; + uint8_t preference; +} fib_path_t; + +/** + * Get an iface entry for a local address + */ +static iface_t * +address2entry (private_kernel_vpp_net_t *this, host_t *ip) +{ + enumerator_t *ifaces, *addrs; + iface_t *entry, *found = NULL; + host_t *host; + + ifaces = this->ifaces->create_enumerator (this->ifaces); + while (!found && ifaces->enumerate (ifaces, &entry)) + { + addrs = entry->addrs->create_enumerator (entry->addrs); + while (!found && addrs->enumerate (addrs, &host)) + { + if (host->ip_equals (host, ip)) + { + found = entry; + } + } + addrs->destroy (addrs); + } + ifaces->destroy (ifaces); + + return found; +} + +/** + * Add or remove a route + */ +static status_t +manage_route (private_kernel_vpp_net_t *this, bool add, chunk_t dst, + uint8_t prefixlen, host_t *gtw, char *name) +{ + char *out; + int out_len; + enumerator_t *enumerator; + iface_t *entry; + vl_api_ip_route_add_del_t *mp; + vl_api_ip_route_add_del_reply_t *rmp; + vl_api_fib_path_t *apath; + bool exists = FALSE; + + this->mutex->lock (this->mutex); + enumerator = this->ifaces->create_enumerator (this->ifaces); + while (enumerator->enumerate (enumerator, &entry)) + { + if (streq (name, entry->if_name)) + { + exists = TRUE; + break; + } + } + enumerator->destroy (enumerator); + this->mutex->unlock (this->mutex); + + if (!exists) + { + DBG1 (DBG_NET, "if_name %s not found", name); + return NOT_FOUND; + } + + mp = vl_msg_api_alloc (sizeof (*mp) + sizeof (*apath)); + memset (mp, 0, sizeof (*mp) + sizeof (*apath)); + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_route_add_del_b8ecfe0d"); + mp->_vl_msg_id = ntohs (msg_id); + mp->is_add = add; + mp->route.prefix.len = prefixlen; + mp->route.n_paths = 1; + apath = &mp->route.paths[0]; + apath->sw_if_index = ntohl (entry->index); + apath->rpf_id = ~0; + apath->weight = 1; + switch (dst.len) + { + case 4: + mp->route.prefix.address.af = ntohl (ADDRESS_IP4); + memcpy (&mp->route.prefix.address.un.ip4, dst.ptr, dst.len); + if (gtw) + { + chunk_t addr = gtw->get_address (gtw); + apath->proto = ntohl (FIB_API_PATH_NH_PROTO_IP4); + memcpy (&apath->nh.address.ip4, addr.ptr, dst.len); + } + break; + case 16: + mp->route.prefix.address.af = ntohl (ADDRESS_IP6); + memcpy (&mp->route.prefix.address.un.ip6, dst.ptr, dst.len); + if (gtw) + { + chunk_t addr = gtw->get_address (gtw); + apath->proto = ntohl (FIB_API_PATH_NH_PROTO_IP6); + memcpy (&apath->nh.address.ip6, addr.ptr, dst.len); + } + break; + default: + vl_msg_api_free (mp); + return FAILED; + } + + if (vac->send (vac, (char *) mp, sizeof (*mp) + sizeof (*apath), &out, + &out_len)) + { + DBG1 (DBG_KNL, "vac %sing route failed", add ? "add" : "remov"); + vl_msg_api_free (mp); + return FAILED; + } + rmp = (void *) out; + vl_msg_api_free (mp); + if (rmp->retval) + { + DBG1 (DBG_KNL, "%s route failed %d", add ? "add" : "delete", + ntohl (rmp->retval)); + free (out); + return FAILED; + } + free (out); + return SUCCESS; +} + +/** + * Check if an address or net (addr with prefix net bits) is in + * subnet (net with net_len net bits) + */ +static bool +addr_in_subnet (chunk_t addr, int prefix, chunk_t net, int net_len) +{ + static const u_char mask[] = { + 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe + }; + int byte = 0; + + if (net_len == 0) + { /* any address matches a /0 network */ + return TRUE; + } + if (addr.len != net.len || net_len > 8 * net.len || prefix < net_len) + { + return FALSE; + } + /* scan through all bytes in network order */ + while (net_len > 0) + { + if (net_len < 8) + { + return (mask[net_len] & addr.ptr[byte]) == + (mask[net_len] & net.ptr[byte]); + } + else + { + if (addr.ptr[byte] != net.ptr[byte]) + { + return FALSE; + } + byte++; + net_len -= 8; + } + } + return TRUE; +} + +/** + * Get a route: If "nexthop" the nexthop is returned, source addr otherwise + */ +static host_t * +get_route (private_kernel_vpp_net_t *this, host_t *dest, int prefix, + bool nexthop, char **iface, host_t *src) +{ + fib_path_t path; + char *out, *tmp; + int out_len, i, num; + vl_api_fib_path_t *fp; + host_t *addr = NULL; + enumerator_t *enumerator; + iface_t *entry; + int family; + + path.sw_if_index = ~0; + path.preference = ~0; + path.next_hop = chunk_empty; + + vl_api_ip_route_dump_t *mp; + vl_api_ip_route_details_t *rmp; + + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_route_dump_b9d2e09e"); + mp->_vl_msg_id = htons (msg_id); + mp->table.is_ip6 = dest->get_family (dest) == AF_INET6 ? 1 : 0; + if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + vl_msg_api_free (mp); + DBG2 (DBG_KNL, "send VL_API_IP_ROUTE_ADD_DEL failed"); + return NULL; + } + vl_msg_api_free (mp); + + if (dest->get_family (dest) == AF_INET) + { + i = 0; + family = AF_INET; + if (prefix == -1) + prefix = 32; + + tmp = out; + while (tmp < (out + out_len)) + { + rmp = (void *) tmp; + num = rmp->route.n_paths; + + if (rmp->route.prefix.len && + addr_in_subnet ( + dest->get_address (dest), prefix, + chunk_create (rmp->route.prefix.address.un.ip4, 4), + rmp->route.prefix.len)) + { + fp = rmp->route.paths; + for (i = 0; i < num; i++) + { +#define IS_IP4_ANY(a) (a[0] == 0 && a[1] == 0 && a[2] == 0 & a[3] == 0) + if (fp->type == FIB_API_PATH_TYPE_DROP) + { + fp++; + continue; + } + if ((fp->preference < path.preference) || + (path.sw_if_index == ~0) || + IS_IP4_ANY (path.next_hop.ptr)) + { + path.sw_if_index = ntohl (fp->sw_if_index); + path.preference = fp->preference; + if (path.next_hop.ptr) + vl_msg_api_free (path.next_hop.ptr); + path.next_hop = chunk_create (fp->nh.address.ip4, 4); + } + fp++; + } + } + tmp += sizeof (*rmp) + (sizeof (*fp) * num); + } + } + else + { + DBG1 (DBG_KNL, "not yet support ip6"); + return NULL; + } + + if (path.next_hop.len) + { + if (nexthop) + { + if (iface) + { + *iface = NULL; + this->mutex->lock (this->mutex); + enumerator = this->ifaces->create_enumerator (this->ifaces); + while (enumerator->enumerate (enumerator, &entry)) + { + if (entry->index == path.sw_if_index) + { + *iface = strdup (entry->if_name); + break; + } + } + enumerator->destroy (enumerator); + this->mutex->unlock (this->mutex); + } + addr = host_create_from_chunk (family, path.next_hop, 0); + } + else + { + if (src) + { + addr = src->clone (src); + } + } + } + + free (out); + + return addr; +} + +METHOD (enumerator_t, addr_enumerate, bool, addr_enumerator_t *this, + va_list args) +{ + iface_t *entry; + host_t **host; + + VA_ARGS_VGET (args, host); + + while (TRUE) + { + while (!this->addrs) + { + if (!this->ifaces->enumerate (this->ifaces, &entry)) + { + return FALSE; + } + if (!entry->up && !(this->which & ADDR_TYPE_DOWN)) + { + continue; + } + this->addrs = entry->addrs->create_enumerator (entry->addrs); + } + if (this->addrs->enumerate (this->addrs, host)) + { + return TRUE; + } + this->addrs->destroy (this->addrs); + this->addrs = NULL; + } +} + +METHOD (enumerator_t, addr_destroy, void, addr_enumerator_t *this) +{ + DESTROY_IF (this->addrs); + this->ifaces->destroy (this->ifaces); + this->mutex->unlock (this->mutex); + free (this); +} + +METHOD (kernel_net_t, get_interface_name, bool, private_kernel_vpp_net_t *this, + host_t *ip, char **name) +{ + iface_t *entry; + + this->mutex->lock (this->mutex); + entry = address2entry (this, ip); + if (entry && name) + { + *name = strdup (entry->if_name); + } + this->mutex->unlock (this->mutex); + + return entry != NULL; +} + +METHOD (kernel_net_t, create_address_enumerator, enumerator_t *, + private_kernel_vpp_net_t *this, kernel_address_type_t which) +{ + addr_enumerator_t *enumerator; + + if (!(which & ADDR_TYPE_REGULAR)) + { + /* we currently have no virtual, but regular IPs only */ + return enumerator_create_empty (); + } + + this->mutex->lock (this->mutex); + + INIT(enumerator, + .public = { + .enumerate = enumerator_enumerate_default, + .venumerate = _addr_enumerate, + .destroy = _addr_destroy, + }, + .which = which, + .ifaces = this->ifaces->create_enumerator(this->ifaces), + .mutex = this->mutex, + ); + return &enumerator->public; +} + +METHOD (kernel_net_t, get_source_addr, host_t *, + private_kernel_vpp_net_t *this, host_t *dest, host_t *src) +{ + return get_route (this, dest, -1, FALSE, NULL, src); +} + +METHOD (kernel_net_t, get_nexthop, host_t *, private_kernel_vpp_net_t *this, + host_t *dest, int prefix, host_t *src, char **iface) +{ + return get_route (this, dest, prefix, TRUE, iface, src); +} + +METHOD (kernel_net_t, add_ip, status_t, private_kernel_vpp_net_t *this, + host_t *virtual_ip, int prefix, char *iface_name) +{ + return NOT_SUPPORTED; +} + +METHOD (kernel_net_t, del_ip, status_t, private_kernel_vpp_net_t *this, + host_t *virtual_ip, int prefix, bool wait) +{ + return NOT_SUPPORTED; +} + +METHOD (kernel_net_t, add_route, status_t, private_kernel_vpp_net_t *this, + chunk_t dst_net, u_int8_t prefixlen, host_t *gateway, host_t *src_ip, + char *if_name) +{ + return manage_route (this, TRUE, dst_net, prefixlen, gateway, if_name); +} + +METHOD (kernel_net_t, del_route, status_t, private_kernel_vpp_net_t *this, + chunk_t dst_net, u_int8_t prefixlen, host_t *gateway, host_t *src_ip, + char *if_name) +{ + return manage_route (this, FALSE, dst_net, prefixlen, gateway, if_name); +} + +static void +iface_destroy (iface_t *this) +{ + this->addrs->destroy_offset (this->addrs, offsetof (host_t, destroy)); + free (this); +} + +METHOD (kernel_net_t, destroy, void, private_kernel_vpp_net_t *this) +{ + this->net_update->cancel (this->net_update); + this->mutex->destroy (this->mutex); + this->ifaces->destroy_function (this->ifaces, (void *) iface_destroy); + free (this); +} + +/** + * Update addresses for an iface entry + */ +static void +update_addrs (private_kernel_vpp_net_t *this, iface_t *entry) +{ + char *out; + int out_len, i, num; + vl_api_ip_address_dump_t *mp; + vl_api_ip_address_details_t *rmp, *tmp; + linked_list_t *addrs; + host_t *host; + + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_address_dump_2d033de4"); + mp->_vl_msg_id = htons (msg_id); + mp->sw_if_index = htonl (entry->index); + mp->is_ipv6 = 0; + if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG2 (DBG_NET, "update_addrs : send VL_API_IP_ADDRESS_DUMP ipv4 failed"); + vl_msg_api_free (mp); + return; + } + num = out_len / sizeof (*rmp); + addrs = linked_list_create (); + tmp = (vl_api_ip_address_details_t *) out; + for (i = 0; i < num; i++) + { + tmp += i; + rmp = tmp; + host = host_create_from_chunk ( + AF_INET, chunk_create (rmp->prefix.address.un.ip4, 4), 0); + addrs->insert_last (addrs, host); + } + free (out); + + mp->is_ipv6 = 1; + if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len)) + { + DBG2 (DBG_NET, "update_addrs : send VL_API_IP_ADDRESS_DUMP ipv6 failed"); + vl_msg_api_free (mp); + return; + } + num = out_len / sizeof (*rmp); + tmp = (vl_api_ip_address_details_t *) out; + for (i = 0; i < num; i++) + { + tmp += i; + rmp = tmp; + host = host_create_from_chunk ( + AF_INET6, chunk_create (rmp->prefix.address.un.ip6, 16), 0); + addrs->insert_last (addrs, host); + } + vl_msg_api_free (mp); + free (out); + + entry->addrs->destroy (entry->addrs); + entry->addrs = + linked_list_create_from_enumerator (addrs->create_enumerator (addrs)); + addrs->destroy (addrs); +} + +/** + * VPP API interface event callback + */ +static void +event_cb (char *data, int data_len, void *ctx) +{ + private_kernel_vpp_net_t *this = ctx; + vl_api_sw_interface_event_t *event; + iface_t *entry; + enumerator_t *enumerator; + + event = (void *) data; + this->mutex->lock (this->mutex); + enumerator = this->ifaces->create_enumerator (this->ifaces); + while (enumerator->enumerate (enumerator, &entry)) + { + if (entry->index == ntohl (event->sw_if_index)) + { + if (event->deleted) + { + this->ifaces->remove_at (this->ifaces, enumerator); + DBG2 (DBG_NET, "interface deleted %u %s", entry->index, + entry->if_name); + iface_destroy (entry); + } + else if (entry->up != (event->flags & IF_STATUS_API_FLAG_LINK_UP)) + { + entry->up = + (event->flags & IF_STATUS_API_FLAG_LINK_UP) ? TRUE : FALSE; + DBG2 (DBG_NET, "interface state changed %u %s %s", entry->index, + entry->if_name, entry->up ? "UP" : "DOWN"); + } + break; + } + } + enumerator->destroy (enumerator); + this->mutex->unlock (this->mutex); + free (data); +} + +/** + * Inteface update thread (update interface list and interface address) + */ +static void * +net_update_thread_fn (private_kernel_vpp_net_t *this) +{ + status_t rv; + while (1) + { + char *out; + int out_len; + vl_api_sw_interface_dump_t *mp; + vl_api_sw_interface_details_t *rmp; + enumerator_t *enumerator; + iface_t *entry; + + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "sw_interface_dump_aa610c27"); + mp->_vl_msg_id = htons (msg_id); + mp->name_filter_valid = 0; + rv = vac->send_dump (vac, (u8 *) mp, sizeof (*mp), &out, &out_len); + if (!rv) + { + int i, num; + this->mutex->lock (this->mutex); + enumerator = this->ifaces->create_enumerator (this->ifaces); + num = out_len / sizeof (*rmp); + rmp = (vl_api_sw_interface_details_t *) out; + for (i = 0; i < num; i++) + { + bool exists = FALSE; + if (i) + rmp += 1; + while (enumerator->enumerate (enumerator, &entry)) + { + if (entry->index == ntohl (rmp->sw_if_index)) + { + exists = TRUE; + break; + } + } + if (!exists) + { + INIT (entry, .index = ntohl (rmp->sw_if_index), + .up = (rmp->flags & IF_STATUS_API_FLAG_LINK_UP) ? + TRUE : + FALSE, + .addrs = linked_list_create (), ); + memcpy (entry->if_name, rmp->interface_name, 63); + this->ifaces->insert_last (this->ifaces, entry); + } + update_addrs (this, entry); + } + enumerator->destroy (enumerator); + this->mutex->unlock (this->mutex); + free (out); + } + vl_msg_api_free (mp); + + if (!this->events_on) + { + vl_api_want_interface_events_t *emp; + api_main_t *am = vlibapi_get_main (); + + emp = vl_msg_api_alloc (sizeof (*emp)); + clib_memset (emp, 0, sizeof (*emp)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "want_interface_events_476f5a08"); + emp->_vl_msg_id = ntohs (msg_id); + emp->enable_disable = 1; + emp->pid = ntohl (am->our_pid); + rv = vac->register_event (vac, (char *) emp, sizeof (*emp), event_cb, + VL_API_SW_INTERFACE_EVENT, this); + if (!rv) + this->events_on = TRUE; + } + + sleep (2); + } + return NULL; +} + +kernel_vpp_net_t * +kernel_vpp_net_create () +{ + private_kernel_vpp_net_t *this; + + INIT(this, + .public = { + .interface = { + .get_interface = _get_interface_name, + .create_address_enumerator = _create_address_enumerator, + .get_source_addr = _get_source_addr, + .get_nexthop = _get_nexthop, + .add_ip = _add_ip, + .del_ip = _del_ip, + .add_route = _add_route, + .del_route = _del_route, + .destroy = _destroy, + }, + }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .ifaces = linked_list_create(), + .events_on = FALSE, + ); + + this->net_update = + thread_create ((thread_main_t) net_update_thread_fn, this); + + return &this->public; +} diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_net.h b/extras/strongswan/vpp_sswan/kernel_vpp_net.h new file mode 100644 index 00000000000..bf5359c29ed --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_net.h @@ -0,0 +1,41 @@ +#ifndef KERNEL_VPP_NET_H_ +#define KERNEL_VPP_NET_H_ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +typedef struct kernel_vpp_net_t kernel_vpp_net_t; + +/** + * Implementation of the kernel network interface using Netlink. + */ +struct kernel_vpp_net_t +{ + + /** + * Implements kernel_net_t interface + */ + kernel_net_t interface; +}; + +/** + * Create a vpp kernel network interface instance. + * + * @return kernel_vpp_net_t instance + */ +kernel_vpp_net_t *kernel_vpp_net_create (); + +#endif /** KERNEL_VPP_NET_H_ @}*/ diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_plugin.c b/extras/strongswan/vpp_sswan/kernel_vpp_plugin.c new file mode 100644 index 00000000000..9791987f5ae --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_plugin.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#define vl_typedefs +#define vl_endianfun +/* Include the (first) vlib-api API definition layer */ +#include +/* Include the current layer (third) vpp API definition layer */ +#include +#include +#undef vl_typedefs +#undef vl_endianfun + +#include "kernel_vpp_plugin.h" +#include "kernel_vpp_shared.h" +#include "kernel_vpp_ipsec.h" +#include "kernel_vpp_net.h" + +typedef struct private_kernel_vpp_plugin_t private_kernel_vpp_plugin_t; + +/** + * private data of kernel vpp plugin + */ +struct private_kernel_vpp_plugin_t +{ + /** + * implements plugin interface + */ + kernel_vpp_plugin_t public; + + vac_t *vac; +}; + +METHOD (plugin_t, get_name, char *, private_kernel_vpp_plugin_t *this) +{ + return "kernel-vpp"; +} + +METHOD (plugin_t, get_features, int, private_kernel_vpp_plugin_t *this, + plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK (kernel_ipsec_register, kernel_vpp_ipsec_create), + PLUGIN_PROVIDE (CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK (kernel_net_register, kernel_vpp_net_create), + PLUGIN_PROVIDE (CUSTOM, "kernel-net"), + }; + *features = f; + return countof (f); +} + +METHOD (plugin_t, destroy, void, private_kernel_vpp_plugin_t *this) +{ + if (this->vac) + { + lib->set (lib, "kernel-vpp-vac", NULL); + this->vac->destroy (this->vac); + } + free (this); +} + +plugin_t * +kernel_vpp_plugin_create () +{ + private_kernel_vpp_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + this->vac = vac_create ("strongswan"); + if (!this->vac) + { + DBG1 (DBG_KNL, "vac_create failed"); + destroy (this); + return NULL; + } + lib->set (lib, "kernel-vpp-vac", this->vac); + + return &this->public.plugin; +} diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_plugin.h b/extras/strongswan/vpp_sswan/kernel_vpp_plugin.h new file mode 100644 index 00000000000..0214029afad --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_plugin.h @@ -0,0 +1,34 @@ +#ifndef KERNEL_VPP_PLUGIN_H_ +#define KERNEL_VPP_PLUGIN_H_ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +typedef struct kernel_vpp_plugin_t kernel_vpp_plugin_t; + +/** + * vpp kernel interface plugin + */ +struct kernel_vpp_plugin_t +{ + + /** + * implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** KERNEL_VPP_PLUGIN_H_ @}*/ diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_shared.c b/extras/strongswan/vpp_sswan/kernel_vpp_shared.c new file mode 100644 index 00000000000..9eabb6879d1 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_shared.c @@ -0,0 +1,622 @@ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kernel_vpp_shared.h" + +#define vl_typedefs +#define vl_endianfun +/* Include the (first) vlib-api API definition layer */ +#include +/* Include the current layer (third) vpp API definition layer */ +#include +#include +#undef vl_typedefs +#undef vl_endianfun + +typedef struct private_vac_t private_vac_t; +typedef struct vl_api_header_t vl_api_header_t; +typedef struct vl_api_rheader_t vl_api_rheader_t; +typedef struct want_event_reply_t want_event_reply_t; + +vac_t *vac; + +/** + * Private variables and functions of vac_t class. + */ +struct private_vac_t +{ + + /** + * public part of the vac_t object. + */ + vac_t public; + + /** + * Timeout for VPP API replies, in ms + */ + uint16_t read_timeout; + + /** + * True if connected to VPP vlib + */ + bool connected_to_vlib; + + /** + * True if receive thread is running + */ + bool rx_is_running; + + /** + * Receive thread + */ + thread_t *rx; + + /** + * Mutex to lock receive queue + */ + mutex_t *queue_lock; + + /** + * Condition variable rx thread susspend + */ + condvar_t *suspend_cv; + + /** + * Condition variable rx thread resume + */ + condvar_t *resume_cv; + + /** + * Condition variable rx thread terminate + */ + condvar_t *terminate_cv; + + /** + * Mutex to lock send VPP API message entries + */ + mutex_t *entries_lock; + + /** + * VPP API message entries currently active, uintptr_t seq => entry_t + */ + hashtable_t *entries; + + /** + * Mutex to lock VPP API event entries + */ + mutex_t *events_lock; + + /** + * VPP API event entries currently active, uintptr_t id = event_t + */ + hashtable_t *events; + + /** + * Current sequence number for VPP API messages + */ + refcount_t seq; +}; + +/** + * VPP API message header + */ +struct vl_api_header_t +{ + + /** message ID */ + uint16_t _vl_msg_id; + + /** opaque cookie to identify the client */ + uint32_t client_index; + + /** client context, to match reply with request */ + uint32_t context; +} __attribute__ ((packed)); + +/** + * VPP API response message header + */ +struct vl_api_rheader_t +{ + + /** message ID */ + uint16_t _vl_msg_id; + + /** opaque cookie to identify the client */ + uint32_t context; +} __attribute__ ((packed)); + +/** + * VPP API register event response message header + */ +struct want_event_reply_t +{ + + /** message ID */ + uint16_t _vl_msg_id; + + /** opaque cookie to identify the client */ + uint32_t context; + + /** retrun code for the request */ + int32_t retval; +} __attribute__ ((packed)); + +/** + * VPP API request entry the answer for a waiting thread is collected in + */ +typedef struct +{ + /** Condition variable thread is waiting */ + condvar_t *condvar; + /** Array of reply msgs in a multi-message response, as struct rmsgbuf_t */ + array_t *rmsgs; + /** All response messages received? */ + bool complete; + /** Is VPP API dump? */ + bool is_dump; +} entry_t; + +/** + * Reply message buffer + */ +typedef struct +{ + /** Data length */ + uint32_t data_len; + /** Reply data */ + uint8_t data[0]; +} rmsgbuf_t; + +/** + * VPP API event entry + */ +typedef struct +{ + /** Event callback */ + event_cb_t cb; + /** User data passed to callback */ + void *ctx; +} event_t; + +/** + * Free VPP API message + */ +static void +vac_free (void *msg) +{ + vl_msg_api_free (msg); +} + +/** + * Process a single VPP API message + */ +static void +vac_api_handler (private_vac_t *this, void *msg) +{ + vl_api_rheader_t *rmp; + entry_t *entry; + rmsgbuf_t *rmsg; + uintptr_t seq, event_id; + u16 id = ntohs (*((u16 *) msg)); + msgbuf_t *msgbuf = (msgbuf_t *) (((u8 *) msg) - offsetof (msgbuf_t, data)); + int l = ntohl (msgbuf->data_len); + event_t *event; + + if (l == 0) + { + DBG2 (DBG_KNL, "vac msg ID %d has wrong len %d", id, l); + vac_free (msg); + return; + } + + rmp = (void *) msg; + seq = (uintptr_t) rmp->context; + + this->entries_lock->lock (this->entries_lock); + entry = this->entries->get (this->entries, (void *) seq); + if (entry) + { + if (entry->is_dump) + { + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "control_ping_reply_f6b0b8ca"); + if (id == msg_id) + { + entry->complete = TRUE; + entry->condvar->signal (entry->condvar); + vac_free (msg); + this->entries_lock->unlock (this->entries_lock); + return; + } + } + else + { + entry->complete = TRUE; + entry->condvar->signal (entry->condvar); + } + + rmsg = malloc (l + sizeof (msgbuf_t)); + rmsg->data_len = l; + memcpy (rmsg->data, msg, l); + array_insert (entry->rmsgs, ARRAY_TAIL, rmsg); + } + else + { + this->events_lock->lock (this->events_lock); + event_id = (uintptr_t) id; + event = this->events->get (this->events, (void *) event_id); + if (event) + event->cb (msg, l, event->ctx); + else + DBG1 (DBG_KNL, "received unknown vac msg seq %u id %d len %d, ignored", + seq, id, l); + this->events_lock->unlock (this->events_lock); + } + + this->entries_lock->unlock (this->entries_lock); + vac_free (msg); +} + +/** + * VPP API receive thread + */ +static void * +vac_rx_thread_fn (private_vac_t *this) +{ + svm_queue_t *q; + api_main_t *am = vlibapi_get_main (); + vl_api_memclnt_keepalive_t *mp; + vl_api_memclnt_keepalive_reply_t *rmp; + vl_shmem_hdr_t *shmem_hdr; + uword msg; + + q = am->vl_input_queue; + + while (TRUE) + { + while (!svm_queue_sub (q, (u8 *) &msg, SVM_Q_WAIT, 0)) + { + u16 id = ntohs (*((u16 *) msg)); + switch (id) + { + case VL_API_RX_THREAD_EXIT: + vl_msg_api_free ((void *) msg); + this->queue_lock->lock (this->queue_lock); + this->terminate_cv->signal (this->terminate_cv); + this->queue_lock->unlock (this->queue_lock); + DBG3 (DBG_KNL, "vac received rx thread exit [%d]", + VL_API_RX_THREAD_EXIT); + thread_exit (NULL); + return NULL; + break; + + case VL_API_MEMCLNT_RX_THREAD_SUSPEND: + vl_msg_api_free ((void *) msg); + this->queue_lock->lock (this->queue_lock); + this->suspend_cv->signal (this->suspend_cv); + this->resume_cv->wait (this->resume_cv, this->queue_lock); + this->queue_lock->unlock (this->queue_lock); + DBG3 (DBG_KNL, "vac received rx thread suspend [%d]", + VL_API_MEMCLNT_RX_THREAD_SUSPEND); + break; + + case VL_API_MEMCLNT_READ_TIMEOUT: + DBG3 (DBG_KNL, "vac received read timeout [%d]", + VL_API_MEMCLNT_READ_TIMEOUT); + vl_msg_api_free ((void *) msg); + break; + + case VL_API_MEMCLNT_KEEPALIVE: + mp = (void *) msg; + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + u16 msg_id = vl_msg_api_get_msg_index ( + (u8 *) "memclnt_keepalive_reply_e8d4e804"); + rmp->_vl_msg_id = ntohs (msg_id); + rmp->context = mp->context; + shmem_hdr = am->shmem_hdr; + vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &rmp); + vl_msg_api_free ((void *) msg); + DBG3 (DBG_KNL, "vac received keepalive %d", + VL_API_MEMCLNT_KEEPALIVE); + break; + + default: + vac_api_handler (this, (void *) msg); + } + } + } + + return NULL; +} + +METHOD (vac_t, destroy, void, private_vac_t *this) +{ + if (this->connected_to_vlib) + { + if (this->rx) + { + api_main_t *am = vlibapi_get_main (); + vl_api_rx_thread_exit_t *ep; + bool timed_out; + ep = vl_msg_api_alloc (sizeof (*ep)); + memset (ep, 0, sizeof (*ep)); + u16 msg_id = + vl_msg_api_get_msg_index ((u8 *) "rx_thread_exit_c3a3a452"); + ep->_vl_msg_id = ntohs (msg_id); + vl_msg_api_send_shmem (am->vl_input_queue, (u8 *) &ep); + this->queue_lock->lock (this->queue_lock); + timed_out = this->terminate_cv->timed_wait (this->terminate_cv, + this->queue_lock, 5000); + this->queue_lock->unlock (this->queue_lock); + if (timed_out) + this->rx->cancel (this->rx); + else + this->rx->join (this->rx); + } + vl_client_disconnect (); + vl_client_api_unmap (); + } + + this->queue_lock->destroy (this->queue_lock); + this->suspend_cv->destroy (this->suspend_cv); + this->resume_cv->destroy (this->resume_cv); + this->terminate_cv->destroy (this->terminate_cv); + this->entries->destroy (this->entries); + this->entries_lock->destroy (this->entries_lock); + this->events->destroy (this->events); + this->events_lock->destroy (this->events_lock); + + vac = NULL; + free (this); +} + +/** + * Write a VPP API message to shared memory + */ +static status_t +vac_write (private_vac_t *this, char *p, int l, uint32_t ctx) +{ + api_main_t *am = vlibapi_get_main (); + vl_api_header_t *mp = vl_msg_api_alloc (l); + memset (mp, 0, sizeof (*mp)); + svm_queue_t *q; + + if (!this->connected_to_vlib) + return FAILED; + + if (!mp) + return FAILED; + + memcpy (mp, p, l); + mp->client_index = am->my_client_index; + mp->context = ctx; + q = am->shmem_hdr->vl_input_queue; + if (svm_queue_add (q, (u8 *) &mp, 0)) + { + DBG1 (DBG_KNL, "vac vpe_api_write failed"); + vac_free (mp); + return FAILED; + } + + return SUCCESS; +} + +/** + * Clean up a thread waiting entry + */ +static void +destroy_entry (entry_t *entry) +{ + entry->condvar->destroy (entry->condvar); + array_destroy_function (entry->rmsgs, (void *) free, NULL); + free (entry); +} + +/** + * Send VPP API message and wait for a reply + */ +static status_t +send_vac (private_vac_t *this, char *in, int in_len, char **out, int *out_len, + bool is_dump) +{ + entry_t *entry; + uint32_t ctx = ref_get (&this->seq); + uintptr_t seq = (uintptr_t) ctx; + rmsgbuf_t *rmsg; + char *ptr; + int i; + + this->entries_lock->lock (this->entries_lock); + INIT (entry, .condvar = condvar_create (CONDVAR_TYPE_DEFAULT), + .rmsgs = array_create (0, 0), .is_dump = is_dump, ); + this->entries->put (this->entries, (void *) seq, entry); + + if (vac_write (this, in, in_len, ctx)) + { + destroy_entry (entry); + this->entries_lock->unlock (this->entries_lock); + return FAILED; + } + + if (is_dump) + { + vl_api_control_ping_t *mp; + status_t rv; + mp = vl_msg_api_alloc (sizeof (*mp)); + memset (mp, 0, sizeof (*mp)); + u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "control_ping_51077d14"); + mp->_vl_msg_id = ntohs (msg_id); + rv = vac_write (this, (char *) mp, sizeof (*mp), ctx); + vl_msg_api_free (mp); + if (rv) + { + DBG2 (DBG_KNL, "vac_write VL_API_CONTROL_PING failed"); + destroy_entry (entry); + this->entries_lock->unlock (this->entries_lock); + return FAILED; + } + } + + while (!entry->complete) + { + if (this->read_timeout) + { + if (entry->condvar->timed_wait (entry->condvar, this->entries_lock, + this->read_timeout * 1000)) + { + break; + } + } + else + { + entry->condvar->wait (entry->condvar, this->entries_lock); + } + } + + this->entries->remove (this->entries, (void *) seq); + this->entries_lock->unlock (this->entries_lock); + + if (!entry->complete) + { + destroy_entry (entry); + DBG1 (DBG_KNL, "vac timeout"); + return OUT_OF_RES; + } + + for (i = 0, *out_len = 0; i < array_count (entry->rmsgs); i++) + { + array_get (entry->rmsgs, i, &rmsg); + *out_len += rmsg->data_len; + } + ptr = malloc (*out_len); + *out = ptr; + while (array_remove (entry->rmsgs, ARRAY_HEAD, &rmsg)) + { + memcpy (ptr, rmsg->data, rmsg->data_len); + ptr += rmsg->data_len; + free (rmsg); + } + + destroy_entry (entry); + + return SUCCESS; +} + +METHOD (vac_t, vac_send, status_t, private_vac_t *this, char *in, int in_len, + char **out, int *out_len) +{ + return send_vac (this, in, in_len, out, out_len, FALSE); +} + +METHOD (vac_t, vac_send_dump, status_t, private_vac_t *this, char *in, + int in_len, char **out, int *out_len) +{ + return send_vac (this, in, in_len, out, out_len, TRUE); +} + +METHOD (vac_t, register_event, status_t, private_vac_t *this, char *in, + int in_len, event_cb_t cb, uint16_t event_id, void *ctx) +{ + char *out; + int out_len; + want_event_reply_t *rmp; + uintptr_t id = (uintptr_t) event_id; + event_t *event; + + if (vac->send (vac, in, in_len, &out, &out_len)) + return FAILED; + rmp = (void *) out; + if (rmp->retval) + return FAILED; + free (out); + vl_msg_api_free (in); + this->events_lock->lock (this->events_lock); + INIT (event, .cb = cb, .ctx = ctx, ); + this->events->put (this->events, (void *) id, event); + this->events_lock->unlock (this->events_lock); + + return SUCCESS; +} + +vac_t * +vac_create (char *name) +{ + private_vac_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + .send = _vac_send, + .send_dump = _vac_send_dump, + .register_event = _register_event, + }, + .rx_is_running = FALSE, + .read_timeout = lib->settings->get_int(lib->settings, + "%s.plugins.kernel-vpp.read_timeout", 0, lib->ns), + .queue_lock = mutex_create(MUTEX_TYPE_DEFAULT), + .suspend_cv = condvar_create(CONDVAR_TYPE_DEFAULT), + .resume_cv = condvar_create(CONDVAR_TYPE_DEFAULT), + .terminate_cv = condvar_create(CONDVAR_TYPE_DEFAULT), + .entries_lock = mutex_create(MUTEX_TYPE_RECURSIVE), + .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), + .events_lock = mutex_create(MUTEX_TYPE_DEFAULT), + .events = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), + .seq = 0, + ); + + clib_mem_init_thread_safe (0, 256 << 20); + + if (vl_client_api_map ("/vpe-api")) + { + DBG1 (DBG_KNL, "vac unable to map"); + destroy (this); + return NULL; + } + + if (vl_client_connect (name, 0, 32) < 0) + { + DBG1 (DBG_KNL, "vac unable to connect"); + vl_client_api_unmap (); + destroy (this); + return NULL; + } + + this->connected_to_vlib = TRUE; + + this->rx = thread_create ((thread_main_t) vac_rx_thread_fn, this); + if (!this->rx) + { + vl_client_api_unmap (); + destroy (this); + return NULL; + } + this->rx_is_running = TRUE; + + vac = &this->public; + return &this->public; +} diff --git a/extras/strongswan/vpp_sswan/kernel_vpp_shared.h b/extras/strongswan/vpp_sswan/kernel_vpp_shared.h new file mode 100644 index 00000000000..c699d4925f3 --- /dev/null +++ b/extras/strongswan/vpp_sswan/kernel_vpp_shared.h @@ -0,0 +1,85 @@ +#ifndef KERNEL_VPP_SHARED_H_ +#define KERNEL_VPP_SHARED_H_ +/* + * Copyright (c) 2022 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +typedef struct vac_t vac_t; + +/** + * Callback function invoked for received event messages. + * + * @param data associated event message, destroyed by VPP API wrapper + * @param data_len length of the event message + * @param ctx user data, as passed to register_event + */ +typedef void (*event_cb_t) (char *data, int data_len, void *ctx); + +/** + * Wrapper around VPP binary API client. + */ +struct vac_t +{ + + /** + * Destroy the VPP API client. + */ + void (*destroy) (vac_t *this); + + /** + * Send VPP API message and wait for a reply + * + * @param in VPP API message to send + * @param in_len length of the message to send + * @param out received VPP API message + * @param out_len length of the received message + */ + status_t (*send) (vac_t *this, char *in, int in_len, char **out, + int *out_len); + + /** + * Send VPP API dump message and wait for a reply. + * + * @param in VPP API message to send + * @param in_len length of the message to send + * @param out received VPP API message + * @param out_len length of the received message + */ + status_t (*send_dump) (vac_t *this, char *in, int in_len, char **out, + int *out_len); + + /** + * Register for VPP API event of a given kind. + * + * @param in VPP API event message to register + * @param in_len length of the event message to register + * @param cb callback function to register + * @param event_id event ID + * @param ctx user data passed to callback invocations + */ + status_t (*register_event) (vac_t *this, char *in, int in_len, event_cb_t cb, + uint16_t event_id, void *ctx); +}; + +extern vac_t *vac; + +/** + * Establishing a binary API connection to VPP. + * + * @param name client name + * @return vac_t instance + */ +vac_t *vac_create (char *name); + +#endif /* KERNEL_VPP_SHARED_H_ */ diff --git a/extras/strongswan/vpp_sswan/swanctl.conf b/extras/strongswan/vpp_sswan/swanctl.conf new file mode 100644 index 00000000000..f3e7a78101f --- /dev/null +++ b/extras/strongswan/vpp_sswan/swanctl.conf @@ -0,0 +1,35 @@ +connections { + net-net { + local_addrs = 192.168.0.2 + remote_addrs = 192.168.0.1 + local { + auth = psk + id = sun.strongswan.org + } + remote { + auth = psk + id = moon.strongswan.org + } + children { + net-net { + local_ts = 192.168.200.0/24 + remote_ts = 192.168.100.0/24 + esp_proposals = aes128-sha1-modp2048 + rekey_time = 240m + } + } + version = 2 + mobike = yes + encap = no # NAT-T if needed + proposals = aes128-sha256-x25519 + } +} +secrets { + ike-net-net { + id = moon.strongswan.org + secret = simplepsk + } +} + +# Include config snippets +include conf.d/*.conf -- cgit 1.2.3-korg