Profiling ESP encryption with OpenSSL at 64 bytes per packet showed
that about 22% of the cycles went to OpenSSL 3's per-call parameter
handling (OSSL_PARAM_locate, strcmp, EVP_CIPHER_CTX_ctrl) and only about
5% to the AES-GCM computation itself.

The Intel IPsec Multi-Buffer library has direct AES-GCM functions that
take a key expanded in advance and process a packet in one call.  Use
them if the library is available at build time, and OpenSSL otherwise.
'--disable-ipsec-mb' disables it.

Each SA now expands its key once, when it is created.  Decryption
compares the ICV in constant time.  The library's manager is only used
for its function table by these functions, so threads share one.

Measured with one PMD thread on an Intel Core Ultra 9 285HX (AVX2), an
E810 25G port and pktgen, in TSC cycles per packet, averaged over two
or three runs:

                    64 bytes        512 bytes        1400 bytes
  encrypt  OpenSSL  806 (3.7 Mpps) 1323 (9.4 Gbps)  2060 (16.6 Gbps)
           IPsec-MB 500 (6.0 Mpps)  912 (13.6 Gbps) 1751 (19.3 Gbps)
  decrypt  OpenSSL  744 (4.0 Mpps) 1317 (9.5 Gbps)  1828 (18.7 Gbps)
           IPsec-MB 456 (6.5 Mpps) 1020 (12.2 Gbps) 1782 (18.9 Gbps)

At 1400 bytes, the AES-GCM computation now takes two thirds of the
cycles.

Assisted-by: claude-opus-5-5, Claude Code
Signed-off-by: Thilak Raj Surendra Babu <[email protected]>
---
 Documentation/faq/configuration.rst     |   3 +-
 Documentation/intro/install/general.rst |   5 +
 NEWS                                    |   2 +
 configure.ac                            |   1 +
 lib/automake.mk                         |   1 +
 lib/esp.c                               | 265 +++++++++++++++++++-----
 lib/netdev-vport.c                      |   3 +-
 m4/openvswitch.m4                       |  33 +++
 tests/library.at                        |   2 +-
 vswitchd/vswitch.xml                    |   5 +-
 10 files changed, 259 insertions(+), 61 deletions(-)

diff --git a/Documentation/faq/configuration.rst 
b/Documentation/faq/configuration.rst
index 840b65898..b15fec8a7 100644
--- a/Documentation/faq/configuration.rst
+++ b/Documentation/faq/configuration.rst
@@ -265,7 +265,8 @@ Q: Does the userspace datapath, for example with DPDK, 
support IPsec?
     ``esp`` tunnel type, which carries IPv4 and IPv6 packets in IPsec ESP
     tunnel mode (RFC 4303) with AES-GCM (RFC 4106).  The datapath threads
     encrypt and decrypt the packets themselves, so this requires Open vSwitch
-    to be built with OpenSSL.
+    to be built with the Intel IPsec Multi-Buffer library, which is faster,
+    or with OpenSSL.
 
     This is different from the IPsec support described in
     :doc:`/howto/ipsec`, where ``ovs-monitor-ipsec`` has the Linux kernel
diff --git a/Documentation/intro/install/general.rst 
b/Documentation/intro/install/general.rst
index b41a25225..bca6e2e3c 100644
--- a/Documentation/intro/install/general.rst
+++ b/Documentation/intro/install/general.rst
@@ -98,6 +98,11 @@ need the following software:
   environment variable OVS_UNBOUND_CONF can be used to specify the
   configuration file for unbound.
 
+- The Intel IPsec Multi-Buffer library (libIPSec_MB), which is optional.  If
+  it is installed, ``esp`` tunnels of the userspace datapath use it for
+  AES-GCM, which is faster than OpenSSL.  Use ``--disable-ipsec-mb`` to build
+  without it.
+
 On Linux, you may use the kernel module distributed with the upstream Linux
 kernel 3.3 or later. You may also use the userspace-only implementation, at
 some cost in features and performance. Refer to :doc:`userspace` for details.
diff --git a/NEWS b/NEWS
index b02185ef9..5a170a2c4 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@ Post-v4.0.0
        from an IKE daemon, such as strongSwan with a 'kernel-ovs' plugin,
        through new 'esp/sa-add', 'esp/sa-del', 'esp/sa-query',
        'esp/sa-flush' and 'esp/spi-alloc' unixctl commands.
+     * 'esp' tunnels use the Intel IPsec Multi-Buffer library for AES-GCM,
+       if it is available at build time, instead of OpenSSL.
 
 
 v4.0.0 - 17 Aug 2026
diff --git a/configure.ac b/configure.ac
index db233fad4..15cd781d6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -93,6 +93,7 @@ OVS_CHECK_USDT
 OVS_CHECK_NETLINK
 OVS_CHECK_OPENSSL
 OVS_CHECK_LIBCAPNG
+OVS_CHECK_IPSEC_MB
 OVS_CHECK_LOGDIR
 OVS_CHECK_PYTHON3
 OVS_CHECK_FLAKE8
diff --git a/lib/automake.mk b/lib/automake.mk
index fd842e781..ab196330b 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -9,6 +9,7 @@ lib_LTLIBRARIES += lib/libopenvswitch.la
 
 lib_libopenvswitch_la_LIBADD = $(SSL_LIBS)
 lib_libopenvswitch_la_LIBADD += $(CAPNG_LDADD)
+lib_libopenvswitch_la_LIBADD += $(IPSEC_MB_LIBS)
 
 lib_libopenvswitch_la_LDFLAGS = \
         $(OVS_LTINFO) \
diff --git a/lib/esp.c b/lib/esp.c
index 62ff291b0..f3b46bcb7 100644
--- a/lib/esp.c
+++ b/lib/esp.c
@@ -20,9 +20,15 @@
 #include <errno.h>
 #include <string.h>
 
-#ifdef HAVE_OPENSSL
+/* AES-GCM comes from the Intel IPsec Multi-Buffer library if available,
+ * otherwise from OpenSSL.  Without either, SAs cannot be created. */
+#if defined(HAVE_IPSEC_MB)
+#include <intel-ipsec-mb.h>
+#define ESP_CRYPTO 1
+#elif defined(HAVE_OPENSSL)
 #include <openssl/crypto.h>
 #include <openssl/evp.h>
+#define ESP_CRYPTO 1
 #endif
 
 #include "byte-order.h"
@@ -39,7 +45,7 @@
 
 VLOG_DEFINE_THIS_MODULE(esp);
 
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
 COVERAGE_DEFINE(esp_tx_seq_exhausted);
 COVERAGE_DEFINE(esp_tx_crypto_error);
 COVERAGE_DEFINE(esp_rx_malformed);
@@ -52,7 +58,9 @@ struct esp_sa {
     struct cmap_node node;          /* In 'esp_sad', for inbound SAs. */
     struct esp_sa_params params;
     uint64_t id;                    /* Unique, never 0. */
-#ifdef HAVE_OPENSSL
+#if defined(HAVE_IPSEC_MB)
+    struct gcm_key_data *gcm_key;   /* Expanded key, cache line aligned. */
+#elif defined(HAVE_OPENSSL)
     const EVP_CIPHER *cipher;
 #endif
 
@@ -83,10 +91,15 @@ struct esp_sa {
     uint64_t *replay_bitmap;        /* Its contents are guarded. */
 };
 
-#ifdef HAVE_OPENSSL
-#define OVS_UNUSED_WITHOUT_OPENSSL
+#ifdef ESP_CRYPTO
+#define OVS_UNUSED_WITHOUT_CRYPTO
 #else
-#define OVS_UNUSED_WITHOUT_OPENSSL OVS_UNUSED
+#define OVS_UNUSED_WITHOUT_CRYPTO OVS_UNUSED
+#endif
+
+#ifdef ESP_CRYPTO
+static void esp_crypto_init_sa(struct esp_sa *);
+static void esp_crypto_uninit_sa(struct esp_sa *);
 #endif
 
 /* SA database, indexed by SPI and destination.  Only written by the main
@@ -96,7 +109,7 @@ static struct cmap esp_sad = CMAP_INITIALIZER;
 bool
 esp_is_supported(void)
 {
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
     return true;
 #else
     return false;
@@ -157,22 +170,16 @@ esp_sa_params_equal(const struct esp_sa_params *a,
 /* Returns a new SA for 'params', or NULL if ESP is not supported or 'params'
  * is invalid. */
 struct esp_sa *
-esp_sa_create(const struct esp_sa_params *params OVS_UNUSED_WITHOUT_OPENSSL)
+esp_sa_create(const struct esp_sa_params *params OVS_UNUSED_WITHOUT_CRYPTO)
 {
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
     static atomic_uint64_t next_id = 1;
-    const EVP_CIPHER *cipher;
     struct esp_sa *sa;
 
     switch (params->key_len - ESP_SALT_LEN) {
     case 16:
-        cipher = EVP_aes_128_gcm();
-        break;
     case 24:
-        cipher = EVP_aes_192_gcm();
-        break;
     case 32:
-        cipher = EVP_aes_256_gcm();
         break;
     default:
         return NULL;
@@ -189,7 +196,7 @@ esp_sa_create(const struct esp_sa_params *params 
OVS_UNUSED_WITHOUT_OPENSSL)
     sa = xzalloc(sizeof *sa);
     sa->params = *params;
     atomic_add_relaxed(&next_id, 1, &sa->id);
-    sa->cipher = cipher;
+    esp_crypto_init_sa(sa);
     atomic_init(&sa->seq, 0);
     atomic_init(&sa->n_packets, 0);
     atomic_init(&sa->n_bytes, 0);
@@ -217,9 +224,10 @@ esp_sa_destroy(struct esp_sa *sa)
     if (sa) {
         ovs_mutex_destroy(&sa->replay_mutex);
         free(sa->replay_bitmap);
-#ifdef HAVE_OPENSSL
-        OPENSSL_cleanse(sa->params.key, sizeof sa->params.key);
+#ifdef ESP_CRYPTO
+        esp_crypto_uninit_sa(sa);
 #endif
+        ovs_memwipe(sa->params.key, sizeof sa->params.key);
         free(sa);
     }
 }
@@ -239,7 +247,7 @@ esp_sa_get_params(const struct esp_sa *sa)
     return &sa->params;
 }
 
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
 static void
 esp_stat_inc(atomic_uint64_t *counter, uint64_t n)
 {
@@ -398,7 +406,7 @@ esp_sad_lookup(ovs_be32 sad_id, const struct in6_addr *dst)
     return NULL;
 }
 
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
 /* Anti-replay window. */
 
 /* Determines the full sequence number of a packet whose transmitted (low 32)
@@ -526,7 +534,7 @@ esp_replay_update(struct esp_sa *sa, uint64_t seq)
     return ok;
 }
 
-#endif /* HAVE_OPENSSL */
+#endif /* ESP_CRYPTO */
 
 /* Packet processing. */
 
@@ -540,7 +548,133 @@ esp_trailer_len(size_t payload_len)
     return pad_len + ESP_TRAILER_LEN + ESP_ICV_LEN;
 }
 
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
+#if defined(HAVE_IPSEC_MB)
+/* The Intel IPsec Multi-Buffer library's direct AES-GCM functions only read
+ * the manager's function table, so all threads share one manager. */
+static IMB_MGR *esp_imb_mgr;
+
+static void
+esp_crypto_init_sa(struct esp_sa *sa)
+{
+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+    const uint8_t *key = sa->params.key;
+
+    if (ovsthread_once_start(&once)) {
+        IMB_ARCH arch;
+
+        esp_imb_mgr = alloc_mb_mgr(0);
+        ovs_assert(esp_imb_mgr);
+        init_mb_mgr_auto(esp_imb_mgr, &arch);
+        VLOG_INFO("using Intel IPsec Multi-Buffer library %s (%s) for "
+                  "AES-GCM", imb_get_version_str(),
+                  arch == IMB_ARCH_AVX512 ? "AVX512"
+                  : arch == IMB_ARCH_AVX2 ? "AVX2"
+                  : arch == IMB_ARCH_AVX ? "AVX"
+                  : arch == IMB_ARCH_SSE ? "SSE" : "no AES-NI");
+        ovsthread_once_done(&once);
+    }
+
+    sa->gcm_key = xmalloc_cacheline(sizeof *sa->gcm_key);
+    switch (sa->params.key_len - ESP_SALT_LEN) {
+    case 16:
+        IMB_AES128_GCM_PRE(esp_imb_mgr, key, sa->gcm_key);
+        break;
+    case 24:
+        IMB_AES192_GCM_PRE(esp_imb_mgr, key, sa->gcm_key);
+        break;
+    default:
+        IMB_AES256_GCM_PRE(esp_imb_mgr, key, sa->gcm_key);
+        break;
+    }
+}
+
+static void
+esp_crypto_uninit_sa(struct esp_sa *sa)
+{
+    ovs_memwipe(sa->gcm_key, sizeof *sa->gcm_key);
+    free_cacheline(sa->gcm_key);
+}
+
+/* Encrypts the 'len' bytes at 'data' in place and stores the ICV in 'tag'. */
+static bool
+esp_gcm_encrypt(const struct esp_sa *sa, const uint8_t *nonce,
+                const uint8_t *aad, size_t aad_len, uint8_t *data,
+                size_t len, uint8_t *tag)
+{
+    struct gcm_context_data ctx;
+
+    switch (sa->params.key_len - ESP_SALT_LEN) {
+    case 16:
+        IMB_AES128_GCM_ENC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, tag, ESP_ICV_LEN);
+        break;
+    case 24:
+        IMB_AES192_GCM_ENC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, tag, ESP_ICV_LEN);
+        break;
+    default:
+        IMB_AES256_GCM_ENC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, tag, ESP_ICV_LEN);
+        break;
+    }
+    return true;
+}
+
+/* Decrypts the 'len' bytes at 'data' in place and returns true if they and
+ * 'aad' match the ICV in 'tag'. */
+static bool
+esp_gcm_decrypt(const struct esp_sa *sa, const uint8_t *nonce,
+                const uint8_t *aad, size_t aad_len, uint8_t *data,
+                size_t len, const uint8_t *tag)
+{
+    struct gcm_context_data ctx;
+    uint8_t computed[ESP_ICV_LEN];
+    uint8_t diff = 0;
+
+    switch (sa->params.key_len - ESP_SALT_LEN) {
+    case 16:
+        IMB_AES128_GCM_DEC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, computed, ESP_ICV_LEN);
+        break;
+    case 24:
+        IMB_AES192_GCM_DEC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, computed, ESP_ICV_LEN);
+        break;
+    default:
+        IMB_AES256_GCM_DEC(esp_imb_mgr, sa->gcm_key, &ctx, data, data, len,
+                           nonce, aad, aad_len, computed, ESP_ICV_LEN);
+        break;
+    }
+
+    /* Constant time, to not tell how much of a forged ICV is right. */
+    for (size_t i = 0; i < ESP_ICV_LEN; i++) {
+        diff |= computed[i] ^ tag[i];
+    }
+    return !diff;
+}
+#elif defined(HAVE_OPENSSL)
+static void
+esp_crypto_init_sa(struct esp_sa *sa)
+{
+    switch (sa->params.key_len - ESP_SALT_LEN) {
+    case 16:
+        sa->cipher = EVP_aes_128_gcm();
+        break;
+    case 24:
+        sa->cipher = EVP_aes_192_gcm();
+        break;
+    default:
+        sa->cipher = EVP_aes_256_gcm();
+        break;
+    }
+}
+
+static void
+esp_crypto_uninit_sa(struct esp_sa *sa OVS_UNUSED)
+{
+}
+
 /* Each thread keeps a few cipher contexts with their keys already expanded,
  * indexed by SA and direction.  A collision only costs a key expansion. */
 #define ESP_CTX_CACHE_SIZE 16
@@ -606,6 +740,44 @@ esp_get_ctx(const struct esp_sa *sa, bool enc)
     return slot->ctx;
 }
 
+/* Encrypts the 'len' bytes at 'data' in place and stores the ICV in 'tag'. */
+static bool
+esp_gcm_encrypt(const struct esp_sa *sa, const uint8_t *nonce,
+                const uint8_t *aad, size_t aad_len, uint8_t *data,
+                size_t len, uint8_t *tag)
+{
+    EVP_CIPHER_CTX *ctx = esp_get_ctx(sa, true);
+    int n;
+
+    return (ctx
+            && EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, 1)
+            && EVP_CipherUpdate(ctx, NULL, &n, aad, aad_len)
+            && EVP_CipherUpdate(ctx, data, &n, data, len)
+            && EVP_CipherFinal_ex(ctx, data + n, &n)
+            && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, ESP_ICV_LEN,
+                                   tag));
+}
+
+/* Decrypts the 'len' bytes at 'data' in place and returns true if they and
+ * 'aad' match the ICV in 'tag'. */
+static bool
+esp_gcm_decrypt(const struct esp_sa *sa, const uint8_t *nonce,
+                const uint8_t *aad, size_t aad_len, uint8_t *data,
+                size_t len, const uint8_t *tag)
+{
+    EVP_CIPHER_CTX *ctx = esp_get_ctx(sa, false);
+    int n;
+
+    return (ctx
+            && EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, 0)
+            && EVP_CipherUpdate(ctx, NULL, &n, aad, aad_len)
+            && EVP_CipherUpdate(ctx, data, &n, data, len)
+            && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, ESP_ICV_LEN,
+                                   CONST_CAST(uint8_t *, tag))
+            && EVP_CipherFinal_ex(ctx, data + n, &n) > 0);
+}
+#endif
+
 /* Builds the AES-GCM nonce and additional authenticated data for a packet
  * with ESP header 'esp' and full sequence number 'seq' (RFC 4106 section 5).
  * Returns the length of the AAD. */
@@ -644,23 +816,21 @@ esp_gcm_prepare(const struct esp_sa *sa, const struct 
esp_header *esp,
  *
  * Returns 0 if successful, otherwise a positive errno value. */
 int
-esp_seal(struct esp_sa *sa OVS_UNUSED_WITHOUT_OPENSSL,
-         struct esp_header *esp OVS_UNUSED_WITHOUT_OPENSSL,
-         size_t payload_len OVS_UNUSED_WITHOUT_OPENSSL,
-         uint8_t next_hdr OVS_UNUSED_WITHOUT_OPENSSL)
+esp_seal(struct esp_sa *sa OVS_UNUSED_WITHOUT_CRYPTO,
+         struct esp_header *esp OVS_UNUSED_WITHOUT_CRYPTO,
+         size_t payload_len OVS_UNUSED_WITHOUT_CRYPTO,
+         uint8_t next_hdr OVS_UNUSED_WITHOUT_CRYPTO)
 {
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
     uint8_t *payload = (uint8_t *) esp + ESP_PREFIX_LEN;
     size_t ct_len = esp_trailer_len(payload_len) - ESP_ICV_LEN + payload_len;
     size_t pad_len = ct_len - payload_len - ESP_TRAILER_LEN;
     uint8_t nonce[ESP_SALT_LEN + ESP_IV_LEN];
     uint8_t *trailer = payload + payload_len;
-    EVP_CIPHER_CTX *ctx;
     uint8_t aad[12];
     size_t aad_len;
     uint64_t seq;
     ovs_be64 iv;
-    int len;
 
     atomic_add_relaxed(&sa->seq, 1, &seq);
     seq++;
@@ -686,15 +856,8 @@ esp_seal(struct esp_sa *sa OVS_UNUSED_WITHOUT_OPENSSL,
 
     aad_len = esp_gcm_prepare(sa, esp, seq, nonce, aad);
 
-    ctx = esp_get_ctx(sa, true);
-    if (OVS_UNLIKELY(!ctx
-                     || !EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, 1)
-                     || !EVP_CipherUpdate(ctx, NULL, &len, aad, aad_len)
-                     || !EVP_CipherUpdate(ctx, payload, &len, payload, ct_len)
-                     || !EVP_CipherFinal_ex(ctx, payload + len, &len)
-                     || !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG,
-                                             ESP_ICV_LEN,
-                                             payload + ct_len))) {
+    if (OVS_UNLIKELY(!esp_gcm_encrypt(sa, nonce, aad, aad_len, payload,
+                                      ct_len, payload + ct_len))) {
         COVERAGE_INC(esp_tx_crypto_error);
         esp_stat_inc(&sa->n_tx_errors, 1);
         return EIO;
@@ -717,16 +880,15 @@ esp_seal(struct esp_sa *sa OVS_UNUSED_WITHOUT_OPENSSL,
  * payload starts ESP_PREFIX_LEN bytes after 'esp'.  Otherwise, returns a
  * positive errno value. */
 int
-esp_open(struct esp_header *esp OVS_UNUSED_WITHOUT_OPENSSL,
-         size_t len OVS_UNUSED_WITHOUT_OPENSSL,
-         ovs_be64 *tun_id OVS_UNUSED_WITHOUT_OPENSSL,
-         uint8_t *next_hdr OVS_UNUSED_WITHOUT_OPENSSL,
-         size_t *payload_len OVS_UNUSED_WITHOUT_OPENSSL)
+esp_open(struct esp_header *esp OVS_UNUSED_WITHOUT_CRYPTO,
+         size_t len OVS_UNUSED_WITHOUT_CRYPTO,
+         ovs_be64 *tun_id OVS_UNUSED_WITHOUT_CRYPTO,
+         uint8_t *next_hdr OVS_UNUSED_WITHOUT_CRYPTO,
+         size_t *payload_len OVS_UNUSED_WITHOUT_CRYPTO)
 {
-#ifdef HAVE_OPENSSL
+#ifdef ESP_CRYPTO
     uint8_t *payload = (uint8_t *) esp + ESP_PREFIX_LEN;
     uint8_t nonce[ESP_SALT_LEN + ESP_IV_LEN];
-    EVP_CIPHER_CTX *ctx;
     uint8_t *trailer;
     struct esp_sa *sa;
     uint8_t *pad;
@@ -736,7 +898,6 @@ esp_open(struct esp_header *esp OVS_UNUSED_WITHOUT_OPENSSL,
     size_t pad_len;
     uint32_t seq_lo;
     uint64_t seq;
-    int out_len;
 
     if (OVS_UNLIKELY(len < ESP_PREFIX_LEN + ESP_TRAILER_LEN + ESP_ICV_LEN)) {
         COVERAGE_INC(esp_rx_malformed);
@@ -759,16 +920,8 @@ esp_open(struct esp_header *esp OVS_UNUSED_WITHOUT_OPENSSL,
 
     aad_len = esp_gcm_prepare(sa, esp, seq, nonce, aad);
 
-    ctx = esp_get_ctx(sa, false);
-    if (OVS_UNLIKELY(!ctx
-                     || !EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, 0)
-                     || !EVP_CipherUpdate(ctx, NULL, &out_len, aad, aad_len)
-                     || !EVP_CipherUpdate(ctx, payload, &out_len, payload,
-                                          ct_len)
-                     || !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG,
-                                             ESP_ICV_LEN, payload + ct_len)
-                     || EVP_CipherFinal_ex(ctx, payload + out_len,
-                                           &out_len) <= 0)) {
+    if (OVS_UNLIKELY(!esp_gcm_decrypt(sa, nonce, aad, aad_len, payload,
+                                      ct_len, payload + ct_len))) {
         COVERAGE_INC(esp_rx_auth_failed);
         esp_stat_inc(&sa->n_auth_failed, 1);
         return EBADMSG;
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index 559b99c06..9f8183929 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -705,7 +705,8 @@ esp_tunnel_config__(struct netdev_vport *dev, const struct 
smap *args,
 
     if (!esp_is_supported()) {
         ds_put_format(errors, "%s: esp tunnels require Open vSwitch built "
-                      "with OpenSSL\n", name);
+                      "with OpenSSL or the Intel IPsec Multi-Buffer "
+                      "library\n", name);
         return EOPNOTSUPP;
     }
     if (tnl_cfg->ip_dst_flow || tnl_cfg->ip_src_flow) {
diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
index 979349e6b..6332b83fb 100644
--- a/m4/openvswitch.m4
+++ b/m4/openvswitch.m4
@@ -137,6 +137,39 @@ AC_DEFUN([OVS_CHECK_LIBCAPNG],
       AC_SUBST([CAPNG_LDADD])
    fi])
 
+dnl Checks for the Intel IPsec Multi-Buffer library, which esp tunnels use for
+dnl AES-GCM if it is available, instead of OpenSSL.
+AC_DEFUN([OVS_CHECK_IPSEC_MB],
+  [AC_ARG_ENABLE(
+     [ipsec-mb],
+     [AS_HELP_STRING([--disable-ipsec-mb],
+                     [Do not use the Intel IPsec Multi-Buffer library])],
+     [case "${enableval}" in
+        (yes) ipsec_mb=true ;;
+        (no)  ipsec_mb=false ;;
+        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-ipsec-mb]) ;;
+      esac],
+     [ipsec_mb=check])
+
+   HAVE_IPSEC_MB=no
+   if test "$ipsec_mb" != false; then
+       AC_CHECK_HEADER([intel-ipsec-mb.h],
+         [AC_CHECK_LIB([IPSec_MB], [imb_get_version_str],
+                       [HAVE_IPSEC_MB=yes])])
+       if test "$HAVE_IPSEC_MB" != yes && test "$ipsec_mb" = true; then
+           AC_MSG_ERROR([IPsec Multi-Buffer library requested, but not found])
+       fi
+   fi
+
+   AC_SUBST([HAVE_IPSEC_MB])
+   if test "$HAVE_IPSEC_MB" = yes; then
+      AC_DEFINE([HAVE_IPSEC_MB], [1],
+                [Define to 1 if the Intel IPsec Multi-Buffer library is
+                 available.])
+      IPSEC_MB_LIBS="-lIPSec_MB"
+      AC_SUBST([IPSEC_MB_LIBS])
+   fi])
+
 dnl Checks for OpenSSL.
 AC_DEFUN([OVS_CHECK_OPENSSL],
   [AC_ARG_ENABLE(
diff --git a/tests/library.at b/tests/library.at
index dc6ec3000..443d1896c 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -66,7 +66,7 @@ AT_CLEANUP
 AT_SETUP([IPsec ESP])
 AT_KEYWORDS([esp ipsec])
 AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
-AT_CHECK([ovstest test-esp])
+AT_CHECK([ovstest test-esp], [0], [], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test skiplist])
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index f86fb7a6b..5895e5fb9 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -2932,8 +2932,9 @@
             and authenticated with AES-GCM (RFC 4106) by the datapath
             itself, so the tunnel is only supported by the userspace
             datapath, e.g. with DPDK, and requires Open vSwitch to be built
-            with OpenSSL.  The security associations are configured with
-            ESP specific options.  It interoperates with other IPsec
+            with the Intel IPsec Multi-Buffer library or with OpenSSL.  The
+            security associations are configured with ESP specific options,
+            or by an IKE daemon.  It interoperates with other IPsec
             implementations, such as the Linux kernel's, in tunnel mode.
             </p>
           </dd>
-- 
2.43.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to