wireguard: initial implementation of wireguard protocol 03/28503/36
authorArtem Glazychev <artem.glazychev@xored.com>
Mon, 31 Aug 2020 10:12:30 +0000 (17:12 +0700)
committerDamjan Marion <dmarion@me.com>
Wed, 9 Sep 2020 11:57:48 +0000 (11:57 +0000)
Type: feature

The main information about plugin you can see in README.md

vpp# wireguard ?
  wireguard create                         wireguard create listen-port <port> private-key <key> src <IP> [generate-key]
  wireguard delete                         wireguard delete <interface>
  wireguard peer add                       wireguard peer add <wg_int> public-key <pub_key_other>endpoint <ip4_dst> allowed-ip <prefix>dst-port [port_dst] persistent-keepalive [keepalive_interval]
  wireguard peer remove                    wireguard peer remove <index>

Change-Id: I85eb0bfc033ccfb2045696398d8a108b1c64b8d9
Signed-off-by: Artem Glazychev <artem.glazychev@xored.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
Signed-off-by: Jim Thompson <jim@netgate.com>
Signed-off-by: Neale Ranns <nranns@cisco.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
34 files changed:
MAINTAINERS
src/plugins/wireguard/CMakeLists.txt [new file with mode: 0755]
src/plugins/wireguard/FEATURE.yaml [new file with mode: 0644]
src/plugins/wireguard/README.md [new file with mode: 0755]
src/plugins/wireguard/blake/blake2-impl.h [new file with mode: 0755]
src/plugins/wireguard/blake/blake2s.c [new file with mode: 0755]
src/plugins/wireguard/blake/blake2s.h [new file with mode: 0755]
src/plugins/wireguard/test/test_wireguard.py [new file with mode: 0755]
src/plugins/wireguard/wireguard.api [new file with mode: 0755]
src/plugins/wireguard/wireguard.c [new file with mode: 0755]
src/plugins/wireguard/wireguard.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_api.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_cli.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_cookie.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_cookie.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_if.c [new file with mode: 0644]
src/plugins/wireguard/wireguard_if.h [new file with mode: 0644]
src/plugins/wireguard/wireguard_index_table.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_index_table.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_input.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_key.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_key.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_messages.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_noise.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_noise.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_output_tun.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_peer.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_peer.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_send.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_send.h [new file with mode: 0755]
src/plugins/wireguard/wireguard_timer.c [new file with mode: 0755]
src/plugins/wireguard/wireguard_timer.h [new file with mode: 0755]
test/requirements-3.txt
test/requirements.txt

index 03cd479..e929020 100644 (file)
@@ -688,6 +688,11 @@ M: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
 M:     Neale Ranns <nranns@cisco.com>
 F:     src/plugins/cnat
 
+Plugin - Wireguard
+I:     wireguard
+M:     Artem Glazychev <artem.glazychev@xored.com>
+F:     src/plugins/wireguard
+
 VPP Config Tooling
 I:     vpp_config
 M:     John DeNisco <jdenisco@cisco.com>
diff --git a/src/plugins/wireguard/CMakeLists.txt b/src/plugins/wireguard/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..db5bb2d
--- /dev/null
@@ -0,0 +1,54 @@
+
+# Copyright (c) 2020 Doc.ai 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.
+
+if (OPENSSL_VERSION VERSION_LESS 1.1.0)
+  return()
+endif()
+
+list(APPEND WG_BLAKE_SOURCES
+  blake/blake2s.h
+  blake/blake2s.c
+)
+
+add_vpp_plugin(wireguard
+  SOURCES
+  ${WG_BLAKE_SOURCES}
+  wireguard.c
+  wireguard.h
+  wireguard_if.c
+  wireguard_if.h
+  wireguard_input.c
+  wireguard_output_tun.c
+  wireguard_key.c
+  wireguard_key.h
+  wireguard_cli.c
+  wireguard_messages.h
+  wireguard_noise.c
+  wireguard_noise.h
+  wireguard_send.c
+  wireguard_send.h
+  wireguard_cookie.c
+  wireguard_cookie.h
+  wireguard_peer.c
+  wireguard_peer.h
+  wireguard_timer.c
+  wireguard_timer.h
+  wireguard_index_table.c
+  wireguard_index_table.h
+  wireguard_api.c
+
+  API_FILES
+  wireguard.api
+
+)
diff --git a/src/plugins/wireguard/FEATURE.yaml b/src/plugins/wireguard/FEATURE.yaml
new file mode 100644 (file)
index 0000000..cf8b6d7
--- /dev/null
@@ -0,0 +1,12 @@
+---
+name: Wireguard protocol
+maintainer: Artem Glazychev <artem.glazychev@xored.com>
+features:
+  - "based on wireguard-openbsd implementation: https://git.zx2c4.com/wireguard-openbsd"
+  - creating secure VPN-tunnel
+description: "Wireguard protocol implementation"
+state: development
+properties: [API, CLI]
+missing:
+  - IPv6 support
+  - DoS protection as in the original protocol
diff --git a/src/plugins/wireguard/README.md b/src/plugins/wireguard/README.md
new file mode 100755 (executable)
index 0000000..a11356c
--- /dev/null
@@ -0,0 +1,74 @@
+# Wireguard vpp-plugin
+
+## Overview
+This plugin is an implementation of [wireguard protocol](https://www.wireguard.com/) for VPP. It allows one to create secure VPN tunnels.
+This implementation is based on [wireguard-openbsd](https://git.zx2c4.com/wireguard-openbsd/), using the implementaiton of *ipip-tunnel*.
+
+## Crypto
+
+The crypto protocols:
+
+- blake2s [[Source]](https://github.com/BLAKE2/BLAKE2)
+
+OpenSSL:
+
+- curve25519
+- chachapoly1305
+
+## Plugin usage example
+Usage is very similar to other wireguard implementations.
+
+### Create connection
+Create keys:
+
+```
+> vpp# wg genkey
+> *my_private_key*
+> vpp# wg pubkey <my_private_key>
+> *my_pub_key*
+```
+
+Create tunnel:
+```
+> vpp# create ipip tunnel src <ip4_src> dst <ip4_dst>
+> *tun_name*
+> vpp# set int state <tun_name> up
+> vpp# set int ip address <tun_name> <tun_ip4>
+```
+
+After this we can create wg-device. The UDP port is opened automatically.
+```
+> vpp# wg set device private-key <my_private_key> src-port <my_port>
+```
+
+Now, we can add a peer configuration:
+```
+> vpp# wg set peer public-key <peer_pub_key> endpoint <peer_ip4> allowed-ip <peer_tun_ip4> dst-port <peer_port> tunnel <tun_name> persistent-keepalive <keepalive_interval>
+```
+If you need to add more peers, don't forget to first create another ipip-tunnel.
+Ping.
+```
+> vpp# ping <peer_tun_ip4>
+```
+### Show config
+To show device and all peer configurations:
+```
+> vpp# show wg
+```
+
+### Remove peer
+Peer can be removed by its public-key.
+```
+> vpp# wg remove peer <peer_pub_key>
+```
+This removes the associated ipip tunnel as well
+
+### Clear all connections
+```
+> vpp# wg remove device
+```
+
+## main next steps for improving this implementation
+1. Use all benefits of VPP-engine.
+2. Add IP6 support (currently only supports IPv4))
+3. Add DoS protection as in original protocol (using cookie)
diff --git a/src/plugins/wireguard/blake/blake2-impl.h b/src/plugins/wireguard/blake/blake2-impl.h
new file mode 100755 (executable)
index 0000000..ad60b4a
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>.
+ * 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.
+ */
+
+/*
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+#ifndef __included_crypto_blake2_impl_h__
+#define __included_crypto_blake2_impl_h__
+
+#include <stdint.h>
+#include <string.h>
+#include <vppinfra/byte_order.h>
+
+#if defined(CLIB_ARCH_IS_LITTLE_ENDIAN)
+#define NATIVE_LITTLE_ENDIAN
+#endif
+
+#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
+#if   defined(_MSC_VER)
+#define BLAKE2_INLINE __inline
+#elif defined(__GNUC__)
+#define BLAKE2_INLINE __inline__
+#else
+#define BLAKE2_INLINE
+#endif
+#else
+#define BLAKE2_INLINE inline
+#endif
+
+static BLAKE2_INLINE uint32_t
+load32 (const void *src)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint32_t w;
+  memcpy (&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = (const uint8_t *) src;
+  return ((uint32_t) (p[0]) << 0) |
+    ((uint32_t) (p[1]) << 8) |
+    ((uint32_t) (p[2]) << 16) | ((uint32_t) (p[3]) << 24);
+#endif
+}
+
+static BLAKE2_INLINE uint64_t
+load64 (const void *src)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint64_t w;
+  memcpy (&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = (const uint8_t *) src;
+  return ((uint64_t) (p[0]) << 0) |
+    ((uint64_t) (p[1]) << 8) |
+    ((uint64_t) (p[2]) << 16) |
+    ((uint64_t) (p[3]) << 24) |
+    ((uint64_t) (p[4]) << 32) |
+    ((uint64_t) (p[5]) << 40) |
+    ((uint64_t) (p[6]) << 48) | ((uint64_t) (p[7]) << 56);
+#endif
+}
+
+static BLAKE2_INLINE uint16_t
+load16 (const void *src)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint16_t w;
+  memcpy (&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = (const uint8_t *) src;
+  return (uint16_t) (((uint32_t) (p[0]) << 0) | ((uint32_t) (p[1]) << 8));
+#endif
+}
+
+static BLAKE2_INLINE void
+store16 (void *dst, uint16_t w)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy (dst, &w, sizeof w);
+#else
+  uint8_t *p = (uint8_t *) dst;
+  *p++ = (uint8_t) w;
+  w >>= 8;
+  *p++ = (uint8_t) w;
+#endif
+}
+
+static BLAKE2_INLINE void
+store32 (void *dst, uint32_t w)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy (dst, &w, sizeof w);
+#else
+  uint8_t *p = (uint8_t *) dst;
+  p[0] = (uint8_t) (w >> 0);
+  p[1] = (uint8_t) (w >> 8);
+  p[2] = (uint8_t) (w >> 16);
+  p[3] = (uint8_t) (w >> 24);
+#endif
+}
+
+static BLAKE2_INLINE void
+store64 (void *dst, uint64_t w)
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy (dst, &w, sizeof w);
+#else
+  uint8_t *p = (uint8_t *) dst;
+  p[0] = (uint8_t) (w >> 0);
+  p[1] = (uint8_t) (w >> 8);
+  p[2] = (uint8_t) (w >> 16);
+  p[3] = (uint8_t) (w >> 24);
+  p[4] = (uint8_t) (w >> 32);
+  p[5] = (uint8_t) (w >> 40);
+  p[6] = (uint8_t) (w >> 48);
+  p[7] = (uint8_t) (w >> 56);
+#endif
+}
+
+static BLAKE2_INLINE uint64_t
+load48 (const void *src)
+{
+  const uint8_t *p = (const uint8_t *) src;
+  return ((uint64_t) (p[0]) << 0) |
+    ((uint64_t) (p[1]) << 8) |
+    ((uint64_t) (p[2]) << 16) |
+    ((uint64_t) (p[3]) << 24) |
+    ((uint64_t) (p[4]) << 32) | ((uint64_t) (p[5]) << 40);
+}
+
+static BLAKE2_INLINE void
+store48 (void *dst, uint64_t w)
+{
+  uint8_t *p = (uint8_t *) dst;
+  p[0] = (uint8_t) (w >> 0);
+  p[1] = (uint8_t) (w >> 8);
+  p[2] = (uint8_t) (w >> 16);
+  p[3] = (uint8_t) (w >> 24);
+  p[4] = (uint8_t) (w >> 32);
+  p[5] = (uint8_t) (w >> 40);
+}
+
+static BLAKE2_INLINE uint32_t
+rotr32 (const uint32_t w, const unsigned c)
+{
+  return (w >> c) | (w << (32 - c));
+}
+
+static BLAKE2_INLINE uint64_t
+rotr64 (const uint64_t w, const unsigned c)
+{
+  return (w >> c) | (w << (64 - c));
+}
+
+/* prevents compiler optimizing out memset() */
+static BLAKE2_INLINE void
+secure_zero_memory (void *v, size_t n)
+{
+  static void *(*const volatile memset_v) (void *, int, size_t) = &memset;
+  memset_v (v, 0, n);
+}
+
+#endif //__included_crypto_blake2_impl_h__
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/blake/blake2s.c b/src/plugins/wireguard/blake/blake2s.c
new file mode 100755 (executable)
index 0000000..3ff312a
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>.
+ * 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.
+ */
+/*
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <wireguard/blake/blake2s.h>
+#include "blake2-impl.h"
+
+static const uint32_t blake2s_IV[8] = {
+  0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
+  0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
+};
+
+static const uint8_t blake2s_sigma[10][16] = {
+  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+  {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
+  {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
+  {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
+  {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
+  {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
+  {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
+  {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
+  {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
+  {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
+};
+
+static void
+blake2s_set_lastnode (blake2s_state_t * S)
+{
+  S->f[1] = (uint32_t) - 1;
+}
+
+/* Some helper functions, not necessarily useful */
+static int
+blake2s_is_lastblock (const blake2s_state_t * S)
+{
+  return S->f[0] != 0;
+}
+
+static void
+blake2s_set_lastblock (blake2s_state_t * S)
+{
+  if (S->last_node)
+    blake2s_set_lastnode (S);
+
+  S->f[0] = (uint32_t) - 1;
+}
+
+static void
+blake2s_increment_counter (blake2s_state_t * S, const uint32_t inc)
+{
+  S->t[0] += inc;
+  S->t[1] += (S->t[0] < inc);
+}
+
+static void
+blake2s_init0 (blake2s_state_t * S)
+{
+  size_t i;
+  memset (S, 0, sizeof (blake2s_state_t));
+
+  for (i = 0; i < 8; ++i)
+    S->h[i] = blake2s_IV[i];
+}
+
+/* init2 xors IV with input parameter block */
+int
+blake2s_init_param (blake2s_state_t * S, const blake2s_param_t * P)
+{
+  const unsigned char *p = (const unsigned char *) (P);
+  size_t i;
+
+  blake2s_init0 (S);
+
+  /* IV XOR ParamBlock */
+  for (i = 0; i < 8; ++i)
+    S->h[i] ^= load32 (&p[i * 4]);
+
+  S->outlen = P->digest_length;
+  return 0;
+}
+
+
+/* Sequential blake2s initialization */
+int
+blake2s_init (blake2s_state_t * S, size_t outlen)
+{
+  blake2s_param_t P[1];
+
+  /* Move interval verification here? */
+  if ((!outlen) || (outlen > BLAKE2S_OUT_BYTES))
+    return -1;
+
+  P->digest_length = (uint8_t) outlen;
+  P->key_length = 0;
+  P->fanout = 1;
+  P->depth = 1;
+  store32 (&P->leaf_length, 0);
+  store32 (&P->node_offset, 0);
+  store16 (&P->xof_length, 0);
+  P->node_depth = 0;
+  P->inner_length = 0;
+  /* memset(P->reserved, 0, sizeof(P->reserved) ); */
+  memset (P->salt, 0, sizeof (P->salt));
+  memset (P->personal, 0, sizeof (P->personal));
+  return blake2s_init_param (S, P);
+}
+
+int
+blake2s_init_key (blake2s_state_t * S, size_t outlen, const void *key,
+                 size_t keylen)
+{
+  blake2s_param_t P[1];
+
+  if ((!outlen) || (outlen > BLAKE2S_OUT_BYTES))
+    return -1;
+
+  if (!key || !keylen || keylen > BLAKE2S_KEY_BYTES)
+    return -1;
+
+  P->digest_length = (uint8_t) outlen;
+  P->key_length = (uint8_t) keylen;
+  P->fanout = 1;
+  P->depth = 1;
+  store32 (&P->leaf_length, 0);
+  store32 (&P->node_offset, 0);
+  store16 (&P->xof_length, 0);
+  P->node_depth = 0;
+  P->inner_length = 0;
+  /* memset(P->reserved, 0, sizeof(P->reserved) ); */
+  memset (P->salt, 0, sizeof (P->salt));
+  memset (P->personal, 0, sizeof (P->personal));
+
+  if (blake2s_init_param (S, P) < 0)
+    return -1;
+
+  {
+    uint8_t block[BLAKE2S_BLOCK_BYTES];
+    memset (block, 0, BLAKE2S_BLOCK_BYTES);
+    memcpy (block, key, keylen);
+    blake2s_update (S, block, BLAKE2S_BLOCK_BYTES);
+    secure_zero_memory (block, BLAKE2S_BLOCK_BYTES);   /* Burn the key from stack */
+  }
+  return 0;
+}
+
+#define G(r,i,a,b,c,d)                      \
+  do {                                      \
+    a = a + b + m[blake2s_sigma[r][2*i+0]]; \
+    d = rotr32(d ^ a, 16);                  \
+    c = c + d;                              \
+    b = rotr32(b ^ c, 12);                  \
+    a = a + b + m[blake2s_sigma[r][2*i+1]]; \
+    d = rotr32(d ^ a, 8);                   \
+    c = c + d;                              \
+    b = rotr32(b ^ c, 7);                   \
+  } while(0)
+
+#define ROUND(r)                    \
+  do {                              \
+    G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \
+    G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \
+    G(r,2,v[ 2],v[ 6],v[10],v[14]); \
+    G(r,3,v[ 3],v[ 7],v[11],v[15]); \
+    G(r,4,v[ 0],v[ 5],v[10],v[15]); \
+    G(r,5,v[ 1],v[ 6],v[11],v[12]); \
+    G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \
+    G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \
+  } while(0)
+
+static void
+blake2s_compress (blake2s_state_t * S, const uint8_t in[BLAKE2S_BLOCK_BYTES])
+{
+  uint32_t m[16];
+  uint32_t v[16];
+  size_t i;
+
+  for (i = 0; i < 16; ++i)
+    {
+      m[i] = load32 (in + i * sizeof (m[i]));
+    }
+
+  for (i = 0; i < 8; ++i)
+    {
+      v[i] = S->h[i];
+    }
+
+  v[8] = blake2s_IV[0];
+  v[9] = blake2s_IV[1];
+  v[10] = blake2s_IV[2];
+  v[11] = blake2s_IV[3];
+  v[12] = S->t[0] ^ blake2s_IV[4];
+  v[13] = S->t[1] ^ blake2s_IV[5];
+  v[14] = S->f[0] ^ blake2s_IV[6];
+  v[15] = S->f[1] ^ blake2s_IV[7];
+
+  ROUND (0);
+  ROUND (1);
+  ROUND (2);
+  ROUND (3);
+  ROUND (4);
+  ROUND (5);
+  ROUND (6);
+  ROUND (7);
+  ROUND (8);
+  ROUND (9);
+
+  for (i = 0; i < 8; ++i)
+    {
+      S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
+    }
+}
+
+#undef G
+#undef ROUND
+
+int
+blake2s_update (blake2s_state_t * S, const void *pin, size_t inlen)
+{
+  const unsigned char *in = (const unsigned char *) pin;
+  if (inlen > 0)
+    {
+      size_t left = S->buflen;
+      size_t fill = BLAKE2S_BLOCK_BYTES - left;
+      if (inlen > fill)
+       {
+         S->buflen = 0;
+         memcpy (S->buf + left, in, fill);     /* Fill buffer */
+         blake2s_increment_counter (S, BLAKE2S_BLOCK_BYTES);
+         blake2s_compress (S, S->buf); /* Compress */
+         in += fill;
+         inlen -= fill;
+         while (inlen > BLAKE2S_BLOCK_BYTES)
+           {
+             blake2s_increment_counter (S, BLAKE2S_BLOCK_BYTES);
+             blake2s_compress (S, in);
+             in += BLAKE2S_BLOCK_BYTES;
+             inlen -= BLAKE2S_BLOCK_BYTES;
+           }
+       }
+      memcpy (S->buf + S->buflen, in, inlen);
+      S->buflen += inlen;
+    }
+  return 0;
+}
+
+int
+blake2s_final (blake2s_state_t * S, void *out, size_t outlen)
+{
+  uint8_t buffer[BLAKE2S_OUT_BYTES] = { 0 };
+  size_t i;
+
+  if (out == NULL || outlen < S->outlen)
+    return -1;
+
+  if (blake2s_is_lastblock (S))
+    return -1;
+
+  blake2s_increment_counter (S, (uint32_t) S->buflen);
+  blake2s_set_lastblock (S);
+  memset (S->buf + S->buflen, 0, BLAKE2S_BLOCK_BYTES - S->buflen);     /* Padding */
+  blake2s_compress (S, S->buf);
+
+  for (i = 0; i < 8; ++i)      /* Output full hash to temp buffer */
+    store32 (buffer + sizeof (S->h[i]) * i, S->h[i]);
+
+  memcpy (out, buffer, outlen);
+  secure_zero_memory (buffer, sizeof (buffer));
+  return 0;
+}
+
+int
+blake2s (void *out, size_t outlen, const void *in, size_t inlen,
+        const void *key, size_t keylen)
+{
+  blake2s_state_t S[1];
+
+  /* Verify parameters */
+  if (NULL == in && inlen > 0)
+    return -1;
+
+  if (NULL == out)
+    return -1;
+
+  if (NULL == key && keylen > 0)
+    return -1;
+
+  if (!outlen || outlen > BLAKE2S_OUT_BYTES)
+    return -1;
+
+  if (keylen > BLAKE2S_KEY_BYTES)
+    return -1;
+
+  if (keylen > 0)
+    {
+      if (blake2s_init_key (S, outlen, key, keylen) < 0)
+       return -1;
+    }
+  else
+    {
+      if (blake2s_init (S, outlen) < 0)
+       return -1;
+    }
+
+  blake2s_update (S, (const uint8_t *) in, inlen);
+  blake2s_final (S, out, outlen);
+  return 0;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/blake/blake2s.h b/src/plugins/wireguard/blake/blake2s.h
new file mode 100755 (executable)
index 0000000..37da0ac
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2012 Samuel Neves <sneves@dei.uc.pt>.
+ * 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.
+ */
+/*
+   More information about the BLAKE2 hash function can be found at
+   https://blake2.net.
+*/
+
+#ifndef __included_crypto_blake2s_h__
+#define __included_crypto_blake2s_h__
+
+#include <vlib/vlib.h>
+
+#if defined(_MSC_VER)
+#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop))
+#else
+#define BLAKE2_PACKED(x) x __attribute__((packed))
+#endif
+
+enum blake2s_constant
+{
+  BLAKE2S_BLOCK_BYTES = 64,
+  BLAKE2S_OUT_BYTES = 32,
+  BLAKE2S_KEY_BYTES = 32,
+  BLAKE2S_HASH_SIZE = BLAKE2S_OUT_BYTES,
+  BLAKE2S_SALT_BYTES = 8,
+  BLAKE2S_PERSONAL_BYTES = 8
+};
+
+typedef struct blake2s_state
+{
+  uint32_t h[8];
+  uint32_t t[2];
+  uint32_t f[2];
+  uint8_t buf[BLAKE2S_BLOCK_BYTES];
+  size_t buflen;
+  size_t outlen;
+  uint8_t last_node;
+} blake2s_state_t;
+
+BLAKE2_PACKED (struct blake2s_param
+              {
+              uint8_t digest_length;   /* 1 */
+              uint8_t key_length;      /* 2 */
+              uint8_t fanout;  /* 3 */
+              uint8_t depth;   /* 4 */
+              uint32_t leaf_length;    /* 8 */
+              uint32_t node_offset;    /* 12 */
+              uint16_t xof_length;     /* 14 */
+              uint8_t node_depth;      /* 15 */
+              uint8_t inner_length;    /* 16 */
+              /* uint8_t  reserved[0]; */
+              uint8_t salt[BLAKE2S_SALT_BYTES];        /* 24 */
+              uint8_t personal[BLAKE2S_PERSONAL_BYTES];        /* 32 */
+              });
+
+typedef struct blake2s_param blake2s_param_t;
+
+/* Streaming API */
+int blake2s_init (blake2s_state_t * S, size_t outlen);
+int blake2s_init_key (blake2s_state_t * S, size_t outlen, const void *key,
+                     size_t keylen);
+int blake2s_init_param (blake2s_state_t * S, const blake2s_param_t * P);
+int blake2s_update (blake2s_state_t * S, const void *in, size_t inlen);
+int blake2s_final (blake2s_state_t * S, void *out, size_t outlen);
+
+int blake2s (void *out, size_t outlen, const void *in, size_t inlen,
+            const void *key, size_t keylen);
+
+#endif /* __included_crypto_blake2s_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/test/test_wireguard.py b/src/plugins/wireguard/test/test_wireguard.py
new file mode 100755 (executable)
index 0000000..cd124f3
--- /dev/null
@@ -0,0 +1,292 @@
+#!/usr/bin/env python3
+""" Wg tests """
+
+from scapy.packet import Packet
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from scapy.contrib.wireguard import Wireguard, WireguardResponse, \
+    WireguardInitiation
+from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
+from cryptography.hazmat.primitives.serialization import Encoding, \
+    PrivateFormat, PublicFormat, NoEncryption
+
+from vpp_ipip_tun_interface import VppIpIpTunInterface
+from vpp_interface import VppInterface
+from vpp_object import VppObject
+from framework import VppTestCase
+from re import compile
+import unittest
+
+""" TestWg is a subclass of  VPPTestCase classes.
+
+Wg test.
+
+"""
+
+
+class VppWgInterface(VppInterface):
+    """
+    VPP WireGuard interface
+    """
+
+    def __init__(self, test, src, port, key=None):
+        super(VppWgInterface, self).__init__(test)
+
+        self.key = key
+        if not self.key:
+            self.generate = True
+        else:
+            self.generate = False
+        self.port = port
+        self.src = src
+
+    def add_vpp_config(self):
+        r = self.test.vapi.wireguard_interface_create(interface={
+            'user_instance': 0xffffffff,
+            'port': self.port,
+            'src_ip': self.src,
+            'private_key': self.key_bytes()
+        })
+        self.set_sw_if_index(r.sw_if_index)
+        self.test.registry.register(self, self.test.logger)
+        return self
+
+    def key_bytes(self):
+        if self.key:
+            return self.key.private_bytes(Encoding.Raw,
+                                          PrivateFormat.Raw,
+                                          NoEncryption())
+        else:
+            return bytearray(32)
+
+    def remove_vpp_config(self):
+        self.test.vapi.wireguard_interface_delete(
+            sw_if_index=self._sw_if_index)
+
+    def query_vpp_config(self):
+        ts = self.test.vapi.wireguard_interface_dump(sw_if_index=0xffffffff)
+        for t in ts:
+            if t.interface.sw_if_index == self._sw_if_index and \
+               str(t.interface.src_ip) == self.src and \
+               t.interface.port == self.port and \
+               t.interface.private_key == self.key_bytes():
+                return True
+        return False
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return "wireguard-%d" % self._sw_if_index
+
+
+def find_route(test, prefix, table_id=0):
+    routes = test.vapi.ip_route_dump(table_id, False)
+
+    for e in routes:
+        if table_id == e.route.table_id \
+           and str(e.route.prefix) == str(prefix):
+            return True
+    return False
+
+
+class VppWgPeer(VppObject):
+
+    def __init__(self,
+                 test,
+                 itf,
+                 endpoint,
+                 port,
+                 allowed_ips,
+                 persistent_keepalive=15):
+        self._test = test
+        self.itf = itf
+        self.endpoint = endpoint
+        self.port = port
+        self.allowed_ips = allowed_ips
+        self.persistent_keepalive = persistent_keepalive
+        self.private_key = X25519PrivateKey.generate()
+        self.public_key = self.private_key.public_key()
+        self.hash = bytearray(16)
+
+    def validate_routing(self):
+        for a in self.allowed_ips:
+            self._test.assertTrue(find_route(self._test, a))
+
+    def validate_no_routing(self):
+        for a in self.allowed_ips:
+            self._test.assertFalse(find_route(self._test, a))
+
+    def add_vpp_config(self):
+        rv = self._test.vapi.wireguard_peer_add(
+            peer={
+                'public_key': self.public_key_bytes(),
+                'port': self.port,
+                'endpoint': self.endpoint,
+                'n_allowed_ips': len(self.allowed_ips),
+                'allowed_ips': self.allowed_ips,
+                'sw_if_index': self.itf.sw_if_index,
+                'persistent_keepalive': self.persistent_keepalive})
+        self.index = rv.peer_index
+        self._test.registry.register(self, self._test.logger)
+        self.validate_routing()
+        return self
+
+    def remove_vpp_config(self):
+        self._test.vapi.wireguard_peer_remove(peer_index=self.index)
+        self.validate_no_routing()
+
+    def object_id(self):
+        return ("wireguard-peer-%s" % self.index)
+
+    def public_key_bytes(self):
+        return self.public_key.public_bytes(Encoding.Raw,
+                                            PublicFormat.Raw)
+
+    def private_key_bytes(self):
+        return self.private_key.private_bytes(Encoding.Raw,
+                                              PrivateFormat.Raw,
+                                              NoEncryption())
+
+    def query_vpp_config(self):
+        peers = self._test.vapi.wireguard_peers_dump()
+
+        for p in peers:
+            if p.peer.public_key == self.public_key_bytes() and \
+               p.peer.port == self.port and \
+               str(p.peer.endpoint) == self.endpoint and \
+               p.peer.sw_if_index == self.itf.sw_if_index and \
+               len(self.allowed_ips) == p.peer.n_allowed_ips:
+                self.allowed_ips.sort()
+                p.peer.allowed_ips.sort()
+
+                for (a1, a2) in zip(self.allowed_ips, p.peer.allowed_ips):
+                    if str(a1) != str(a2):
+                        return False
+                return True
+        return False
+
+
+class TestWg(VppTestCase):
+    """ Wireguard Test Case """
+
+    error_str = compile(r"Error")
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestWg, cls).setUpClass()
+        try:
+            cls.create_pg_interfaces(range(3))
+            for i in cls.pg_interfaces:
+                i.admin_up()
+                i.config_ip4()
+                i.resolve_arp()
+
+        except Exception:
+            super(TestWg, cls).tearDownClass()
+            raise
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestWg, cls).tearDownClass()
+
+    def test_wg_interface(self):
+        port = 12312
+
+        # Create interface
+        wg0 = VppWgInterface(self,
+                             self.pg1.local_ip4,
+                             port).add_vpp_config()
+
+        self.logger.info(self.vapi.cli("sh int"))
+
+        # delete interface
+        wg0.remove_vpp_config()
+
+    def test_wg_peer(self):
+        wg_output_node_name = '/err/wg-output-tun/'
+        wg_input_node_name = '/err/wg-input/'
+
+        port = 12323
+
+        # Create interfaces
+        wg0 = VppWgInterface(self,
+                             self.pg1.local_ip4,
+                             port,
+                             key=X25519PrivateKey.generate()).add_vpp_config()
+        wg1 = VppWgInterface(self,
+                             self.pg2.local_ip4,
+                             port+1).add_vpp_config()
+        wg0.admin_up()
+        wg1.admin_up()
+
+        # Check peer counter
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        peer_1 = VppWgPeer(self,
+                           wg0,
+                           self.pg1.remote_ip4,
+                           port+1,
+                           ["10.11.2.0/24",
+                            "10.11.3.0/24"]).add_vpp_config()
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
+
+        # wait for the peer to send a handshake
+        capture = self.pg1.get_capture(1, timeout=2)
+        handshake = capture[0]
+
+        self.assertEqual(handshake[IP].src, wg0.src)
+        self.assertEqual(handshake[IP].dst, peer_1.endpoint)
+        self.assertEqual(handshake[UDP].sport, wg0.port)
+        self.assertEqual(handshake[UDP].dport, peer_1.port)
+        handshake = Wireguard(handshake[Raw])
+        self.assertEqual(handshake.message_type, 1)  # "initiate")
+        init = handshake[WireguardInitiation]
+
+        # route a packet into the wg interface
+        #  use the allowed-ip prefix
+        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+             IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
+             UDP(sport=555, dport=556) /
+             Raw())
+        # rx = self.send_and_expect(self.pg0, [p], self.pg1)
+        rx = self.send_and_assert_no_replies(self.pg0, [p])
+
+        self.logger.info(self.vapi.cli("sh error"))
+        init_sent = wg_output_node_name + "Keypair error"
+        self.assertEqual(1, self.statistics.get_err_counter(init_sent))
+
+        # Create many peers on sencond interface
+        NUM_PEERS = 16
+        self.pg2.generate_remote_hosts(NUM_PEERS)
+        self.pg2.configure_ipv4_neighbors()
+
+        peers = []
+        for i in range(NUM_PEERS):
+            peers.append(VppWgPeer(self,
+                                   wg1,
+                                   self.pg2.remote_hosts[i].ip4,
+                                   port+1+i,
+                                   ["10.10.%d.4/32" % i]).add_vpp_config())
+            self.assertEqual(len(self.vapi.wireguard_peers_dump()), i+2)
+
+        self.logger.info(self.vapi.cli("show wireguard peer"))
+        self.logger.info(self.vapi.cli("show wireguard interface"))
+        self.logger.info(self.vapi.cli("show adj 37"))
+        self.logger.info(self.vapi.cli("sh ip fib 172.16.3.17"))
+        self.logger.info(self.vapi.cli("sh ip fib 10.11.3.0"))
+
+        # remove peers
+        for p in peers:
+            self.assertTrue(p.query_vpp_config())
+            p.remove_vpp_config()
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
+        peer_1.remove_vpp_config()
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0)
+
+        wg0.remove_vpp_config()
+        # wg1.remove_vpp_config()
diff --git a/src/plugins/wireguard/wireguard.api b/src/plugins/wireguard/wireguard.api
new file mode 100755 (executable)
index 0000000..195755e
--- /dev/null
@@ -0,0 +1,162 @@
+/* Hey Emacs use -*- mode: C -*- */
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+option version = "0.1.0";
+
+import "vnet/interface_types.api";
+import "vnet/ip/ip_types.api";
+
+/** \brief Create wireguard interface
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param private_key - private key in binary format of this device
+    @param port - port of this device
+    @param src_ip - packet sent through this interface us this
+                    address as the IP source.
+*/
+typedef wireguard_interface
+{
+  u32 user_instance [default=0xffffffff];
+  vl_api_interface_index_t sw_if_index;
+  u8 private_key[32];
+  u16 port;
+  vl_api_address_t src_ip;
+};
+
+/** \brief Create an Wireguard interface
+ */
+define wireguard_interface_create {
+  u32 client_index;
+  u32 context;
+  vl_api_wireguard_interface_t interface;
+  bool generate_key;
+};
+
+/** \brief Add Wireguard interface interface response
+    @param context - sender context, to match reply w/ request
+    @param retval - return status
+    @param sw_if_index - sw_if_index of new interface (for successful add)
+*/
+define wireguard_interface_create_reply
+{
+  u32 context;
+  i32 retval;
+  vl_api_interface_index_t sw_if_index;
+};
+
+autoreply define wireguard_interface_delete
+{
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+};
+
+define wireguard_interface_dump
+{
+  u32 client_index;
+  u32 context;
+  bool show_private_key;
+  vl_api_interface_index_t sw_if_index;
+};
+
+define wireguard_interface_details
+{
+  u32 context;
+  vl_api_wireguard_interface_t interface;
+};
+
+enum wireguard_peer_flags : u8
+{
+  WIREGUARD_PEER_STATUS_DEAD = 0x1,
+};
+
+/** \brief Create new peer
+    @param public_key - public key (in binary format) of destination peer
+    @param port - destination port
+    @param table_id - The IP table in which 'endpoint' is reachable
+    @param endpoint - destination ip
+    @param allowed_ip - allowed incoming ip tunnel
+    @param tun_sw_if_index - tunnel interface
+    @param persistent_keepalive - keepalive packet timeout
+*/
+typedef wireguard_peer
+{
+  u8 public_key[32];
+  u16 port;
+  u16 persistent_keepalive;
+  u32 table_id;
+  vl_api_address_t endpoint;
+  vl_api_interface_index_t sw_if_index;
+  vl_api_wireguard_peer_flags_t flags;
+  u8 n_allowed_ips;
+  vl_api_prefix_t allowed_ips[n_allowed_ips];
+};
+
+/** \brief Create new peer
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param peer - peer to create
+*/
+define wireguard_peer_add
+{
+  u32 client_index;
+  u32 context;
+  vl_api_wireguard_peer_t peer;
+};
+define wireguard_peer_add_reply
+{
+  u32 context;
+  i32 retval;
+  u32 peer_index;
+};
+
+/** \brief Remove peer by public_key
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param public_key
+*/
+autoreply define wireguard_peer_remove
+{
+  u32 client_index;
+  u32 context;
+  u32 peer_index;
+};
+
+/** \brief Dump all peers
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define wireguard_peers_dump {
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Dump peers response
+    @param context - sender context, to match reply w/ request
+    @param is_dead - is peer valid yet
+    @param public_key - peer public_key
+    @param ip4_address - ip4 endpoint address
+*/
+define wireguard_peers_details {
+  u32 context;
+  vl_api_wireguard_peer_t peer;
+};
+
+/*
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard.c b/src/plugins/wireguard/wireguard.c
new file mode 100755 (executable)
index 0000000..0092181
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vnet/ipip/ipip.h>
+#include <vpp/app/version.h>
+#include <vnet/udp/udp.h>
+
+#include <wireguard/wireguard_send.h>
+#include <wireguard/wireguard_key.h>
+#include <wireguard/wireguard_if.h>
+#include <wireguard/wireguard.h>
+
+wg_main_t wg_main;
+
+static clib_error_t *
+wg_init (vlib_main_t * vm)
+{
+  wg_main_t *wmp = &wg_main;
+
+  wmp->vlib_main = vm;
+  wmp->peers = 0;
+
+  return (NULL);
+}
+
+VLIB_INIT_FUNCTION (wg_init);
+
+/* *INDENT-OFF* */
+
+VNET_FEATURE_INIT (wg_output_tun, static) =
+{
+  .arc_name = "ip4-output",
+  .node_name = "wg-output-tun",
+};
+
+VLIB_PLUGIN_REGISTER () =
+{
+  .version = VPP_BUILD_VER,
+  .description = "Wireguard Protocol",
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard.h b/src/plugins/wireguard/wireguard.h
new file mode 100755 (executable)
index 0000000..70a692e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+#ifndef __included_wg_h__
+#define __included_wg_h__
+
+#include <wireguard/wireguard_index_table.h>
+#include <wireguard/wireguard_messages.h>
+#include <wireguard/wireguard_peer.h>
+
+extern vlib_node_registration_t wg_input_node;
+extern vlib_node_registration_t wg_output_tun_node;
+
+
+
+typedef struct
+{
+  /* convenience */
+  vlib_main_t *vlib_main;
+
+  u16 msg_id_base;
+
+  // Peers pool
+  wg_peer_t *peers;
+  wg_index_table_t index_table;
+
+} wg_main_t;
+
+extern wg_main_t wg_main;
+
+#endif /* __included_wg_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_api.c b/src/plugins/wireguard/wireguard_api.c
new file mode 100755 (executable)
index 0000000..e107cb5
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vnet/vnet.h>
+#include <vlibmemory/api.h>
+
+#include <vnet/format_fns.h>
+#include <vnet/ip/ip_types_api.h>
+#include <vlibapi/api.h>
+
+#include <wireguard/wireguard.api_enum.h>
+#include <wireguard/wireguard.api_types.h>
+
+#include <wireguard/wireguard_key.h>
+#include <wireguard/wireguard.h>
+#include <wireguard/wireguard_if.h>
+#include <wireguard/wireguard_peer.h>
+
+#define REPLY_MSG_ID_BASE wmp->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+static void
+  vl_api_wireguard_interface_create_t_handler
+  (vl_api_wireguard_interface_create_t * mp)
+{
+  vl_api_wireguard_interface_create_reply_t *rmp;
+  wg_main_t *wmp = &wg_main;
+  u8 private_key[NOISE_PUBLIC_KEY_LEN];
+  ip_address_t src;
+  u32 sw_if_index;
+  int rv = 0;
+
+  ip_address_decode2 (&mp->interface.src_ip, &src);
+
+  if (AF_IP6 == ip_addr_version (&src))
+    rv = VNET_API_ERROR_INVALID_PROTOCOL;
+  else
+    {
+      if (mp->generate_key)
+       curve25519_gen_secret (private_key);
+      else
+       clib_memcpy (private_key, mp->interface.private_key,
+                    NOISE_PUBLIC_KEY_LEN);
+
+      rv = wg_if_create (ntohl (mp->interface.user_instance), private_key,
+                        ntohs (mp->interface.port), &src, &sw_if_index);
+    }
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO2(VL_API_WIREGUARD_INTERFACE_CREATE_REPLY,
+  {
+    rmp->sw_if_index = htonl(sw_if_index);
+  });
+  /* *INDENT-ON* */
+}
+
+static void
+  vl_api_wireguard_interface_delete_t_handler
+  (vl_api_wireguard_interface_delete_t * mp)
+{
+  vl_api_wireguard_interface_delete_reply_t *rmp;
+  wg_main_t *wmp = &wg_main;
+  int rv = 0;
+
+  VALIDATE_SW_IF_INDEX (mp);
+
+  rv = wg_if_delete (ntohl (mp->sw_if_index));
+
+  BAD_SW_IF_INDEX_LABEL;
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO(VL_API_WIREGUARD_INTERFACE_DELETE_REPLY);
+  /* *INDENT-ON* */
+}
+
+typedef struct wg_deatils_walk_t_
+{
+  vl_api_registration_t *reg;
+  u32 context;
+} wg_deatils_walk_t;
+
+static walk_rc_t
+wireguard_if_send_details (index_t wgii, void *data)
+{
+  vl_api_wireguard_interface_details_t *rmp;
+  wg_deatils_walk_t *ctx = data;
+  const wg_if_t *wgi;
+
+  wgi = wg_if_get (wgii);
+
+  rmp = vl_msg_api_alloc_zero (sizeof (*rmp));
+  rmp->_vl_msg_id = htons (VL_API_WIREGUARD_INTERFACE_DETAILS +
+                          wg_main.msg_id_base);
+
+  clib_memcpy (rmp->interface.private_key,
+              wgi->local.l_private, NOISE_PUBLIC_KEY_LEN);
+  rmp->interface.sw_if_index = htonl (wgi->sw_if_index);
+  rmp->interface.port = htons (wgi->port);
+  ip_address_encode2 (&wgi->src_ip, &rmp->interface.src_ip);
+
+  rmp->context = ctx->context;
+
+  vl_api_send_msg (ctx->reg, (u8 *) rmp);
+
+  return (WALK_CONTINUE);
+}
+
+static void
+vl_api_wireguard_interface_dump_t_handler (vl_api_wireguard_interface_dump_t *
+                                          mp)
+{
+  vl_api_registration_t *reg;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+  if (reg == 0)
+    return;
+
+  wg_deatils_walk_t ctx = {
+    .reg = reg,
+    .context = mp->context,
+  };
+
+  wg_if_walk (wireguard_if_send_details, &ctx);
+}
+
+static void
+vl_api_wireguard_peer_add_t_handler (vl_api_wireguard_peer_add_t * mp)
+{
+  vl_api_wireguard_peer_add_reply_t *rmp;
+  wg_main_t *wmp = &wg_main;
+  index_t peeri;
+  int ii, rv = 0;
+
+  ip_address_t endpoint;
+  fib_prefix_t *allowed_ips = NULL;
+
+  VALIDATE_SW_IF_INDEX (&(mp->peer));
+
+  if (0 == mp->peer.n_allowed_ips)
+    {
+      rv = VNET_API_ERROR_INVALID_VALUE;
+      goto done;
+    }
+
+  vec_validate (allowed_ips, mp->peer.n_allowed_ips - 1);
+  ip_address_decode2 (&mp->peer.endpoint, &endpoint);
+
+  for (ii = 0; ii < mp->peer.n_allowed_ips; ii++)
+    ip_prefix_decode (&mp->peer.allowed_ips[ii], &allowed_ips[ii]);
+
+  if (AF_IP6 == ip_addr_version (&endpoint) ||
+      FIB_PROTOCOL_IP6 == allowed_ips[0].fp_proto)
+    /* ip6 currently not supported, but the API needs to support it
+     * else we'll need to change it later, and that's a PITA */
+    rv = VNET_API_ERROR_INVALID_PROTOCOL;
+  else
+    rv = wg_peer_add (ntohl (mp->peer.sw_if_index),
+                     mp->peer.public_key,
+                     ntohl (mp->peer.table_id),
+                     &ip_addr_46 (&endpoint),
+                     allowed_ips,
+                     ntohs (mp->peer.port),
+                     ntohs (mp->peer.persistent_keepalive), &peeri);
+
+  vec_free (allowed_ips);
+done:
+  BAD_SW_IF_INDEX_LABEL;
+  /* *INDENT-OFF* */
+  REPLY_MACRO2(VL_API_WIREGUARD_PEER_ADD_REPLY,
+  {
+    rmp->peer_index = ntohl (peeri);
+  });
+  /* *INDENT-ON* */
+}
+
+static void
+vl_api_wireguard_peer_remove_t_handler (vl_api_wireguard_peer_remove_t * mp)
+{
+  vl_api_wireguard_peer_remove_reply_t *rmp;
+  wg_main_t *wmp = &wg_main;
+  int rv = 0;
+
+  rv = wg_peer_remove (ntohl (mp->peer_index));
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO(VL_API_WIREGUARD_PEER_REMOVE_REPLY);
+  /* *INDENT-ON* */
+}
+
+static walk_rc_t
+send_wg_peers_details (index_t peeri, void *data)
+{
+  vl_api_wireguard_peers_details_t *rmp;
+  wg_deatils_walk_t *ctx = data;
+  const wg_peer_t *peer;
+  u8 n_allowed_ips;
+  size_t ss;
+
+  peer = wg_peer_get (peeri);
+  n_allowed_ips = vec_len (peer->allowed_ips);
+
+  ss = (sizeof (*rmp) + (n_allowed_ips * sizeof (rmp->peer.allowed_ips[0])));
+
+  rmp = vl_msg_api_alloc_zero (ss);
+
+  rmp->_vl_msg_id = htons (VL_API_WIREGUARD_PEERS_DETAILS +
+                          wg_main.msg_id_base);
+
+  if (peer->is_dead)
+    rmp->peer.flags = WIREGUARD_PEER_STATUS_DEAD;
+  clib_memcpy (rmp->peer.public_key,
+              peer->remote.r_public, NOISE_PUBLIC_KEY_LEN);
+
+  ip_address_encode (&peer->dst.addr, IP46_TYPE_ANY, &rmp->peer.endpoint);
+  rmp->peer.port = htons (peer->dst.port);
+  rmp->peer.n_allowed_ips = n_allowed_ips;
+  rmp->peer.sw_if_index = htonl (peer->wg_sw_if_index);
+
+  int ii;
+  for (ii = 0; ii < n_allowed_ips; ii++)
+    ip_prefix_encode (&peer->allowed_ips[ii].prefix,
+                     &rmp->peer.allowed_ips[ii]);
+
+  rmp->context = ctx->context;
+
+  vl_api_send_msg (ctx->reg, (u8 *) rmp);
+
+  return (WALK_CONTINUE);
+}
+
+static void
+vl_api_wireguard_peers_dump_t_handler (vl_api_wireguard_peers_dump_t * mp)
+{
+  vl_api_registration_t *reg;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+  if (reg == NULL)
+    return;
+
+  wg_deatils_walk_t ctx = {
+    .reg = reg,
+    .context = mp->context,
+  };
+
+  wg_peer_walk (send_wg_peers_details, &ctx);
+}
+
+/* set tup the API message handling tables */
+#include <wireguard/wireguard.api.c>
+static clib_error_t *
+wg_api_hookup (vlib_main_t * vm)
+{
+  wg_main_t *wmp = &wg_main;
+  wmp->msg_id_base = setup_message_id_table ();
+  return 0;
+}
+
+VLIB_API_INIT_FUNCTION (wg_api_hookup);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_cli.c b/src/plugins/wireguard/wireguard_cli.c
new file mode 100755 (executable)
index 0000000..7fdccdc
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <wireguard/wireguard.h>
+#include <wireguard/wireguard_key.h>
+#include <wireguard/wireguard_peer.h>
+#include <wireguard/wireguard_if.h>
+
+static clib_error_t *
+wg_if_create_cli (vlib_main_t * vm,
+                 unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  u8 private_key[NOISE_PUBLIC_KEY_LEN];
+  u32 instance, sw_if_index;
+  ip_address_t src_ip;
+  clib_error_t *error;
+  u8 *private_key_64;
+  u32 port, generate_key = 0;
+  int rv;
+
+  error = NULL;
+  instance = sw_if_index = ~0;
+  private_key_64 = 0;
+  port = 0;
+
+  if (unformat_user (input, unformat_line_input, line_input))
+    {
+      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+       {
+         if (unformat (line_input, "instance %d", &instance))
+           ;
+         else if (unformat (line_input, "private-key %s", &private_key_64))
+           {
+             if (!(key_from_base64 (private_key_64,
+                                    NOISE_KEY_LEN_BASE64, private_key)))
+               {
+                 error = clib_error_return (0, "Error parsing private key");
+                 break;
+               }
+           }
+         else if (unformat (line_input, "listen-port %d", &port))
+           ;
+         else if (unformat (line_input, "port %d", &port))
+           ;
+         else if (unformat (line_input, "generate-key"))
+           generate_key = 1;
+         else
+           if (unformat (line_input, "src %U", unformat_ip_address, &src_ip))
+           ;
+         else
+           {
+             error = clib_error_return (0, "unknown input: %U",
+                                        format_unformat_error, line_input);
+             break;
+           }
+       }
+
+      unformat_free (line_input);
+
+      if (error)
+       return error;
+    }
+
+  if (generate_key)
+    curve25519_gen_secret (private_key);
+
+  rv = wg_if_create (instance, private_key, port, &src_ip, &sw_if_index);
+
+  if (rv)
+    return clib_error_return (0, "wireguard interface create failed");
+
+  vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, vnet_get_main (),
+                  sw_if_index);
+  return 0;
+}
+
+/*?
+ * Create a Wireguard interface.
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_if_create_command, static) = {
+  .path = "wireguard create",
+  .short_help = "wireguard create listen-port <port> "
+    "private-key <key> src <IP> [generate-key]",
+  .function = wg_if_create_cli,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+wg_if_delete_cli (vlib_main_t * vm,
+                 unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vnet_main_t *vnm;
+  u32 sw_if_index;
+  int rv;
+
+  vnm = vnet_get_main ();
+  sw_if_index = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat
+         (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
+       ;
+      else
+       break;
+    }
+
+  if (~0 != sw_if_index)
+    {
+      rv = wg_if_delete (sw_if_index);
+
+      if (rv)
+       return clib_error_return (0, "wireguard interface delete failed");
+    }
+  else
+    return clib_error_return (0, "no such interface: %U",
+                             format_unformat_error, input);
+
+  return 0;
+}
+
+/*?
+ * Delete a Wireguard interface.
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_if_delete_command, static) = {
+  .path = "wireguard delete",
+  .short_help = "wireguard delete <interface>",
+  .function = wg_if_delete_cli,
+};
+/* *INDENT-ON* */
+
+
+static clib_error_t *
+wg_peer_add_command_fn (vlib_main_t * vm,
+                       unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  clib_error_t *error = NULL;
+  unformat_input_t _line_input, *line_input = &_line_input;
+
+  u8 *public_key_64 = 0;
+  u8 public_key[NOISE_PUBLIC_KEY_LEN];
+  fib_prefix_t allowed_ip, *allowed_ips = NULL;
+  ip_prefix_t pfx;
+  ip_address_t ip;
+  u32 portDst = 0, table_id = 0;
+  u32 persistent_keepalive = 0;
+  u32 tun_sw_if_index = ~0;
+  u32 peer_index;
+  int rv;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "public-key %s", &public_key_64))
+       {
+         if (!(key_from_base64 (public_key_64,
+                                NOISE_KEY_LEN_BASE64, public_key)))
+           {
+             error = clib_error_return (0, "Error parsing private key");
+             goto done;
+           }
+       }
+      else if (unformat (line_input, "endpoint %U", unformat_ip_address, &ip))
+       ;
+      else if (unformat (line_input, "table-id %d", &table_id))
+       ;
+      else if (unformat (line_input, "port %d", &portDst))
+       ;
+      else if (unformat (line_input, "persistent-keepalive %d",
+                        &persistent_keepalive))
+       ;
+      else if (unformat (line_input, "allowed-ip %U",
+                        unformat_ip_prefix, &pfx))
+       {
+         ip_prefix_to_fib_prefix (&pfx, &allowed_ip);
+         vec_add1 (allowed_ips, allowed_ip);
+       }
+      else if (unformat (line_input, "%U",
+                        unformat_vnet_sw_interface, vnm, &tun_sw_if_index))
+       ;
+      else
+       {
+         error = clib_error_return (0, "Input error");
+         goto done;
+       }
+    }
+
+  if (AF_IP6 == ip_addr_version (&ip) ||
+      FIB_PROTOCOL_IP6 == allowed_ip.fp_proto)
+    rv = VNET_API_ERROR_INVALID_PROTOCOL;
+  else
+    rv = wg_peer_add (tun_sw_if_index,
+                     public_key,
+                     table_id,
+                     &ip_addr_46 (&ip),
+                     allowed_ips,
+                     portDst, persistent_keepalive, &peer_index);
+
+  switch (rv)
+    {
+    case VNET_API_ERROR_KEY_LENGTH:
+      error = clib_error_return (0, "Error parsing public key");
+      break;
+    case VNET_API_ERROR_ENTRY_ALREADY_EXISTS:
+      error = clib_error_return (0, "Peer already exist");
+      break;
+    case VNET_API_ERROR_INVALID_SW_IF_INDEX:
+      error = clib_error_return (0, "Tunnel is not specified");
+      break;
+    case VNET_API_ERROR_LIMIT_EXCEEDED:
+      error = clib_error_return (0, "Max peers limit");
+      break;
+    case VNET_API_ERROR_INIT_FAILED:
+      error = clib_error_return (0, "wireguard device parameters is not set");
+      break;
+    case VNET_API_ERROR_INVALID_PROTOCOL:
+      error = clib_error_return (0, "ipv6 not supported yet");
+      break;
+    }
+
+done:
+  vec_free (public_key_64);
+  vec_free (allowed_ips);
+  unformat_free (line_input);
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_peer_add_command, static) =
+{
+  .path = "wireguard peer add",
+  .short_help = "wireguard peer add <wg_int> public-key <pub_key_other>"
+  "endpoint <ip4_dst> allowed-ip <prefix>"
+  "dst-port [port_dst] persistent-keepalive [keepalive_interval]",
+  .function = wg_peer_add_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+wg_peer_remove_command_fn (vlib_main_t * vm,
+                          unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  clib_error_t *error = NULL;
+  u32 peer_index;
+  int rv;
+
+  unformat_input_t _line_input, *line_input = &_line_input;
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  if (unformat (line_input, "%d", &peer_index))
+    ;
+  else
+    {
+      error = clib_error_return (0, "Input error");
+      goto done;
+    }
+
+  rv = wg_peer_remove (peer_index);
+
+  switch (rv)
+    {
+    case VNET_API_ERROR_KEY_LENGTH:
+      error = clib_error_return (0, "Error parsing public key");
+      break;
+    }
+
+done:
+  unformat_free (line_input);
+  return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_peer_remove_command, static) =
+{
+  .path = "wireguard peer remove",
+  .short_help = "wireguard peer remove <index>",
+  .function = wg_peer_remove_command_fn,
+};
+/* *INDENT-ON* */
+
+static walk_rc_t
+wg_peer_show_one (index_t peeri, void *arg)
+{
+  vlib_cli_output (arg, "%U", format_wg_peer, peeri);
+
+  return (WALK_CONTINUE);
+}
+
+static clib_error_t *
+wg_show_peer_command_fn (vlib_main_t * vm,
+                        unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  wg_peer_walk (wg_peer_show_one, vm);
+
+  return NULL;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_show_peers_command, static) =
+{
+  .path = "show wireguard peer",
+  .short_help = "show wireguard peer",
+  .function = wg_show_peer_command_fn,
+};
+/* *INDENT-ON* */
+
+static walk_rc_t
+wg_if_show_one (index_t itfi, void *arg)
+{
+  vlib_cli_output (arg, "%U", format_wg_if, itfi);
+
+  return (WALK_CONTINUE);
+}
+
+static clib_error_t *
+wg_show_if_command_fn (vlib_main_t * vm,
+                      unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  wg_if_walk (wg_if_show_one, vm);
+
+  return NULL;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (wg_show_itfs_command, static) =
+{
+  .path = "show wireguard interface",
+  .short_help = "show wireguard",
+  .function = wg_show_if_command_fn,
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_cookie.c b/src/plugins/wireguard/wireguard_cookie.c
new file mode 100755 (executable)
index 0000000..aa476f7
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.
+ * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>.
+ * 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 <stddef.h>
+#include <openssl/rand.h>
+#include <vlib/vlib.h>
+
+#include <wireguard/wireguard_cookie.h>
+#include <wireguard/wireguard.h>
+
+static void cookie_precompute_key (uint8_t *,
+                                  const uint8_t[COOKIE_INPUT_SIZE],
+                                  const char *);
+static void cookie_macs_mac1 (message_macs_t *, const void *, size_t,
+                             const uint8_t[COOKIE_KEY_SIZE]);
+static void cookie_macs_mac2 (message_macs_t *, const void *, size_t,
+                             const uint8_t[COOKIE_COOKIE_SIZE]);
+static void cookie_checker_make_cookie (vlib_main_t * vm, cookie_checker_t *,
+                                       uint8_t[COOKIE_COOKIE_SIZE],
+                                       ip4_address_t ip4, u16 udp_port);
+
+/* Public Functions */
+void
+cookie_maker_init (cookie_maker_t * cp, const uint8_t key[COOKIE_INPUT_SIZE])
+{
+  clib_memset (cp, 0, sizeof (*cp));
+  cookie_precompute_key (cp->cp_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
+  cookie_precompute_key (cp->cp_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
+}
+
+void
+cookie_checker_update (cookie_checker_t * cc, uint8_t key[COOKIE_INPUT_SIZE])
+{
+  if (key)
+    {
+      cookie_precompute_key (cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
+      cookie_precompute_key (cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
+    }
+  else
+    {
+      clib_memset (cc->cc_mac1_key, 0, sizeof (cc->cc_mac1_key));
+      clib_memset (cc->cc_cookie_key, 0, sizeof (cc->cc_cookie_key));
+    }
+}
+
+void
+cookie_maker_mac (cookie_maker_t * cp, message_macs_t * cm, void *buf,
+                 size_t len)
+{
+  len = len - sizeof (message_macs_t);
+  cookie_macs_mac1 (cm, buf, len, cp->cp_mac1_key);
+
+  clib_memcpy (cp->cp_mac1_last, cm->mac1, COOKIE_MAC_SIZE);
+  cp->cp_mac1_valid = 1;
+
+  if (!wg_birthdate_has_expired (cp->cp_birthdate,
+                                COOKIE_SECRET_MAX_AGE -
+                                COOKIE_SECRET_LATENCY))
+    cookie_macs_mac2 (cm, buf, len, cp->cp_cookie);
+  else
+    clib_memset (cm->mac2, 0, COOKIE_MAC_SIZE);
+}
+
+enum cookie_mac_state
+cookie_checker_validate_macs (vlib_main_t * vm, cookie_checker_t * cc,
+                             message_macs_t * cm, void *buf, size_t len,
+                             bool busy, ip4_address_t ip4, u16 udp_port)
+{
+  message_macs_t our_cm;
+  uint8_t cookie[COOKIE_COOKIE_SIZE];
+
+  len = len - sizeof (message_macs_t);
+  cookie_macs_mac1 (&our_cm, buf, len, cc->cc_mac1_key);
+
+  /* If mac1 is invald, we want to drop the packet */
+  if (clib_memcmp (our_cm.mac1, cm->mac1, COOKIE_MAC_SIZE) != 0)
+    return INVALID_MAC;
+
+  if (!busy)
+    return VALID_MAC_BUT_NO_COOKIE;
+
+  cookie_checker_make_cookie (vm, cc, cookie, ip4, udp_port);
+  cookie_macs_mac2 (&our_cm, buf, len, cookie);
+
+  /* If the mac2 is invalid, we want to send a cookie response */
+  if (clib_memcmp (our_cm.mac2, cm->mac2, COOKIE_MAC_SIZE) != 0)
+    return VALID_MAC_BUT_NO_COOKIE;
+
+  return VALID_MAC_WITH_COOKIE;
+}
+
+/* Private functions */
+static void
+cookie_precompute_key (uint8_t * key, const uint8_t input[COOKIE_INPUT_SIZE],
+                      const char *label)
+{
+  blake2s_state_t blake;
+
+  blake2s_init (&blake, COOKIE_KEY_SIZE);
+  blake2s_update (&blake, (const uint8_t *) label, strlen (label));
+  blake2s_update (&blake, input, COOKIE_INPUT_SIZE);
+  blake2s_final (&blake, key, COOKIE_KEY_SIZE);
+}
+
+static void
+cookie_macs_mac1 (message_macs_t * cm, const void *buf, size_t len,
+                 const uint8_t key[COOKIE_KEY_SIZE])
+{
+  blake2s_state_t state;
+  blake2s_init_key (&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE);
+  blake2s_update (&state, buf, len);
+  blake2s_final (&state, cm->mac1, COOKIE_MAC_SIZE);
+
+}
+
+static void
+cookie_macs_mac2 (message_macs_t * cm, const void *buf, size_t len,
+                 const uint8_t key[COOKIE_COOKIE_SIZE])
+{
+  blake2s_state_t state;
+  blake2s_init_key (&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE);
+  blake2s_update (&state, buf, len);
+  blake2s_update (&state, cm->mac1, COOKIE_MAC_SIZE);
+  blake2s_final (&state, cm->mac2, COOKIE_MAC_SIZE);
+}
+
+static void
+cookie_checker_make_cookie (vlib_main_t * vm, cookie_checker_t * cc,
+                           uint8_t cookie[COOKIE_COOKIE_SIZE],
+                           ip4_address_t ip4, u16 udp_port)
+{
+  blake2s_state_t state;
+
+  if (wg_birthdate_has_expired (cc->cc_secret_birthdate,
+                               COOKIE_SECRET_MAX_AGE))
+    {
+      cc->cc_secret_birthdate = vlib_time_now (vm);
+      RAND_bytes (cc->cc_secret, COOKIE_SECRET_SIZE);
+    }
+
+  blake2s_init_key (&state, COOKIE_COOKIE_SIZE, cc->cc_secret,
+                   COOKIE_SECRET_SIZE);
+
+  blake2s_update (&state, ip4.as_u8, sizeof (ip4_address_t));  //TODO: IP6
+  blake2s_update (&state, (u8 *) & udp_port, sizeof (u16));
+  blake2s_final (&state, cookie, COOKIE_COOKIE_SIZE);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_cookie.h b/src/plugins/wireguard/wireguard_cookie.h
new file mode 100755 (executable)
index 0000000..489cce8
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.
+ * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>.
+ * 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.
+ */
+
+#ifndef __included_wg_cookie_h__
+#define __included_wg_cookie_h__
+
+#include <vnet/ip/ip4_packet.h>
+#include <wireguard/wireguard_noise.h>
+
+enum cookie_mac_state
+{
+  INVALID_MAC,
+  VALID_MAC_BUT_NO_COOKIE,
+  VALID_MAC_WITH_COOKIE
+};
+
+#define COOKIE_MAC_SIZE                16
+#define COOKIE_KEY_SIZE                32
+#define COOKIE_NONCE_SIZE      24
+#define COOKIE_COOKIE_SIZE     16
+#define COOKIE_SECRET_SIZE     32
+#define COOKIE_INPUT_SIZE      32
+#define COOKIE_ENCRYPTED_SIZE  (COOKIE_COOKIE_SIZE + COOKIE_MAC_SIZE)
+
+#define COOKIE_MAC1_KEY_LABEL  "mac1----"
+#define COOKIE_COOKIE_KEY_LABEL        "cookie--"
+#define COOKIE_SECRET_MAX_AGE  120
+#define COOKIE_SECRET_LATENCY  5
+
+/* Constants for initiation rate limiting */
+#define RATELIMIT_SIZE         (1 << 13)
+#define RATELIMIT_SIZE_MAX     (RATELIMIT_SIZE * 8)
+#define NSEC_PER_SEC           1000000000LL
+#define INITIATIONS_PER_SECOND 20
+#define INITIATIONS_BURSTABLE  5
+#define INITIATION_COST                (NSEC_PER_SEC / INITIATIONS_PER_SECOND)
+#define TOKEN_MAX              (INITIATION_COST * INITIATIONS_BURSTABLE)
+#define ELEMENT_TIMEOUT                1
+#define IPV4_MASK_SIZE         4       /* Use all 4 bytes of IPv4 address */
+#define IPV6_MASK_SIZE         8       /* Use top 8 bytes (/64) of IPv6 address */
+
+typedef struct cookie_macs
+{
+  uint8_t mac1[COOKIE_MAC_SIZE];
+  uint8_t mac2[COOKIE_MAC_SIZE];
+} message_macs_t;
+
+typedef struct cookie_maker
+{
+  uint8_t cp_mac1_key[COOKIE_KEY_SIZE];
+  uint8_t cp_cookie_key[COOKIE_KEY_SIZE];
+
+  uint8_t cp_cookie[COOKIE_COOKIE_SIZE];
+  f64 cp_birthdate;
+  int cp_mac1_valid;
+  uint8_t cp_mac1_last[COOKIE_MAC_SIZE];
+} cookie_maker_t;
+
+typedef struct cookie_checker
+{
+  uint8_t cc_mac1_key[COOKIE_KEY_SIZE];
+  uint8_t cc_cookie_key[COOKIE_KEY_SIZE];
+
+  f64 cc_secret_birthdate;
+  uint8_t cc_secret[COOKIE_SECRET_SIZE];
+} cookie_checker_t;
+
+
+void cookie_maker_init (cookie_maker_t *, const uint8_t[COOKIE_INPUT_SIZE]);
+void cookie_checker_update (cookie_checker_t *, uint8_t[COOKIE_INPUT_SIZE]);
+void cookie_maker_mac (cookie_maker_t *, message_macs_t *, void *, size_t);
+enum cookie_mac_state cookie_checker_validate_macs (vlib_main_t * vm,
+                                                   cookie_checker_t *,
+                                                   message_macs_t *, void *,
+                                                   size_t, bool,
+                                                   ip4_address_t ip4,
+                                                   u16 udp_port);
+
+#endif /* __included_wg_cookie_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_if.c b/src/plugins/wireguard/wireguard_if.c
new file mode 100644 (file)
index 0000000..ff8ed35
--- /dev/null
@@ -0,0 +1,422 @@
+
+#include <vnet/adj/adj_midchain.h>
+#include <vnet/udp/udp.h>
+
+#include <wireguard/wireguard_messages.h>
+#include <wireguard/wireguard_if.h>
+#include <wireguard/wireguard.h>
+
+/* pool of interfaces */
+wg_if_t *wg_if_pool;
+
+/* bitmap of Allocated WG_ITF instances */
+static uword *wg_if_instances;
+
+/* vector of interfaces key'd on their sw_if_index */
+static index_t *wg_if_index_by_sw_if_index;
+
+/* vector of interfaces key'd on their UDP port (in network order) */
+index_t *wg_if_index_by_port;
+
+static u8 *
+format_wg_if_name (u8 * s, va_list * args)
+{
+  u32 dev_instance = va_arg (*args, u32);
+  return format (s, "wg%d", dev_instance);
+}
+
+u8 *
+format_wg_if (u8 * s, va_list * args)
+{
+  index_t wgii = va_arg (*args, u32);
+  wg_if_t *wgi = wg_if_get (wgii);
+  u8 key[NOISE_KEY_LEN_BASE64];
+
+  key_to_base64 (wgi->local.l_private, NOISE_PUBLIC_KEY_LEN, key);
+
+  s = format (s, "[%d] %U src:%U port:%d",
+             wgii,
+             format_vnet_sw_if_index_name, vnet_get_main (),
+             wgi->sw_if_index, format_ip_address, &wgi->src_ip, wgi->port);
+
+  key_to_base64 (wgi->local.l_private, NOISE_PUBLIC_KEY_LEN, key);
+
+  s = format (s, " private-key:%s", key);
+
+  key_to_base64 (wgi->local.l_public, NOISE_PUBLIC_KEY_LEN, key);
+
+  s = format (s, " public-key:%s", key);
+
+  return (s);
+}
+
+index_t
+wg_if_find_by_sw_if_index (u32 sw_if_index)
+{
+  if (vec_len (wg_if_index_by_sw_if_index) <= sw_if_index)
+    return INDEX_INVALID;
+  u32 ti = wg_if_index_by_sw_if_index[sw_if_index];
+  if (ti == ~0)
+    return INDEX_INVALID;
+
+  return (ti);
+}
+
+static noise_remote_t *
+wg_remote_get (uint8_t public[NOISE_PUBLIC_KEY_LEN])
+{
+  wg_main_t *wmp = &wg_main;
+  wg_peer_t *peer = NULL;
+  wg_peer_t *peer_iter;
+  /* *INDENT-OFF* */
+  pool_foreach (peer_iter, wmp->peers,
+  ({
+    if (!memcmp (peer_iter->remote.r_public, public, NOISE_PUBLIC_KEY_LEN))
+    {
+      peer = peer_iter;
+      break;
+    }
+  }));
+  /* *INDENT-ON* */
+  return peer ? &peer->remote : NULL;
+}
+
+static uint32_t
+wg_index_set (noise_remote_t * remote)
+{
+  wg_main_t *wmp = &wg_main;
+  u32 rnd_seed = (u32) (vlib_time_now (wmp->vlib_main) * 1e6);
+  u32 ret =
+    wg_index_table_add (&wmp->index_table, remote->r_peer_idx, rnd_seed);
+  return ret;
+}
+
+static void
+wg_index_drop (uint32_t key)
+{
+  wg_main_t *wmp = &wg_main;
+  wg_index_table_del (&wmp->index_table, key);
+}
+
+static clib_error_t *
+wg_if_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+  vnet_hw_interface_t *hi;
+  index_t wgii;
+  u32 hw_flags;
+
+  hi = vnet_get_hw_interface (vnm, hw_if_index);
+  hw_flags = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ?
+             VNET_HW_INTERFACE_FLAG_LINK_UP : 0);
+  vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags);
+
+  wgii = wg_if_find_by_sw_if_index (hi->sw_if_index);
+
+  wg_if_peer_walk (wg_if_get (wgii), wg_peer_if_admin_state_change, NULL);
+
+  return (NULL);
+}
+
+void
+wg_if_update_adj (vnet_main_t * vnm, u32 sw_if_index, adj_index_t ai)
+{
+  /* The peers manage the adjacencies */
+}
+
+
+/* *INDENT-OFF* */
+VNET_DEVICE_CLASS (wg_if_device_class) = {
+  .name = "Wireguard Tunnel",
+  .format_device_name = format_wg_if_name,
+  .admin_up_down_function = wg_if_admin_up_down,
+};
+
+VNET_HW_INTERFACE_CLASS(wg_hw_interface_class) = {
+  .name = "Wireguard",
+  .update_adjacency = wg_if_update_adj,
+  .flags = VNET_HW_INTERFACE_CLASS_FLAG_NBMA,
+};
+/* *INDENT-ON* */
+
+/*
+ * Maintain a bitmap of allocated wg_if instance numbers.
+ */
+#define WG_ITF_MAX_INSTANCE            (16 * 1024)
+
+static u32
+wg_if_instance_alloc (u32 want)
+{
+  /*
+   * Check for dynamically allocated instance number.
+   */
+  if (~0 == want)
+    {
+      u32 bit;
+
+      bit = clib_bitmap_first_clear (wg_if_instances);
+      if (bit >= WG_ITF_MAX_INSTANCE)
+       {
+         return ~0;
+       }
+      wg_if_instances = clib_bitmap_set (wg_if_instances, bit, 1);
+      return bit;
+    }
+
+  /*
+   * In range?
+   */
+  if (want >= WG_ITF_MAX_INSTANCE)
+    {
+      return ~0;
+    }
+
+  /*
+   * Already in use?
+   */
+  if (clib_bitmap_get (wg_if_instances, want))
+    {
+      return ~0;
+    }
+
+  /*
+   * Grant allocation request.
+   */
+  wg_if_instances = clib_bitmap_set (wg_if_instances, want, 1);
+
+  return want;
+}
+
+static int
+wg_if_instance_free (u32 instance)
+{
+  if (instance >= WG_ITF_MAX_INSTANCE)
+    {
+      return -1;
+    }
+
+  if (clib_bitmap_get (wg_if_instances, instance) == 0)
+    {
+      return -1;
+    }
+
+  wg_if_instances = clib_bitmap_set (wg_if_instances, instance, 0);
+  return 0;
+}
+
+
+int
+wg_if_create (u32 user_instance,
+             const u8 private_key[NOISE_PUBLIC_KEY_LEN],
+             u16 port, const ip_address_t * src_ip, u32 * sw_if_indexp)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  u32 instance, hw_if_index;
+  vnet_hw_interface_t *hi;
+  wg_if_t *wg_if;
+
+  ASSERT (sw_if_indexp);
+
+  *sw_if_indexp = (u32) ~ 0;
+
+  /*
+   * Allocate a wg_if instance. Either select on dynamically
+   * or try to use the desired user_instance number.
+   */
+  instance = wg_if_instance_alloc (user_instance);
+  if (instance == ~0)
+    return VNET_API_ERROR_INVALID_REGISTRATION;
+
+  pool_get (wg_if_pool, wg_if);
+
+  /* tunnel index (or instance) */
+  u32 t_idx = wg_if - wg_if_pool;
+
+  wg_if->user_instance = instance;
+  if (~0 == wg_if->user_instance)
+    wg_if->user_instance = t_idx;
+
+  udp_dst_port_info_t *pi = udp_get_dst_port_info (&udp_main, port, UDP_IP4);
+  if (pi)
+    return (VNET_API_ERROR_VALUE_EXIST);
+  udp_register_dst_port (vlib_get_main (), port, wg_input_node.index, 1);
+
+  vec_validate_init_empty (wg_if_index_by_port, port, INDEX_INVALID);
+  wg_if_index_by_port[port] = wg_if - wg_if_pool;
+
+  wg_if->port = port;
+  struct noise_upcall upcall;
+  upcall.u_remote_get = wg_remote_get;
+  upcall.u_index_set = wg_index_set;
+  upcall.u_index_drop = wg_index_drop;
+
+  noise_local_init (&wg_if->local, &upcall);
+  noise_local_set_private (&wg_if->local, private_key);
+  cookie_checker_update (&wg_if->cookie_checker, wg_if->local.l_public);
+
+  hw_if_index = vnet_register_interface (vnm,
+                                        wg_if_device_class.index,
+                                        t_idx,
+                                        wg_hw_interface_class.index, t_idx);
+
+  hi = vnet_get_hw_interface (vnm, hw_if_index);
+
+  vec_validate_init_empty (wg_if_index_by_sw_if_index, hi->sw_if_index,
+                          INDEX_INVALID);
+  wg_if_index_by_sw_if_index[hi->sw_if_index] = t_idx;
+
+  ip_address_copy (&wg_if->src_ip, src_ip);
+  wg_if->sw_if_index = *sw_if_indexp = hi->sw_if_index;
+
+  return 0;
+}
+
+int
+wg_if_delete (u32 sw_if_index)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+
+  if (pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index))
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, sw_if_index);
+  if (hw == 0 || hw->dev_class_index != wg_if_device_class.index)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  wg_if_t *wg_if;
+  wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index));
+  if (NULL == wg_if)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  if (wg_if_instance_free (hw->dev_instance) < 0)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  wg_if_index_by_port[wg_if->port] = INDEX_INVALID;
+  vnet_delete_hw_interface (vnm, hw->hw_if_index);
+  pool_put (wg_if_pool, wg_if);
+
+  return 0;
+}
+
+void
+wg_if_peer_add (wg_if_t * wgi, index_t peeri)
+{
+  hash_set (wgi->peers, peeri, peeri);
+
+  if (1 == hash_elts (wgi->peers))
+    vnet_feature_enable_disable ("ip4-output", "wg-output-tun",
+                                wgi->sw_if_index, 1, 0, 0);
+}
+
+void
+wg_if_peer_remove (wg_if_t * wgi, index_t peeri)
+{
+  hash_unset (wgi->peers, peeri);
+
+  if (0 == hash_elts (wgi->peers))
+    vnet_feature_enable_disable ("ip4-output", "wg-output-tun",
+                                wgi->sw_if_index, 0, 0, 0);
+}
+
+void
+wg_if_walk (wg_if_walk_cb_t fn, void *data)
+{
+  index_t wgii;
+
+  /* *INDENT-OFF* */
+  pool_foreach_index (wgii, wg_if_pool,
+  {
+    if (WALK_STOP == fn(wgii, data))
+      break;
+  });
+  /* *INDENT-ON* */
+}
+
+void
+wg_if_peer_walk (wg_if_t * wgi, wg_if_peer_walk_cb_t fn, void *data)
+{
+  index_t peeri, val;
+
+  /* *INDENT-OFF* */
+  hash_foreach (peeri, val, wgi->peers,
+  {
+    if (WALK_STOP == fn(wgi, peeri, data))
+      break;
+  });
+  /* *INDENT-ON* */
+}
+
+
+static void
+wg_if_table_bind_v4 (ip4_main_t * im,
+                    uword opaque,
+                    u32 sw_if_index, u32 new_fib_index, u32 old_fib_index)
+{
+  wg_if_t *wg_if;
+
+  wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index));
+  if (NULL == wg_if)
+    return;
+
+  wg_peer_table_bind_ctx_t ctx = {
+    .af = AF_IP4,
+    .old_fib_index = old_fib_index,
+    .new_fib_index = new_fib_index,
+  };
+
+  wg_if_peer_walk (wg_if, wg_peer_if_table_change, &ctx);
+}
+
+static void
+wg_if_table_bind_v6 (ip6_main_t * im,
+                    uword opaque,
+                    u32 sw_if_index, u32 new_fib_index, u32 old_fib_index)
+{
+  wg_if_t *wg_if;
+
+  wg_if = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index));
+  if (NULL == wg_if)
+    return;
+
+  wg_peer_table_bind_ctx_t ctx = {
+    .af = AF_IP6,
+    .old_fib_index = old_fib_index,
+    .new_fib_index = new_fib_index,
+  };
+
+  wg_if_peer_walk (wg_if, wg_peer_if_table_change, &ctx);
+}
+
+static clib_error_t *
+wg_if_module_init (vlib_main_t * vm)
+{
+  {
+    ip4_table_bind_callback_t cb = {
+      .function = wg_if_table_bind_v4,
+    };
+    vec_add1 (ip4_main.table_bind_callbacks, cb);
+  }
+  {
+    ip6_table_bind_callback_t cb = {
+      .function = wg_if_table_bind_v6,
+    };
+    vec_add1 (ip6_main.table_bind_callbacks, cb);
+  }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_INIT_FUNCTION (wg_if_module_init) =
+{
+  .runs_after = VLIB_INITS("ip_main_init"),
+};
+/* *INDENT-ON* */
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_if.h b/src/plugins/wireguard/wireguard_if.h
new file mode 100644 (file)
index 0000000..9e6b619
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __WG_ITF_H__
+#define __WG_ITF_H__
+
+#include <wireguard/wireguard_index_table.h>
+#include <wireguard/wireguard_messages.h>
+
+typedef struct wg_if_t_
+{
+  int user_instance;
+  u32 sw_if_index;
+
+  // Interface params
+  noise_local_t local;
+  cookie_checker_t cookie_checker;
+  u16 port;
+
+  wg_index_table_t index_table;
+
+  /* Source IP address for originated packets */
+  ip_address_t src_ip;
+
+  /* hash table of peers on this link */
+  uword *peers;
+} wg_if_t;
+
+
+int wg_if_create (u32 user_instance,
+                 const u8 private_key_64[NOISE_PUBLIC_KEY_LEN],
+                 u16 port, const ip_address_t * src_ip, u32 * sw_if_indexp);
+int wg_if_delete (u32 sw_if_index);
+index_t wg_if_find_by_sw_if_index (u32 sw_if_index);
+
+u8 *format_wg_if (u8 * s, va_list * va);
+
+typedef walk_rc_t (*wg_if_walk_cb_t) (index_t wgi, void *data);
+void wg_if_walk (wg_if_walk_cb_t fn, void *data);
+
+typedef walk_rc_t (*wg_if_peer_walk_cb_t) (wg_if_t * wgi, index_t peeri,
+                                          void *data);
+void wg_if_peer_walk (wg_if_t * wgi, wg_if_peer_walk_cb_t fn, void *data);
+
+void wg_if_peer_add (wg_if_t * wgi, index_t peeri);
+void wg_if_peer_remove (wg_if_t * wgi, index_t peeri);
+
+/**
+ * Data-plane exposed functions
+ */
+extern wg_if_t *wg_if_pool;
+
+static_always_inline wg_if_t *
+wg_if_get (index_t wgii)
+{
+  if (INDEX_INVALID == wgii)
+    return (NULL);
+  return (pool_elt_at_index (wg_if_pool, wgii));
+}
+
+extern index_t *wg_if_index_by_port;
+
+static_always_inline wg_if_t *
+wg_if_get_by_port (u16 port)
+{
+  if (vec_len (wg_if_index_by_port) < port)
+    return (NULL);
+  if (INDEX_INVALID == wg_if_index_by_port[port])
+    return (NULL);
+  return (wg_if_get (wg_if_index_by_port[port]));
+}
+
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_index_table.c b/src/plugins/wireguard/wireguard_index_table.c
new file mode 100755 (executable)
index 0000000..5f81204
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vppinfra/hash.h>
+#include <vppinfra/pool.h>
+#include <vppinfra/random.h>
+#include <wireguard/wireguard_index_table.h>
+
+u32
+wg_index_table_add (wg_index_table_t * table, u32 peer_pool_idx, u32 rnd_seed)
+{
+  u32 key;
+
+  while (1)
+    {
+      key = random_u32 (&rnd_seed);
+      if (hash_get (table->hash, key))
+       continue;
+
+      hash_set (table->hash, key, peer_pool_idx);
+      break;
+    }
+  return key;
+}
+
+void
+wg_index_table_del (wg_index_table_t * table, u32 key)
+{
+  uword *p;
+  p = hash_get (table->hash, key);
+  if (p)
+    hash_unset (table->hash, key);
+}
+
+u32 *
+wg_index_table_lookup (const wg_index_table_t * table, u32 key)
+{
+  uword *p;
+
+  p = hash_get (table->hash, key);
+  return (u32 *) p;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_index_table.h b/src/plugins/wireguard/wireguard_index_table.h
new file mode 100755 (executable)
index 0000000..67cae1f
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __included_wg_index_table_h__
+#define __included_wg_index_table_h__
+
+#include <vppinfra/types.h>
+
+typedef struct
+{
+  uword *hash;
+} wg_index_table_t;
+
+u32 wg_index_table_add (wg_index_table_t * table, u32 peer_pool_idx,
+                       u32 rnd_seed);
+void wg_index_table_del (wg_index_table_t * table, u32 key);
+u32 *wg_index_table_lookup (const wg_index_table_t * table, u32 key);
+
+#endif //__included_wg_index_table_h__
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_input.c b/src/plugins/wireguard/wireguard_input.c
new file mode 100755 (executable)
index 0000000..832ad03
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vnet/pg/pg.h>
+#include <vppinfra/error.h>
+#include <wireguard/wireguard.h>
+
+#include <wireguard/wireguard_send.h>
+#include <wireguard/wireguard_if.h>
+
+#define foreach_wg_input_error                          \
+  _(NONE, "No error")                                   \
+  _(HANDSHAKE_MAC, "Invalid MAC handshake")             \
+  _(PEER, "Peer error")                                 \
+  _(INTERFACE, "Interface error")                       \
+  _(DECRYPTION, "Failed during decryption")             \
+  _(KEEPALIVE_SEND, "Failed while sending Keepalive")   \
+  _(HANDSHAKE_SEND, "Failed while sending Handshake")   \
+  _(UNDEFINED, "Undefined error")
+
+typedef enum
+{
+#define _(sym,str) WG_INPUT_ERROR_##sym,
+  foreach_wg_input_error
+#undef _
+    WG_INPUT_N_ERROR,
+} wg_input_error_t;
+
+static char *wg_input_error_strings[] = {
+#define _(sym,string) string,
+  foreach_wg_input_error
+#undef _
+};
+
+typedef struct
+{
+  message_type_t type;
+  u16 current_length;
+  bool is_keepalive;
+
+} wg_input_trace_t;
+
+u8 *
+format_wg_message_type (u8 * s, va_list * args)
+{
+  message_type_t type = va_arg (*args, message_type_t);
+
+  switch (type)
+    {
+#define _(v,a) case MESSAGE_##v: return (format (s, "%s", a));
+      foreach_wg_message_type
+#undef _
+    }
+  return (format (s, "unknown"));
+}
+
+/* packet trace format function */
+static u8 *
+format_wg_input_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+
+  wg_input_trace_t *t = va_arg (*args, wg_input_trace_t *);
+
+  s = format (s, "WG input: \n");
+  s = format (s, "  Type: %U\n", format_wg_message_type, t->type);
+  s = format (s, "  Length: %d\n", t->current_length);
+  s = format (s, "  Keepalive: %s", t->is_keepalive ? "true" : "false");
+
+  return s;
+}
+
+typedef enum
+{
+  WG_INPUT_NEXT_IP4_INPUT,
+  WG_INPUT_NEXT_PUNT,
+  WG_INPUT_NEXT_ERROR,
+  WG_INPUT_N_NEXT,
+} wg_input_next_t;
+
+/* static void */
+/* set_peer_address (wg_peer_t * peer, ip4_address_t ip4, u16 udp_port) */
+/* { */
+/*   if (peer) */
+/*     { */
+/*       ip46_address_set_ip4 (&peer->dst.addr, &ip4); */
+/*       peer->dst.port = udp_port; */
+/*     } */
+/* } */
+
+static wg_input_error_t
+wg_handshake_process (vlib_main_t * vm, wg_main_t * wmp, vlib_buffer_t * b)
+{
+  enum cookie_mac_state mac_state;
+  bool packet_needs_cookie;
+  bool under_load;
+  wg_if_t *wg_if;
+  wg_peer_t *peer = NULL;
+
+  void *current_b_data = vlib_buffer_get_current (b);
+
+  udp_header_t *uhd = current_b_data - sizeof (udp_header_t);
+  ip4_header_t *iph =
+    current_b_data - sizeof (udp_header_t) - sizeof (ip4_header_t);
+  ip4_address_t ip4_src = iph->src_address;
+  u16 udp_src_port = clib_host_to_net_u16 (uhd->src_port);;
+  u16 udp_dst_port = clib_host_to_net_u16 (uhd->dst_port);;
+
+  message_header_t *header = current_b_data;
+  under_load = false;
+
+  wg_if = wg_if_get_by_port (udp_dst_port);
+
+  if (NULL == wg_if)
+    return WG_INPUT_ERROR_INTERFACE;
+
+  if (header->type == MESSAGE_HANDSHAKE_COOKIE)
+    {
+      message_handshake_cookie_t *packet =
+       (message_handshake_cookie_t *) current_b_data;
+      u32 *entry =
+       wg_index_table_lookup (&wmp->index_table, packet->receiver_index);
+      if (entry)
+       {
+         peer = pool_elt_at_index (wmp->peers, *entry);
+       }
+      if (!peer)
+       return WG_INPUT_ERROR_PEER;
+
+      // TODO: Implement cookie_maker_consume_payload
+
+      return WG_INPUT_ERROR_NONE;
+    }
+
+  u32 len = (header->type == MESSAGE_HANDSHAKE_INITIATION ?
+            sizeof (message_handshake_initiation_t) :
+            sizeof (message_handshake_response_t));
+
+  message_macs_t *macs = (message_macs_t *)
+    ((u8 *) current_b_data + len - sizeof (*macs));
+
+  mac_state =
+    cookie_checker_validate_macs (vm, &wg_if->cookie_checker, macs,
+                                 current_b_data, len, under_load, ip4_src,
+                                 udp_src_port);
+
+  if ((under_load && mac_state == VALID_MAC_WITH_COOKIE)
+      || (!under_load && mac_state == VALID_MAC_BUT_NO_COOKIE))
+    packet_needs_cookie = false;
+  else if (under_load && mac_state == VALID_MAC_BUT_NO_COOKIE)
+    packet_needs_cookie = true;
+  else
+    return WG_INPUT_ERROR_HANDSHAKE_MAC;
+
+  switch (header->type)
+    {
+    case MESSAGE_HANDSHAKE_INITIATION:
+      {
+       message_handshake_initiation_t *message = current_b_data;
+
+       if (packet_needs_cookie)
+         {
+           // TODO: Add processing
+         }
+       noise_remote_t *rp;
+
+       if (noise_consume_initiation
+           (wmp->vlib_main, &wg_if->local, &rp, message->sender_index,
+            message->unencrypted_ephemeral, message->encrypted_static,
+            message->encrypted_timestamp))
+         {
+           peer = pool_elt_at_index (wmp->peers, rp->r_peer_idx);
+         }
+
+       if (!peer)
+         return WG_INPUT_ERROR_PEER;
+
+       // set_peer_address (peer, ip4_src, udp_src_port);
+       if (PREDICT_FALSE (!wg_send_handshake_response (vm, peer)))
+         {
+           vlib_node_increment_counter (vm, wg_input_node.index,
+                                        WG_INPUT_ERROR_HANDSHAKE_SEND, 1);
+         }
+       break;
+      }
+    case MESSAGE_HANDSHAKE_RESPONSE:
+      {
+       message_handshake_response_t *resp = current_b_data;
+       u32 *entry =
+         wg_index_table_lookup (&wmp->index_table, resp->receiver_index);
+       if (entry)
+         {
+           peer = pool_elt_at_index (wmp->peers, *entry);
+           if (!peer || peer->is_dead)
+             return WG_INPUT_ERROR_PEER;
+         }
+
+       if (!noise_consume_response
+           (wmp->vlib_main, &peer->remote, resp->sender_index,
+            resp->receiver_index, resp->unencrypted_ephemeral,
+            resp->encrypted_nothing))
+         {
+           return WG_INPUT_ERROR_PEER;
+         }
+       if (packet_needs_cookie)
+         {
+           // TODO: Add processing
+         }
+
+       // set_peer_address (peer, ip4_src, udp_src_port);
+       if (noise_remote_begin_session (wmp->vlib_main, &peer->remote))
+         {
+           wg_timers_session_derived (peer);
+           wg_timers_handshake_complete (peer);
+           if (PREDICT_FALSE (!wg_send_keepalive (vm, peer)))
+             {
+               vlib_node_increment_counter (vm, wg_input_node.index,
+                                            WG_INPUT_ERROR_KEEPALIVE_SEND,
+                                            1);
+             }
+         }
+       break;
+      }
+    default:
+      break;
+    }
+
+  wg_timers_any_authenticated_packet_received (peer);
+  wg_timers_any_authenticated_packet_traversal (peer);
+  return WG_INPUT_ERROR_NONE;
+}
+
+static_always_inline bool
+fib_prefix_is_cover_addr_4 (const fib_prefix_t * p1,
+                           const ip4_address_t * ip4)
+{
+  switch (p1->fp_proto)
+    {
+    case FIB_PROTOCOL_IP4:
+      return (ip4_destination_matches_route (&ip4_main,
+                                            &p1->fp_addr.ip4,
+                                            ip4, p1->fp_len) != 0);
+    case FIB_PROTOCOL_IP6:
+      return (false);
+    case FIB_PROTOCOL_MPLS:
+      break;
+    }
+  return (false);
+}
+
+VLIB_NODE_FN (wg_input_node) (vlib_main_t * vm,
+                             vlib_node_runtime_t * node,
+                             vlib_frame_t * frame)
+{
+  message_type_t header_type;
+  u32 n_left_from;
+  u32 *from;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u16 nexts[VLIB_FRAME_SIZE], *next;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  b = bufs;
+  next = nexts;
+
+  vlib_get_buffers (vm, from, bufs, n_left_from);
+
+  wg_main_t *wmp = &wg_main;
+  wg_peer_t *peer = NULL;
+
+  while (n_left_from > 0)
+    {
+      bool is_keepalive = false;
+      next[0] = WG_INPUT_NEXT_PUNT;
+      header_type =
+       ((message_header_t *) vlib_buffer_get_current (b[0]))->type;
+
+      switch (header_type)
+       {
+       case MESSAGE_HANDSHAKE_INITIATION:
+       case MESSAGE_HANDSHAKE_RESPONSE:
+       case MESSAGE_HANDSHAKE_COOKIE:
+         {
+           wg_input_error_t ret = wg_handshake_process (vm, wmp, b[0]);
+           if (ret != WG_INPUT_ERROR_NONE)
+             {
+               next[0] = WG_INPUT_NEXT_ERROR;
+               b[0]->error = node->errors[ret];
+             }
+           break;
+         }
+       case MESSAGE_DATA:
+         {
+           message_data_t *data = vlib_buffer_get_current (b[0]);
+           u32 *entry =
+             wg_index_table_lookup (&wmp->index_table, data->receiver_index);
+
+           if (entry)
+             {
+               peer = pool_elt_at_index (wmp->peers, *entry);
+               if (!peer)
+                 {
+                   next[0] = WG_INPUT_NEXT_ERROR;
+                   b[0]->error = node->errors[WG_INPUT_ERROR_PEER];
+                   goto out;
+                 }
+             }
+
+           u16 encr_len = b[0]->current_length - sizeof (message_data_t);
+           u16 decr_len = encr_len - NOISE_AUTHTAG_LEN;
+           u8 *decr_data = clib_mem_alloc (decr_len);
+
+           enum noise_state_crypt state_cr =
+             noise_remote_decrypt (wmp->vlib_main,
+                                   &peer->remote,
+                                   data->receiver_index,
+                                   data->counter,
+                                   data->encrypted_data,
+                                   encr_len,
+                                   decr_data);
+
+           switch (state_cr)
+             {
+             case SC_OK:
+               break;
+             case SC_CONN_RESET:
+               wg_timers_handshake_complete (peer);
+               break;
+             case SC_KEEP_KEY_FRESH:
+               if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false)))
+                 {
+                   vlib_node_increment_counter (vm, wg_input_node.index,
+                                                WG_INPUT_ERROR_HANDSHAKE_SEND,
+                                                1);
+                 }
+               break;
+             case SC_FAILED:
+               next[0] = WG_INPUT_NEXT_ERROR;
+               b[0]->error = node->errors[WG_INPUT_ERROR_DECRYPTION];
+               goto out;
+             default:
+               break;
+             }
+
+           clib_memcpy (vlib_buffer_get_current (b[0]), decr_data, decr_len);
+           b[0]->current_length = decr_len;
+           b[0]->flags &= ~VNET_BUFFER_F_OFFLOAD_UDP_CKSUM;
+
+           clib_mem_free (decr_data);
+
+           wg_timers_any_authenticated_packet_received (peer);
+           wg_timers_any_authenticated_packet_traversal (peer);
+
+           if (decr_len == 0)
+             {
+               is_keepalive = true;
+               goto out;
+             }
+
+           wg_timers_data_received (peer);
+
+           ip4_header_t *iph = vlib_buffer_get_current (b[0]);
+
+           const wg_peer_allowed_ip_t *allowed_ip;
+           bool allowed = false;
+
+           /*
+            * we could make this into an ACL, but the expectation
+            * is that there aren't many allowed IPs and thus a linear
+            * walk is fater than an ACL
+            */
+           vec_foreach (allowed_ip, peer->allowed_ips)
+           {
+             if (fib_prefix_is_cover_addr_4 (&allowed_ip->prefix,
+                                             &iph->src_address))
+               {
+                 allowed = true;
+                 break;
+               }
+           }
+           if (allowed)
+             {
+               vnet_buffer (b[0])->sw_if_index[VLIB_RX] =
+                 peer->wg_sw_if_index;
+               next[0] = WG_INPUT_NEXT_IP4_INPUT;
+             }
+           break;
+         }
+       default:
+         break;
+       }
+
+    out:
+      if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
+                        && (b[0]->flags & VLIB_BUFFER_IS_TRACED)))
+       {
+         wg_input_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
+         t->type = header_type;
+         t->current_length = b[0]->current_length;
+         t->is_keepalive = is_keepalive;
+       }
+      n_left_from -= 1;
+      next += 1;
+      b += 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (wg_input_node) =
+{
+  .name = "wg-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_wg_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN (wg_input_error_strings),
+  .error_strings = wg_input_error_strings,
+  .n_next_nodes = WG_INPUT_N_NEXT,
+  /* edit / add dispositions here */
+  .next_nodes = {
+        [WG_INPUT_NEXT_IP4_INPUT] = "ip4-input-no-checksum",
+        [WG_INPUT_NEXT_PUNT] = "error-punt",
+        [WG_INPUT_NEXT_ERROR] = "error-drop",
+  },
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_key.c b/src/plugins/wireguard/wireguard_key.c
new file mode 100755 (executable)
index 0000000..db8c486
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2005-2011 Jouni Malinen <j@w1.fi>.
+ * 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 <wireguard/wireguard_key.h>
+#include <openssl/evp.h>
+
+bool
+curve25519_gen_shared (u8 shared_key[CURVE25519_KEY_SIZE],
+                      const u8 secret_key[CURVE25519_KEY_SIZE],
+                      const u8 basepoint[CURVE25519_KEY_SIZE])
+{
+
+  bool ret;
+  EVP_PKEY_CTX *ctx;
+  size_t key_len;
+
+  EVP_PKEY *peerkey = NULL;
+  EVP_PKEY *pkey =
+    EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, secret_key,
+                                 CURVE25519_KEY_SIZE);
+
+  ret = true;
+
+  ctx = EVP_PKEY_CTX_new (pkey, NULL);
+  if (EVP_PKEY_derive_init (ctx) <= 0)
+    {
+      ret = false;
+      goto out;
+    }
+
+  peerkey =
+    EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, basepoint,
+                                CURVE25519_KEY_SIZE);
+  if (EVP_PKEY_derive_set_peer (ctx, peerkey) <= 0)
+    {
+      ret = false;
+      goto out;
+    }
+
+  key_len = CURVE25519_KEY_SIZE;
+  if (EVP_PKEY_derive (ctx, shared_key, &key_len) <= 0)
+    {
+      ret = false;
+    }
+
+out:
+  EVP_PKEY_CTX_free (ctx);
+  EVP_PKEY_free (pkey);
+  EVP_PKEY_free (peerkey);
+  return ret;
+}
+
+bool
+curve25519_gen_public (u8 public_key[CURVE25519_KEY_SIZE],
+                      const u8 secret_key[CURVE25519_KEY_SIZE])
+{
+  size_t pub_len;
+  EVP_PKEY *pkey =
+    EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, secret_key,
+                                 CURVE25519_KEY_SIZE);
+  pub_len = CURVE25519_KEY_SIZE;
+  if (!EVP_PKEY_get_raw_public_key (pkey, public_key, &pub_len))
+    {
+      EVP_PKEY_free (pkey);
+      return false;
+    }
+  EVP_PKEY_free (pkey);
+  return true;
+}
+
+bool
+curve25519_gen_secret (u8 secret_key[CURVE25519_KEY_SIZE])
+{
+  size_t secret_len;
+  EVP_PKEY *pkey = NULL;
+  EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_X25519, NULL);
+  EVP_PKEY_keygen_init (pctx);
+  EVP_PKEY_keygen (pctx, &pkey);
+  EVP_PKEY_CTX_free (pctx);
+
+  secret_len = CURVE25519_KEY_SIZE;
+  if (!EVP_PKEY_get_raw_private_key (pkey, secret_key, &secret_len))
+    {
+      EVP_PKEY_free (pkey);
+      return false;
+    }
+  EVP_PKEY_free (pkey);
+  return true;
+}
+
+bool
+key_to_base64 (const u8 * src, size_t src_len, u8 * out)
+{
+  if (!EVP_EncodeBlock (out, src, src_len))
+    return false;
+  return true;
+}
+
+bool
+key_from_base64 (const u8 * src, size_t src_len, u8 * out)
+{
+  if (EVP_DecodeBlock (out, src, src_len - 1) <= 0)
+    return false;
+  return true;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_key.h b/src/plugins/wireguard/wireguard_key.h
new file mode 100755 (executable)
index 0000000..6decca6
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2005 Jouni Malinen <j@w1.fi>.
+ * 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.
+ */
+
+#ifndef __included_wg_convert_h__
+#define __included_wg_convert_h__
+
+#include <stdbool.h>
+#include <vlib/vlib.h>
+
+enum curve25519_lengths
+{
+  CURVE25519_KEY_SIZE = 32
+};
+
+bool curve25519_gen_shared (u8 shared_key[CURVE25519_KEY_SIZE],
+                           const u8 secret_key[CURVE25519_KEY_SIZE],
+                           const u8 basepoint[CURVE25519_KEY_SIZE]);
+bool curve25519_gen_secret (u8 secret[CURVE25519_KEY_SIZE]);
+bool curve25519_gen_public (u8 public_key[CURVE25519_KEY_SIZE],
+                           const u8 secret_key[CURVE25519_KEY_SIZE]);
+
+bool key_to_base64 (const u8 * src, size_t src_len, u8 * out);
+bool key_from_base64 (const u8 * src, size_t src_len, u8 * out);
+
+#endif /* __included_wg_convert_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_messages.h b/src/plugins/wireguard/wireguard_messages.h
new file mode 100755 (executable)
index 0000000..3587c5c
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __included_wg_messages_h__
+#define __included_wg_messages_h__
+
+#include <stdint.h>
+#include <wireguard/wireguard_noise.h>
+#include <wireguard/wireguard_cookie.h>
+
+#define WG_TICK 0.01                           /**< WG tick period (s) */
+#define WHZ (u32) (1/WG_TICK)          /**< WG tick frequency */
+
+#define NOISE_KEY_LEN_BASE64 ((((NOISE_PUBLIC_KEY_LEN) + 2) / 3) * 4 + 1)
+#define noise_encrypted_len(plain_len) ((plain_len) + NOISE_AUTHTAG_LEN)
+
+enum limits
+{
+  REKEY_TIMEOUT = 5,
+  REKEY_TIMEOUT_JITTER = WHZ / 3,
+  KEEPALIVE_TIMEOUT = 10,
+  MAX_TIMER_HANDSHAKES = 90 / REKEY_TIMEOUT,
+  MAX_PEERS = 1U << 20
+};
+
+#define foreach_wg_message_type        \
+  _(INVALID, "Invalid")                \
+  _(HANDSHAKE_INITIATION, "Handshake initiation")              \
+  _(HANDSHAKE_RESPONSE, "Handshake response") \
+  _(HANDSHAKE_COOKIE, "Handshake cookie") \
+  _(DATA, "Data") \
+
+typedef enum message_type
+{
+#define _(v,s) MESSAGE_##v,
+  foreach_wg_message_type
+#undef _
+} message_type_t;
+
+typedef struct message_header
+{
+  message_type_t type;
+} message_header_t;
+
+typedef struct message_handshake_initiation
+{
+  message_header_t header;
+  u32 sender_index;
+  u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN];
+  u8 encrypted_static[noise_encrypted_len (NOISE_PUBLIC_KEY_LEN)];
+  u8 encrypted_timestamp[noise_encrypted_len (NOISE_TIMESTAMP_LEN)];
+  message_macs_t macs;
+} message_handshake_initiation_t;
+
+typedef struct message_handshake_response
+{
+  message_header_t header;
+  u32 sender_index;
+  u32 receiver_index;
+  u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN];
+  u8 encrypted_nothing[noise_encrypted_len (0)];
+  message_macs_t macs;
+} message_handshake_response_t;
+
+typedef struct message_handshake_cookie
+{
+  message_header_t header;
+  u32 receiver_index;
+  u8 nonce[COOKIE_NONCE_SIZE];
+  u8 encrypted_cookie[noise_encrypted_len (COOKIE_MAC_SIZE)];
+} message_handshake_cookie_t;
+
+typedef struct message_data
+{
+  message_header_t header;
+  u32 receiver_index;
+  u64 counter;
+  u8 encrypted_data[];
+} message_data_t;
+
+#define message_data_len(plain_len) \
+    (noise_encrypted_len(plain_len) + sizeof(message_data_t))
+
+#endif /* __included_wg_messages_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_noise.c b/src/plugins/wireguard/wireguard_noise.c
new file mode 100755 (executable)
index 0000000..666618a
--- /dev/null
@@ -0,0 +1,985 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.
+ * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>.
+ * 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 <openssl/hmac.h>
+#include <wireguard/wireguard.h>
+
+/* This implements Noise_IKpsk2:
+ *
+ * <- s
+ * ******
+ * -> e, es, s, ss, {t}
+ * <- e, ee, se, psk, {}
+ */
+
+/* Private functions */
+static noise_keypair_t *noise_remote_keypair_allocate (noise_remote_t *);
+static void noise_remote_keypair_free (vlib_main_t * vm, noise_remote_t *,
+                                      noise_keypair_t **);
+static uint32_t noise_remote_handshake_index_get (noise_remote_t *);
+static void noise_remote_handshake_index_drop (noise_remote_t *);
+
+static uint64_t noise_counter_send (noise_counter_t *);
+static bool noise_counter_recv (noise_counter_t *, uint64_t);
+
+static void noise_kdf (uint8_t *, uint8_t *, uint8_t *, const uint8_t *,
+                      size_t, size_t, size_t, size_t,
+                      const uint8_t[NOISE_HASH_LEN]);
+static bool noise_mix_dh (uint8_t[NOISE_HASH_LEN],
+                         uint8_t[NOISE_SYMMETRIC_KEY_LEN],
+                         const uint8_t[NOISE_PUBLIC_KEY_LEN],
+                         const uint8_t[NOISE_PUBLIC_KEY_LEN]);
+static bool noise_mix_ss (uint8_t ck[NOISE_HASH_LEN],
+                         uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
+                         const uint8_t ss[NOISE_PUBLIC_KEY_LEN]);
+static void noise_mix_hash (uint8_t[NOISE_HASH_LEN], const uint8_t *, size_t);
+static void noise_mix_psk (uint8_t[NOISE_HASH_LEN],
+                          uint8_t[NOISE_HASH_LEN],
+                          uint8_t[NOISE_SYMMETRIC_KEY_LEN],
+                          const uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
+static void noise_param_init (uint8_t[NOISE_HASH_LEN],
+                             uint8_t[NOISE_HASH_LEN],
+                             const uint8_t[NOISE_PUBLIC_KEY_LEN]);
+
+static void noise_msg_encrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t,
+                              uint32_t key_idx, uint8_t[NOISE_HASH_LEN]);
+static bool noise_msg_decrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t,
+                              uint32_t key_idx, uint8_t[NOISE_HASH_LEN]);
+static void noise_msg_ephemeral (uint8_t[NOISE_HASH_LEN],
+                                uint8_t[NOISE_HASH_LEN],
+                                const uint8_t src[NOISE_PUBLIC_KEY_LEN]);
+
+static void noise_tai64n_now (uint8_t[NOISE_TIMESTAMP_LEN]);
+
+static void secure_zero_memory (void *v, size_t n);
+
+/* Set/Get noise parameters */
+void
+noise_local_init (noise_local_t * l, struct noise_upcall *upcall)
+{
+  clib_memset (l, 0, sizeof (*l));
+  l->l_upcall = *upcall;
+}
+
+bool
+noise_local_set_private (noise_local_t * l,
+                        const uint8_t private[NOISE_PUBLIC_KEY_LEN])
+{
+  clib_memcpy (l->l_private, private, NOISE_PUBLIC_KEY_LEN);
+  l->l_has_identity = curve25519_gen_public (l->l_public, private);
+
+  return l->l_has_identity;
+}
+
+bool
+noise_local_keys (noise_local_t * l, uint8_t public[NOISE_PUBLIC_KEY_LEN],
+                 uint8_t private[NOISE_PUBLIC_KEY_LEN])
+{
+  if (l->l_has_identity)
+    {
+      if (public != NULL)
+       clib_memcpy (public, l->l_public, NOISE_PUBLIC_KEY_LEN);
+      if (private != NULL)
+       clib_memcpy (private, l->l_private, NOISE_PUBLIC_KEY_LEN);
+    }
+  else
+    {
+      return false;
+    }
+  return true;
+}
+
+void
+noise_remote_init (noise_remote_t * r, uint32_t peer_pool_idx,
+                  const uint8_t public[NOISE_PUBLIC_KEY_LEN],
+                  noise_local_t * l)
+{
+  clib_memset (r, 0, sizeof (*r));
+  clib_memcpy (r->r_public, public, NOISE_PUBLIC_KEY_LEN);
+  r->r_peer_idx = peer_pool_idx;
+
+  ASSERT (l != NULL);
+  r->r_local = l;
+  r->r_handshake.hs_state = HS_ZEROED;
+  noise_remote_precompute (r);
+}
+
+bool
+noise_remote_set_psk (noise_remote_t * r,
+                     uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
+{
+  int same;
+  same = !clib_memcmp (r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
+  if (!same)
+    {
+      clib_memcpy (r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
+    }
+  return same == 0;
+}
+
+bool
+noise_remote_keys (noise_remote_t * r, uint8_t public[NOISE_PUBLIC_KEY_LEN],
+                  uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
+{
+  static uint8_t null_psk[NOISE_SYMMETRIC_KEY_LEN];
+  int ret;
+
+  if (public != NULL)
+    clib_memcpy (public, r->r_public, NOISE_PUBLIC_KEY_LEN);
+
+  if (psk != NULL)
+    clib_memcpy (psk, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
+  ret = clib_memcmp (r->r_psk, null_psk, NOISE_SYMMETRIC_KEY_LEN);
+
+  return ret;
+}
+
+void
+noise_remote_precompute (noise_remote_t * r)
+{
+  noise_local_t *l = r->r_local;
+  if (!l->l_has_identity)
+    clib_memset (r->r_ss, 0, NOISE_PUBLIC_KEY_LEN);
+  else if (!curve25519_gen_shared (r->r_ss, l->l_private, r->r_public))
+    clib_memset (r->r_ss, 0, NOISE_PUBLIC_KEY_LEN);
+
+  noise_remote_handshake_index_drop (r);
+  secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
+}
+
+/* Handshake functions */
+bool
+noise_create_initiation (vlib_main_t * vm, noise_remote_t * r,
+                        uint32_t * s_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                        uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
+                        uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN])
+{
+  noise_handshake_t *hs = &r->r_handshake;
+  noise_local_t *l = r->r_local;
+  uint8_t _key[NOISE_SYMMETRIC_KEY_LEN];
+  uint32_t key_idx;
+  uint8_t *key;
+  int ret = false;
+
+  key_idx =
+    vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
+                        NOISE_SYMMETRIC_KEY_LEN);
+  key = vnet_crypto_get_key (key_idx)->data;
+
+  if (!l->l_has_identity)
+    goto error;
+  noise_param_init (hs->hs_ck, hs->hs_hash, r->r_public);
+
+  /* e */
+  curve25519_gen_secret (hs->hs_e);
+  if (!curve25519_gen_public (ue, hs->hs_e))
+    goto error;
+  noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue);
+
+  /* es */
+  if (!noise_mix_dh (hs->hs_ck, key, hs->hs_e, r->r_public))
+    goto error;
+
+  /* s */
+  noise_msg_encrypt (vm, es, l->l_public, NOISE_PUBLIC_KEY_LEN, key_idx,
+                    hs->hs_hash);
+
+  /* ss */
+  if (!noise_mix_ss (hs->hs_ck, key, r->r_ss))
+    goto error;
+
+  /* {t} */
+  noise_tai64n_now (ets);
+  noise_msg_encrypt (vm, ets, ets, NOISE_TIMESTAMP_LEN, key_idx, hs->hs_hash);
+  noise_remote_handshake_index_drop (r);
+  hs->hs_state = CREATED_INITIATION;
+  hs->hs_local_index = noise_remote_handshake_index_get (r);
+  *s_idx = hs->hs_local_index;
+  ret = true;
+error:
+  vnet_crypto_key_del (vm, key_idx);
+  secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
+  return ret;
+}
+
+bool
+noise_consume_initiation (vlib_main_t * vm, noise_local_t * l,
+                         noise_remote_t ** rp, uint32_t s_idx,
+                         uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                         uint8_t es[NOISE_PUBLIC_KEY_LEN +
+                                    NOISE_AUTHTAG_LEN],
+                         uint8_t ets[NOISE_TIMESTAMP_LEN +
+                                     NOISE_AUTHTAG_LEN])
+{
+  noise_remote_t *r;
+  noise_handshake_t hs;
+  uint8_t _key[NOISE_SYMMETRIC_KEY_LEN];
+  uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
+  uint8_t timestamp[NOISE_TIMESTAMP_LEN];
+  u32 key_idx;
+  uint8_t *key;
+  int ret = false;
+
+  key_idx =
+    vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
+                        NOISE_SYMMETRIC_KEY_LEN);
+  key = vnet_crypto_get_key (key_idx)->data;
+
+  if (!l->l_has_identity)
+    goto error;
+  noise_param_init (hs.hs_ck, hs.hs_hash, l->l_public);
+
+  /* e */
+  noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue);
+
+  /* es */
+  if (!noise_mix_dh (hs.hs_ck, key, l->l_private, ue))
+    goto error;
+
+  /* s */
+
+  if (!noise_msg_decrypt (vm, r_public, es,
+                         NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN, key_idx,
+                         hs.hs_hash))
+    goto error;
+
+  /* Lookup the remote we received from */
+  if ((r = l->l_upcall.u_remote_get (r_public)) == NULL)
+    goto error;
+
+  /* ss */
+  if (!noise_mix_ss (hs.hs_ck, key, r->r_ss))
+    goto error;
+
+  /* {t} */
+  if (!noise_msg_decrypt (vm, timestamp, ets,
+                         NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN, key_idx,
+                         hs.hs_hash))
+    goto error;
+  ;
+
+  hs.hs_state = CONSUMED_INITIATION;
+  hs.hs_local_index = 0;
+  hs.hs_remote_index = s_idx;
+  clib_memcpy (hs.hs_e, ue, NOISE_PUBLIC_KEY_LEN);
+
+  /* Replay */
+  if (clib_memcmp (timestamp, r->r_timestamp, NOISE_TIMESTAMP_LEN) > 0)
+    clib_memcpy (r->r_timestamp, timestamp, NOISE_TIMESTAMP_LEN);
+  else
+    goto error;
+
+  /* Flood attack */
+  if (wg_birthdate_has_expired (r->r_last_init, REJECT_INTERVAL))
+    r->r_last_init = vlib_time_now (vm);
+  else
+    goto error;
+
+  /* Ok, we're happy to accept this initiation now */
+  noise_remote_handshake_index_drop (r);
+  r->r_handshake = hs;
+  *rp = r;
+  ret = true;
+error:
+  vnet_crypto_key_del (vm, key_idx);
+  secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
+  secure_zero_memory (&hs, sizeof (hs));
+  return ret;
+}
+
+bool
+noise_create_response (vlib_main_t * vm, noise_remote_t * r, uint32_t * s_idx,
+                      uint32_t * r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                      uint8_t en[0 + NOISE_AUTHTAG_LEN])
+{
+  noise_handshake_t *hs = &r->r_handshake;
+  uint8_t _key[NOISE_SYMMETRIC_KEY_LEN];
+  uint8_t e[NOISE_PUBLIC_KEY_LEN];
+  uint32_t key_idx;
+  uint8_t *key;
+  int ret = false;
+
+  key_idx =
+    vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
+                        NOISE_SYMMETRIC_KEY_LEN);
+  key = vnet_crypto_get_key (key_idx)->data;
+
+  if (hs->hs_state != CONSUMED_INITIATION)
+    goto error;
+
+  /* e */
+  curve25519_gen_secret (e);
+  if (!curve25519_gen_public (ue, e))
+    goto error;
+  noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue);
+
+  /* ee */
+  if (!noise_mix_dh (hs->hs_ck, NULL, e, hs->hs_e))
+    goto error;
+
+  /* se */
+  if (!noise_mix_dh (hs->hs_ck, NULL, e, r->r_public))
+    goto error;
+
+  /* psk */
+  noise_mix_psk (hs->hs_ck, hs->hs_hash, key, r->r_psk);
+
+  /* {} */
+  noise_msg_encrypt (vm, en, NULL, 0, key_idx, hs->hs_hash);
+
+
+  hs->hs_state = CREATED_RESPONSE;
+  hs->hs_local_index = noise_remote_handshake_index_get (r);
+  *r_idx = hs->hs_remote_index;
+  *s_idx = hs->hs_local_index;
+  ret = true;
+error:
+  vnet_crypto_key_del (vm, key_idx);
+  secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
+  secure_zero_memory (e, NOISE_PUBLIC_KEY_LEN);
+  return ret;
+}
+
+bool
+noise_consume_response (vlib_main_t * vm, noise_remote_t * r, uint32_t s_idx,
+                       uint32_t r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                       uint8_t en[0 + NOISE_AUTHTAG_LEN])
+{
+  noise_local_t *l = r->r_local;
+  noise_handshake_t hs;
+  uint8_t _key[NOISE_SYMMETRIC_KEY_LEN];
+  uint8_t preshared_key[NOISE_PUBLIC_KEY_LEN];
+  uint32_t key_idx;
+  uint8_t *key;
+  int ret = false;
+
+  key_idx =
+    vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
+                        NOISE_SYMMETRIC_KEY_LEN);
+  key = vnet_crypto_get_key (key_idx)->data;
+
+  if (!l->l_has_identity)
+    goto error;
+
+  hs = r->r_handshake;
+  clib_memcpy (preshared_key, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
+
+  if (hs.hs_state != CREATED_INITIATION || hs.hs_local_index != r_idx)
+    goto error;
+
+  /* e */
+  noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue);
+
+  /* ee */
+  if (!noise_mix_dh (hs.hs_ck, NULL, hs.hs_e, ue))
+    goto error;
+
+  /* se */
+  if (!noise_mix_dh (hs.hs_ck, NULL, l->l_private, ue))
+    goto error;
+
+  /* psk */
+  noise_mix_psk (hs.hs_ck, hs.hs_hash, key, preshared_key);
+
+  /* {} */
+
+  if (!noise_msg_decrypt
+      (vm, NULL, en, 0 + NOISE_AUTHTAG_LEN, key_idx, hs.hs_hash))
+    goto error;
+
+
+  hs.hs_remote_index = s_idx;
+
+  if (r->r_handshake.hs_state == hs.hs_state &&
+      r->r_handshake.hs_local_index == hs.hs_local_index)
+    {
+      r->r_handshake = hs;
+      r->r_handshake.hs_state = CONSUMED_RESPONSE;
+      ret = true;
+    }
+error:
+  vnet_crypto_key_del (vm, key_idx);
+  secure_zero_memory (&hs, sizeof (hs));
+  secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
+  return ret;
+}
+
+bool
+noise_remote_begin_session (vlib_main_t * vm, noise_remote_t * r)
+{
+  noise_handshake_t *hs = &r->r_handshake;
+  noise_keypair_t kp, *next, *current, *previous;
+
+  uint8_t key_send[NOISE_SYMMETRIC_KEY_LEN];
+  uint8_t key_recv[NOISE_SYMMETRIC_KEY_LEN];
+
+  /* We now derive the keypair from the handshake */
+  if (hs->hs_state == CONSUMED_RESPONSE)
+    {
+      kp.kp_is_initiator = 1;
+      noise_kdf (key_send, key_recv, NULL, NULL,
+                NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
+                hs->hs_ck);
+    }
+  else if (hs->hs_state == CREATED_RESPONSE)
+    {
+      kp.kp_is_initiator = 0;
+      noise_kdf (key_recv, key_send, NULL, NULL,
+                NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
+                hs->hs_ck);
+    }
+  else
+    {
+      return false;
+    }
+
+  kp.kp_valid = 1;
+  kp.kp_send_index = vnet_crypto_key_add (vm,
+                                         VNET_CRYPTO_ALG_CHACHA20_POLY1305,
+                                         key_send, NOISE_SYMMETRIC_KEY_LEN);
+  kp.kp_recv_index = vnet_crypto_key_add (vm,
+                                         VNET_CRYPTO_ALG_CHACHA20_POLY1305,
+                                         key_recv, NOISE_SYMMETRIC_KEY_LEN);
+  kp.kp_local_index = hs->hs_local_index;
+  kp.kp_remote_index = hs->hs_remote_index;
+  kp.kp_birthdate = vlib_time_now (vm);
+  clib_memset (&kp.kp_ctr, 0, sizeof (kp.kp_ctr));
+
+  /* Now we need to add_new_keypair */
+  next = r->r_next;
+  current = r->r_current;
+  previous = r->r_previous;
+
+  if (kp.kp_is_initiator)
+    {
+      if (next != NULL)
+       {
+         r->r_next = NULL;
+         r->r_previous = next;
+         noise_remote_keypair_free (vm, r, &current);
+       }
+      else
+       {
+         r->r_previous = current;
+       }
+
+      noise_remote_keypair_free (vm, r, &previous);
+
+      r->r_current = noise_remote_keypair_allocate (r);
+      *r->r_current = kp;
+    }
+  else
+    {
+      noise_remote_keypair_free (vm, r, &next);
+      r->r_previous = NULL;
+      noise_remote_keypair_free (vm, r, &previous);
+
+      r->r_next = noise_remote_keypair_allocate (r);
+      *r->r_next = kp;
+    }
+  secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
+  secure_zero_memory (&kp, sizeof (kp));
+  return true;
+}
+
+void
+noise_remote_clear (vlib_main_t * vm, noise_remote_t * r)
+{
+  noise_remote_handshake_index_drop (r);
+  secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
+
+  noise_remote_keypair_free (vm, r, &r->r_next);
+  noise_remote_keypair_free (vm, r, &r->r_current);
+  noise_remote_keypair_free (vm, r, &r->r_previous);
+  r->r_next = NULL;
+  r->r_current = NULL;
+  r->r_previous = NULL;
+}
+
+void
+noise_remote_expire_current (noise_remote_t * r)
+{
+  if (r->r_next != NULL)
+    r->r_next->kp_valid = 0;
+  if (r->r_current != NULL)
+    r->r_current->kp_valid = 0;
+}
+
+bool
+noise_remote_ready (noise_remote_t * r)
+{
+  noise_keypair_t *kp;
+  int ret;
+
+  if ((kp = r->r_current) == NULL ||
+      !kp->kp_valid ||
+      wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) ||
+      kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
+      kp->kp_ctr.c_send >= REJECT_AFTER_MESSAGES)
+    ret = false;
+  else
+    ret = true;
+  return ret;
+}
+
+static void
+chacha20poly1305_calc (vlib_main_t * vm,
+                      u8 * src,
+                      u32 src_len,
+                      u8 * dst,
+                      u8 * aad,
+                      u32 aad_len,
+                      u64 nonce,
+                      vnet_crypto_op_id_t op_id,
+                      vnet_crypto_key_index_t key_index)
+{
+  u8 iv[12];
+  clib_memset (iv, 0, 12);
+  clib_memcpy (iv + 4, &nonce, sizeof (nonce));
+
+  vnet_crypto_op_t _op, *op = &_op;
+
+  u8 _tag[16] = { };
+  if (op_id == VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC)
+    {
+      clib_memcpy (_tag, src + src_len - NOISE_AUTHTAG_LEN,
+                  NOISE_AUTHTAG_LEN);
+      src_len -= NOISE_AUTHTAG_LEN;
+    }
+  vnet_crypto_op_init (op, op_id);
+  op->key_index = key_index;
+  op->src = src;
+  op->dst = dst;
+  op->len = src_len;
+  op->aad = aad;
+  op->aad_len = aad_len;
+  op->iv = iv;
+  op->tag_len = NOISE_AUTHTAG_LEN;
+  op->tag = _tag;
+  vnet_crypto_process_ops (vm, op, 1);
+  if (op_id == VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC)
+    {
+      clib_memcpy (dst + src_len, op->tag, NOISE_AUTHTAG_LEN);
+    }
+}
+
+enum noise_state_crypt
+noise_remote_encrypt (vlib_main_t * vm, noise_remote_t * r, uint32_t * r_idx,
+                     uint64_t * nonce, uint8_t * src, size_t srclen,
+                     uint8_t * dst)
+{
+  noise_keypair_t *kp;
+  enum noise_state_crypt ret = SC_FAILED;
+
+  if ((kp = r->r_current) == NULL)
+    goto error;
+
+  /* We confirm that our values are within our tolerances. We want:
+   *  - a valid keypair
+   *  - our keypair to be less than REJECT_AFTER_TIME seconds old
+   *  - our receive counter to be less than REJECT_AFTER_MESSAGES
+   *  - our send counter to be less than REJECT_AFTER_MESSAGES
+   */
+  if (!kp->kp_valid ||
+      wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) ||
+      kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
+      ((*nonce = noise_counter_send (&kp->kp_ctr)) > REJECT_AFTER_MESSAGES))
+    goto error;
+
+  /* We encrypt into the same buffer, so the caller must ensure that buf
+   * has NOISE_AUTHTAG_LEN bytes to store the MAC. The nonce and index
+   * are passed back out to the caller through the provided data pointer. */
+  *r_idx = kp->kp_remote_index;
+
+  chacha20poly1305_calc (vm, src, srclen, dst, NULL, 0, *nonce,
+                        VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC,
+                        kp->kp_send_index);
+
+  /* If our values are still within tolerances, but we are approaching
+   * the tolerances, we notify the caller with ESTALE that they should
+   * establish a new keypair. The current keypair can continue to be used
+   * until the tolerances are hit. We notify if:
+   *  - our send counter is valid and not less than REKEY_AFTER_MESSAGES
+   *  - we're the initiator and our keypair is older than
+   *    REKEY_AFTER_TIME seconds */
+  ret = SC_KEEP_KEY_FRESH;
+  if ((kp->kp_valid && *nonce >= REKEY_AFTER_MESSAGES) ||
+      (kp->kp_is_initiator &&
+       wg_birthdate_has_expired (kp->kp_birthdate, REKEY_AFTER_TIME)))
+    goto error;
+
+  ret = SC_OK;
+error:
+  return ret;
+}
+
+enum noise_state_crypt
+noise_remote_decrypt (vlib_main_t * vm, noise_remote_t * r, uint32_t r_idx,
+                     uint64_t nonce, uint8_t * src, size_t srclen,
+                     uint8_t * dst)
+{
+  noise_keypair_t *kp;
+  enum noise_state_crypt ret = SC_FAILED;
+
+  if (r->r_current != NULL && r->r_current->kp_local_index == r_idx)
+    {
+      kp = r->r_current;
+    }
+  else if (r->r_previous != NULL && r->r_previous->kp_local_index == r_idx)
+    {
+      kp = r->r_previous;
+    }
+  else if (r->r_next != NULL && r->r_next->kp_local_index == r_idx)
+    {
+      kp = r->r_next;
+    }
+  else
+    {
+      goto error;
+    }
+
+  /* We confirm that our values are within our tolerances. These values
+   * are the same as the encrypt routine.
+   *
+   * kp_ctr isn't locked here, we're happy to accept a racy read. */
+  if (wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) ||
+      kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES)
+    goto error;
+
+  /* Decrypt, then validate the counter. We don't want to validate the
+   * counter before decrypting as we do not know the message is authentic
+   * prior to decryption. */
+  chacha20poly1305_calc (vm, src, srclen, dst, NULL, 0, nonce,
+                        VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC,
+                        kp->kp_recv_index);
+
+  if (!noise_counter_recv (&kp->kp_ctr, nonce))
+    goto error;
+
+  /* If we've received the handshake confirming data packet then move the
+   * next keypair into current. If we do slide the next keypair in, then
+   * we skip the REKEY_AFTER_TIME_RECV check. This is safe to do as a
+   * data packet can't confirm a session that we are an INITIATOR of. */
+  if (kp == r->r_next && kp->kp_local_index == r_idx)
+    {
+      noise_remote_keypair_free (vm, r, &r->r_previous);
+      r->r_previous = r->r_current;
+      r->r_current = r->r_next;
+      r->r_next = NULL;
+
+      ret = SC_CONN_RESET;
+      goto error;
+    }
+
+
+  /* Similar to when we encrypt, we want to notify the caller when we
+   * are approaching our tolerances. We notify if:
+   *  - we're the initiator and the current keypair is older than
+   *    REKEY_AFTER_TIME_RECV seconds. */
+  ret = SC_KEEP_KEY_FRESH;
+  kp = r->r_current;
+  if (kp != NULL &&
+      kp->kp_valid &&
+      kp->kp_is_initiator &&
+      wg_birthdate_has_expired (kp->kp_birthdate, REKEY_AFTER_TIME_RECV))
+    goto error;
+
+  ret = SC_OK;
+error:
+  return ret;
+}
+
+/* Private functions - these should not be called outside this file under any
+ * circumstances. */
+static noise_keypair_t *
+noise_remote_keypair_allocate (noise_remote_t * r)
+{
+  noise_keypair_t *kp;
+  kp = clib_mem_alloc (sizeof (*kp));
+  return kp;
+}
+
+static void
+noise_remote_keypair_free (vlib_main_t * vm, noise_remote_t * r,
+                          noise_keypair_t ** kp)
+{
+  struct noise_upcall *u = &r->r_local->l_upcall;
+  if (*kp)
+    {
+      u->u_index_drop ((*kp)->kp_local_index);
+      vnet_crypto_key_del (vm, (*kp)->kp_send_index);
+      vnet_crypto_key_del (vm, (*kp)->kp_recv_index);
+      clib_mem_free (*kp);
+    }
+}
+
+static uint32_t
+noise_remote_handshake_index_get (noise_remote_t * r)
+{
+  struct noise_upcall *u = &r->r_local->l_upcall;
+  return u->u_index_set (r);
+}
+
+static void
+noise_remote_handshake_index_drop (noise_remote_t * r)
+{
+  noise_handshake_t *hs = &r->r_handshake;
+  struct noise_upcall *u = &r->r_local->l_upcall;
+  if (hs->hs_state != HS_ZEROED)
+    u->u_index_drop (hs->hs_local_index);
+}
+
+static uint64_t
+noise_counter_send (noise_counter_t * ctr)
+{
+  uint64_t ret = ctr->c_send++;
+  return ret;
+}
+
+static bool
+noise_counter_recv (noise_counter_t * ctr, uint64_t recv)
+{
+  uint64_t i, top, index_recv, index_ctr;
+  unsigned long bit;
+  bool ret = false;
+
+
+  /* Check that the recv counter is valid */
+  if (ctr->c_recv >= REJECT_AFTER_MESSAGES || recv >= REJECT_AFTER_MESSAGES)
+    goto error;
+
+  /* If the packet is out of the window, invalid */
+  if (recv + COUNTER_WINDOW_SIZE < ctr->c_recv)
+    goto error;
+
+  /* If the new counter is ahead of the current counter, we'll need to
+   * zero out the bitmap that has previously been used */
+  index_recv = recv / COUNTER_BITS;
+  index_ctr = ctr->c_recv / COUNTER_BITS;
+
+  if (recv > ctr->c_recv)
+    {
+      top = clib_min (index_recv - index_ctr, COUNTER_NUM);
+      for (i = 1; i <= top; i++)
+       ctr->c_backtrack[(i + index_ctr) & (COUNTER_NUM - 1)] = 0;
+      ctr->c_recv = recv;
+    }
+
+  index_recv %= COUNTER_NUM;
+  bit = 1ul << (recv % COUNTER_BITS);
+
+  if (ctr->c_backtrack[index_recv] & bit)
+    goto error;
+
+  ctr->c_backtrack[index_recv] |= bit;
+
+  ret = true;
+error:
+  return ret;
+}
+
+static void
+noise_kdf (uint8_t * a, uint8_t * b, uint8_t * c, const uint8_t * x,
+          size_t a_len, size_t b_len, size_t c_len, size_t x_len,
+          const uint8_t ck[NOISE_HASH_LEN])
+{
+  uint8_t out[BLAKE2S_HASH_SIZE + 1];
+  uint8_t sec[BLAKE2S_HASH_SIZE];
+
+  /* Extract entropy from "x" into sec */
+  u32 l = 0;
+  HMAC (EVP_blake2s256 (), ck, NOISE_HASH_LEN, x, x_len, sec, &l);
+  ASSERT (l == BLAKE2S_HASH_SIZE);
+  if (a == NULL || a_len == 0)
+    goto out;
+
+  /* Expand first key: key = sec, data = 0x1 */
+  out[0] = 1;
+  HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, 1, out, &l);
+  ASSERT (l == BLAKE2S_HASH_SIZE);
+  clib_memcpy (a, out, a_len);
+
+  if (b == NULL || b_len == 0)
+    goto out;
+
+  /* Expand second key: key = sec, data = "a" || 0x2 */
+  out[BLAKE2S_HASH_SIZE] = 2;
+  HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1,
+       out, &l);
+  ASSERT (l == BLAKE2S_HASH_SIZE);
+  clib_memcpy (b, out, b_len);
+
+  if (c == NULL || c_len == 0)
+    goto out;
+
+  /* Expand third key: key = sec, data = "b" || 0x3 */
+  out[BLAKE2S_HASH_SIZE] = 3;
+  HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1,
+       out, &l);
+  ASSERT (l == BLAKE2S_HASH_SIZE);
+
+  clib_memcpy (c, out, c_len);
+
+out:
+  /* Clear sensitive data from stack */
+  secure_zero_memory (sec, BLAKE2S_HASH_SIZE);
+  secure_zero_memory (out, BLAKE2S_HASH_SIZE + 1);
+}
+
+static bool
+noise_mix_dh (uint8_t ck[NOISE_HASH_LEN],
+             uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
+             const uint8_t private[NOISE_PUBLIC_KEY_LEN],
+             const uint8_t public[NOISE_PUBLIC_KEY_LEN])
+{
+  uint8_t dh[NOISE_PUBLIC_KEY_LEN];
+  if (!curve25519_gen_shared (dh, private, public))
+    return false;
+  noise_kdf (ck, key, NULL, dh,
+            NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN,
+            ck);
+  secure_zero_memory (dh, NOISE_PUBLIC_KEY_LEN);
+  return true;
+}
+
+static bool
+noise_mix_ss (uint8_t ck[NOISE_HASH_LEN],
+             uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
+             const uint8_t ss[NOISE_PUBLIC_KEY_LEN])
+{
+  static uint8_t null_point[NOISE_PUBLIC_KEY_LEN];
+  if (clib_memcmp (ss, null_point, NOISE_PUBLIC_KEY_LEN) == 0)
+    return false;
+  noise_kdf (ck, key, NULL, ss,
+            NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN,
+            ck);
+  return true;
+}
+
+static void
+noise_mix_hash (uint8_t hash[NOISE_HASH_LEN], const uint8_t * src,
+               size_t src_len)
+{
+  blake2s_state_t blake;
+
+  blake2s_init (&blake, NOISE_HASH_LEN);
+  blake2s_update (&blake, hash, NOISE_HASH_LEN);
+  blake2s_update (&blake, src, src_len);
+  blake2s_final (&blake, hash, NOISE_HASH_LEN);
+}
+
+static void
+noise_mix_psk (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
+              uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
+              const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
+{
+  uint8_t tmp[NOISE_HASH_LEN];
+
+  noise_kdf (ck, tmp, key, psk,
+            NOISE_HASH_LEN, NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN,
+            NOISE_SYMMETRIC_KEY_LEN, ck);
+  noise_mix_hash (hash, tmp, NOISE_HASH_LEN);
+  secure_zero_memory (tmp, NOISE_HASH_LEN);
+}
+
+static void
+noise_param_init (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
+                 const uint8_t s[NOISE_PUBLIC_KEY_LEN])
+{
+  blake2s_state_t blake;
+
+  blake2s (ck, NOISE_HASH_LEN, (uint8_t *) NOISE_HANDSHAKE_NAME,
+          strlen (NOISE_HANDSHAKE_NAME), NULL, 0);
+
+  blake2s_init (&blake, NOISE_HASH_LEN);
+  blake2s_update (&blake, ck, NOISE_HASH_LEN);
+  blake2s_update (&blake, (uint8_t *) NOISE_IDENTIFIER_NAME,
+                 strlen (NOISE_IDENTIFIER_NAME));
+  blake2s_final (&blake, hash, NOISE_HASH_LEN);
+
+  noise_mix_hash (hash, s, NOISE_PUBLIC_KEY_LEN);
+}
+
+static void
+noise_msg_encrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src,
+                  size_t src_len, uint32_t key_idx,
+                  uint8_t hash[NOISE_HASH_LEN])
+{
+  /* Nonce always zero for Noise_IK */
+  chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN, 0,
+                        VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC, key_idx);
+  noise_mix_hash (hash, dst, src_len + NOISE_AUTHTAG_LEN);
+}
+
+static bool
+noise_msg_decrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src,
+                  size_t src_len, uint32_t key_idx,
+                  uint8_t hash[NOISE_HASH_LEN])
+{
+  /* Nonce always zero for Noise_IK */
+  chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN, 0,
+                        VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC, key_idx);
+  noise_mix_hash (hash, src, src_len);
+  return true;
+}
+
+static void
+noise_msg_ephemeral (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
+                    const uint8_t src[NOISE_PUBLIC_KEY_LEN])
+{
+  noise_mix_hash (hash, src, NOISE_PUBLIC_KEY_LEN);
+  noise_kdf (ck, NULL, NULL, src, NOISE_HASH_LEN, 0, 0,
+            NOISE_PUBLIC_KEY_LEN, ck);
+}
+
+static void
+noise_tai64n_now (uint8_t output[NOISE_TIMESTAMP_LEN])
+{
+  uint32_t unix_sec;
+  uint32_t unix_nanosec;
+
+  uint64_t sec;
+  uint32_t nsec;
+
+  unix_time_now_nsec_fraction (&unix_sec, &unix_nanosec);
+
+  /* Round down the nsec counter to limit precise timing leak. */
+  unix_nanosec &= REJECT_INTERVAL_MASK;
+
+  /* https://cr.yp.to/libtai/tai64.html */
+  sec = htobe64 (0x400000000000000aULL + unix_sec);
+  nsec = htobe32 (unix_nanosec);
+
+  /* memcpy to output buffer, assuming output could be unaligned. */
+  clib_memcpy (output, &sec, sizeof (sec));
+  clib_memcpy (output + sizeof (sec), &nsec, sizeof (nsec));
+}
+
+static void
+secure_zero_memory (void *v, size_t n)
+{
+  static void *(*const volatile memset_v) (void *, int, size_t) = &memset;
+  memset_v (v, 0, n);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_noise.h b/src/plugins/wireguard/wireguard_noise.h
new file mode 100755 (executable)
index 0000000..1f6804c
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2020 Doc.ai and/or its affiliates.
+ * Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.
+ * Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>.
+ * 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.
+ */
+
+#ifndef __included_wg_noise_h__
+#define __included_wg_noise_h__
+
+#include <vlib/vlib.h>
+#include <vnet/crypto/crypto.h>
+#include <wireguard/blake/blake2s.h>
+#include <wireguard/wireguard_key.h>
+
+#define NOISE_PUBLIC_KEY_LEN   CURVE25519_KEY_SIZE
+#define NOISE_SYMMETRIC_KEY_LEN          32    // CHACHA20POLY1305_KEY_SIZE
+#define NOISE_TIMESTAMP_LEN    (sizeof(uint64_t) + sizeof(uint32_t))
+#define NOISE_AUTHTAG_LEN      16      //CHACHA20POLY1305_AUTHTAG_SIZE
+#define NOISE_HASH_LEN         BLAKE2S_HASH_SIZE
+
+/* Protocol string constants */
+#define NOISE_HANDSHAKE_NAME   "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
+#define NOISE_IDENTIFIER_NAME  "WireGuard v1 zx2c4 Jason@zx2c4.com"
+
+/* Constants for the counter */
+#define COUNTER_BITS_TOTAL     8192
+#define COUNTER_BITS           (sizeof(unsigned long) * 8)
+#define COUNTER_NUM            (COUNTER_BITS_TOTAL / COUNTER_BITS)
+#define COUNTER_WINDOW_SIZE    (COUNTER_BITS_TOTAL - COUNTER_BITS)
+
+/* Constants for the keypair */
+#define REKEY_AFTER_MESSAGES   (1ull << 60)
+#define REJECT_AFTER_MESSAGES  (UINT64_MAX - COUNTER_WINDOW_SIZE - 1)
+#define REKEY_AFTER_TIME       120
+#define REKEY_AFTER_TIME_RECV  165
+#define REJECT_AFTER_TIME      180
+#define REJECT_INTERVAL                (0.02)  /* fifty times per sec */
+/* 24 = floor(log2(REJECT_INTERVAL)) */
+#define REJECT_INTERVAL_MASK   (~((1ull<<24)-1))
+
+enum noise_state_crypt
+{
+  SC_OK = 0,
+  SC_CONN_RESET,
+  SC_KEEP_KEY_FRESH,
+  SC_FAILED,
+};
+
+enum noise_state_hs
+{
+  HS_ZEROED = 0,
+  CREATED_INITIATION,
+  CONSUMED_INITIATION,
+  CREATED_RESPONSE,
+  CONSUMED_RESPONSE,
+};
+
+typedef struct noise_handshake
+{
+  enum noise_state_hs hs_state;
+  uint32_t hs_local_index;
+  uint32_t hs_remote_index;
+  uint8_t hs_e[NOISE_PUBLIC_KEY_LEN];
+  uint8_t hs_hash[NOISE_HASH_LEN];
+  uint8_t hs_ck[NOISE_HASH_LEN];
+} noise_handshake_t;
+
+typedef struct noise_counter
+{
+  uint64_t c_send;
+  uint64_t c_recv;
+  unsigned long c_backtrack[COUNTER_NUM];
+} noise_counter_t;
+
+typedef struct noise_keypair
+{
+  int kp_valid;
+  int kp_is_initiator;
+  uint32_t kp_local_index;
+  uint32_t kp_remote_index;
+  vnet_crypto_key_index_t kp_send_index;
+  vnet_crypto_key_index_t kp_recv_index;
+  f64 kp_birthdate;
+  noise_counter_t kp_ctr;
+} noise_keypair_t;
+
+typedef struct noise_local noise_local_t;
+typedef struct noise_remote
+{
+  uint32_t r_peer_idx;
+  uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
+  noise_local_t *r_local;
+  uint8_t r_ss[NOISE_PUBLIC_KEY_LEN];
+
+  noise_handshake_t r_handshake;
+  uint8_t r_psk[NOISE_SYMMETRIC_KEY_LEN];
+  uint8_t r_timestamp[NOISE_TIMESTAMP_LEN];
+  f64 r_last_init;
+
+  noise_keypair_t *r_next, *r_current, *r_previous;
+} noise_remote_t;
+
+typedef struct noise_local
+{
+  bool l_has_identity;
+  uint8_t l_public[NOISE_PUBLIC_KEY_LEN];
+  uint8_t l_private[NOISE_PUBLIC_KEY_LEN];
+
+  struct noise_upcall
+  {
+    void *u_arg;
+    noise_remote_t *(*u_remote_get) (uint8_t[NOISE_PUBLIC_KEY_LEN]);
+      uint32_t (*u_index_set) (noise_remote_t *);
+    void (*u_index_drop) (uint32_t);
+  } l_upcall;
+} noise_local_t;
+
+/* Set/Get noise parameters */
+void noise_local_init (noise_local_t *, struct noise_upcall *);
+bool noise_local_set_private (noise_local_t *,
+                             const uint8_t[NOISE_PUBLIC_KEY_LEN]);
+bool noise_local_keys (noise_local_t *, uint8_t[NOISE_PUBLIC_KEY_LEN],
+                      uint8_t[NOISE_PUBLIC_KEY_LEN]);
+
+void noise_remote_init (noise_remote_t *, uint32_t,
+                       const uint8_t[NOISE_PUBLIC_KEY_LEN], noise_local_t *);
+bool noise_remote_set_psk (noise_remote_t *,
+                          uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
+bool noise_remote_keys (noise_remote_t *, uint8_t[NOISE_PUBLIC_KEY_LEN],
+                       uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
+
+/* Should be called anytime noise_local_set_private is called */
+void noise_remote_precompute (noise_remote_t *);
+
+/* Cryptographic functions */
+bool noise_create_initiation (vlib_main_t * vm, noise_remote_t *,
+                             uint32_t * s_idx,
+                             uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                             uint8_t es[NOISE_PUBLIC_KEY_LEN +
+                                        NOISE_AUTHTAG_LEN],
+                             uint8_t ets[NOISE_TIMESTAMP_LEN +
+                                         NOISE_AUTHTAG_LEN]);
+
+bool noise_consume_initiation (vlib_main_t * vm, noise_local_t *,
+                              noise_remote_t **,
+                              uint32_t s_idx,
+                              uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                              uint8_t es[NOISE_PUBLIC_KEY_LEN +
+                                         NOISE_AUTHTAG_LEN],
+                              uint8_t ets[NOISE_TIMESTAMP_LEN +
+                                          NOISE_AUTHTAG_LEN]);
+
+bool noise_create_response (vlib_main_t * vm, noise_remote_t *,
+                           uint32_t * s_idx,
+                           uint32_t * r_idx,
+                           uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                           uint8_t en[0 + NOISE_AUTHTAG_LEN]);
+
+bool noise_consume_response (vlib_main_t * vm, noise_remote_t *,
+                            uint32_t s_idx,
+                            uint32_t r_idx,
+                            uint8_t ue[NOISE_PUBLIC_KEY_LEN],
+                            uint8_t en[0 + NOISE_AUTHTAG_LEN]);
+
+bool noise_remote_begin_session (vlib_main_t * vm, noise_remote_t * r);
+void noise_remote_clear (vlib_main_t * vm, noise_remote_t * r);
+void noise_remote_expire_current (noise_remote_t * r);
+
+bool noise_remote_ready (noise_remote_t *);
+
+enum noise_state_crypt
+noise_remote_encrypt (vlib_main_t * vm, noise_remote_t *,
+                     uint32_t * r_idx,
+                     uint64_t * nonce,
+                     uint8_t * src, size_t srclen, uint8_t * dst);
+enum noise_state_crypt
+noise_remote_decrypt (vlib_main_t * vm, noise_remote_t *,
+                     uint32_t r_idx,
+                     uint64_t nonce,
+                     uint8_t * src, size_t srclen, uint8_t * dst);
+
+
+#endif /* __included_wg_noise_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_output_tun.c b/src/plugins/wireguard/wireguard_output_tun.c
new file mode 100755 (executable)
index 0000000..daec7a4
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vnet/pg/pg.h>
+#include <vnet/fib/ip6_fib.h>
+#include <vnet/fib/ip4_fib.h>
+#include <vnet/fib/fib_entry.h>
+#include <vppinfra/error.h>
+
+#include <wireguard/wireguard.h>
+#include <wireguard/wireguard_send.h>
+
+#define foreach_wg_output_error                                         \
+ _(NONE, "No error")                                                   \
+ _(PEER, "Peer error")                                                  \
+ _(KEYPAIR, "Keypair error")                                            \
+ _(HANDSHAKE_SEND, "Handshake sending failed")                          \
+ _(TOO_BIG, "packet too big")                                           \
+
+#define WG_OUTPUT_SCRATCH_SIZE 2048
+
+typedef struct wg_output_scratch_t_
+{
+  u8 scratch[WG_OUTPUT_SCRATCH_SIZE];
+} wg_output_scratch_t;
+
+/* Cache line aligned per-thread scratch space */
+static wg_output_scratch_t *wg_output_scratchs;
+
+typedef enum
+{
+#define _(sym,str) WG_OUTPUT_ERROR_##sym,
+  foreach_wg_output_error
+#undef _
+    WG_OUTPUT_N_ERROR,
+} wg_output_error_t;
+
+static char *wg_output_error_strings[] = {
+#define _(sym,string) string,
+  foreach_wg_output_error
+#undef _
+};
+
+typedef enum
+{
+  WG_OUTPUT_NEXT_ERROR,
+  WG_OUTPUT_NEXT_INTERFACE_OUTPUT,
+  WG_OUTPUT_N_NEXT,
+} wg_output_next_t;
+
+typedef struct
+{
+  ip4_udp_header_t hdr;
+} wg_output_tun_trace_t;
+
+u8 *
+format_ip4_udp_header (u8 * s, va_list * args)
+{
+  ip4_udp_header_t *hdr = va_arg (*args, ip4_udp_header_t *);
+
+  s = format (s, "%U:$U",
+             format_ip4_header, &hdr->ip4, format_udp_header, &hdr->udp);
+
+  return (s);
+}
+
+/* packet trace format function */
+static u8 *
+format_wg_output_tun_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+
+  wg_output_tun_trace_t *t = va_arg (*args, wg_output_tun_trace_t *);
+
+  s = format (s, "Encrypted packet: %U\n", format_ip4_udp_header, &t->hdr);
+  return s;
+}
+
+VLIB_NODE_FN (wg_output_tun_node) (vlib_main_t * vm,
+                                  vlib_node_runtime_t * node,
+                                  vlib_frame_t * frame)
+{
+  u32 n_left_from;
+  u32 *from;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u16 nexts[VLIB_FRAME_SIZE], *next;
+  u32 thread_index = vm->thread_index;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  b = bufs;
+  next = nexts;
+
+  vlib_get_buffers (vm, from, bufs, n_left_from);
+
+  wg_main_t *wmp = &wg_main;
+  u32 handsh_fails = 0;
+  wg_peer_t *peer = NULL;
+
+  while (n_left_from > 0)
+    {
+      ip4_udp_header_t *hdr = vlib_buffer_get_current (b[0]);
+      u8 *plain_data = vlib_buffer_get_current (b[0]) + sizeof (ip4_header_t);
+      u16 plain_data_len =
+       clib_net_to_host_u16 (((ip4_header_t *) plain_data)->length);
+
+      next[0] = WG_OUTPUT_NEXT_ERROR;
+
+      peer =
+       wg_peer_get_by_adj_index (vnet_buffer (b[0])->ip.adj_index[VLIB_TX]);
+
+      if (!peer || peer->is_dead)
+       {
+         b[0]->error = node->errors[WG_OUTPUT_ERROR_PEER];
+         goto out;
+       }
+
+      if (PREDICT_FALSE (!peer->remote.r_current))
+       {
+         if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false)))
+           handsh_fails++;
+         b[0]->error = node->errors[WG_OUTPUT_ERROR_KEYPAIR];
+         goto out;
+       }
+
+      size_t encrypted_packet_len = message_data_len (plain_data_len);
+
+      /*
+       * Ensure there is enough space to write the encrypted data
+       * into the packet
+       */
+      if (PREDICT_FALSE (encrypted_packet_len > WG_OUTPUT_SCRATCH_SIZE) ||
+         PREDICT_FALSE ((b[0]->current_data + encrypted_packet_len) <
+                        vlib_buffer_get_default_data_size (vm)))
+       {
+         b[0]->error = node->errors[WG_OUTPUT_ERROR_TOO_BIG];
+         goto out;
+       }
+
+      message_data_t *encrypted_packet =
+       (message_data_t *) wg_output_scratchs[thread_index].scratch;
+
+      enum noise_state_crypt state;
+      state =
+       noise_remote_encrypt (wmp->vlib_main,
+                             &peer->remote,
+                             &encrypted_packet->receiver_index,
+                             &encrypted_packet->counter, plain_data,
+                             plain_data_len,
+                             encrypted_packet->encrypted_data);
+      switch (state)
+       {
+       case SC_OK:
+         break;
+       case SC_KEEP_KEY_FRESH:
+         if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false)))
+           handsh_fails++;
+         break;
+       case SC_FAILED:
+         //TODO: Maybe wrong
+         if (PREDICT_FALSE (!wg_send_handshake (vm, peer, false)))
+           handsh_fails++;
+         clib_mem_free (encrypted_packet);
+         goto out;
+       default:
+         break;
+       }
+
+      // Here we are sure that can send packet to next node.
+      next[0] = WG_OUTPUT_NEXT_INTERFACE_OUTPUT;
+      encrypted_packet->header.type = MESSAGE_DATA;
+
+      clib_memcpy (plain_data, (u8 *) encrypted_packet, encrypted_packet_len);
+
+      hdr->udp.length = clib_host_to_net_u16 (encrypted_packet_len +
+                                             sizeof (udp_header_t));
+      b[0]->current_length = (encrypted_packet_len +
+                             sizeof (ip4_header_t) + sizeof (udp_header_t));
+      ip4_header_set_len_w_chksum
+       (&hdr->ip4, clib_host_to_net_u16 (b[0]->current_length));
+
+      wg_timers_any_authenticated_packet_traversal (peer);
+      wg_timers_any_authenticated_packet_sent (peer);
+      wg_timers_data_sent (peer);
+
+    out:
+      if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
+                        && (b[0]->flags & VLIB_BUFFER_IS_TRACED)))
+       {
+         wg_output_tun_trace_t *t =
+           vlib_add_trace (vm, node, b[0], sizeof (*t));
+         t->hdr = *hdr;
+       }
+      n_left_from -= 1;
+      next += 1;
+      b += 1;
+    }
+
+  vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
+
+  vlib_node_increment_counter (vm, node->node_index,
+                              WG_OUTPUT_ERROR_HANDSHAKE_SEND, handsh_fails);
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (wg_output_tun_node) =
+{
+  .name = "wg-output-tun",
+  .vector_size = sizeof (u32),
+  .format_trace = format_wg_output_tun_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+  .n_errors = ARRAY_LEN (wg_output_error_strings),
+  .error_strings = wg_output_error_strings,
+  .n_next_nodes = WG_OUTPUT_N_NEXT,
+  .next_nodes = {
+        [WG_OUTPUT_NEXT_INTERFACE_OUTPUT] = "adj-midchain-tx",
+        [WG_OUTPUT_NEXT_ERROR] = "error-drop",
+  },
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+wireguard_output_module_init (vlib_main_t * vm)
+{
+  vlib_thread_main_t *tm = vlib_get_thread_main ();
+
+  vec_validate_aligned (wg_output_scratchs, tm->n_vlib_mains,
+                       CLIB_CACHE_LINE_BYTES);
+  return (NULL);
+}
+
+VLIB_INIT_FUNCTION (wireguard_output_module_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_peer.c b/src/plugins/wireguard/wireguard_peer.c
new file mode 100755 (executable)
index 0000000..0dcc4e2
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vnet/adj/adj_midchain.h>
+#include <vnet/fib/fib_table.h>
+#include <wireguard/wireguard_peer.h>
+#include <wireguard/wireguard_if.h>
+#include <wireguard/wireguard_messages.h>
+#include <wireguard/wireguard_key.h>
+#include <wireguard/wireguard_send.h>
+#include <wireguard/wireguard.h>
+
+static fib_source_t wg_fib_source;
+
+index_t *wg_peer_by_adj_index;
+
+wg_peer_t *
+wg_peer_get (index_t peeri)
+{
+  return (pool_elt_at_index (wg_main.peers, peeri));
+}
+
+static void
+wg_peer_endpoint_reset (wg_peer_endpoint_t * ep)
+{
+  ip46_address_reset (&ep->addr);
+  ep->port = 0;
+}
+
+static void
+wg_peer_endpoint_init (wg_peer_endpoint_t * ep,
+                      const ip46_address_t * addr, u16 port)
+{
+  ip46_address_copy (&ep->addr, addr);
+  ep->port = port;
+}
+
+static void
+wg_peer_fib_flush (wg_peer_t * peer)
+{
+  wg_peer_allowed_ip_t *allowed_ip;
+
+  vec_foreach (allowed_ip, peer->allowed_ips)
+  {
+    fib_table_entry_delete_index (allowed_ip->fib_entry_index, wg_fib_source);
+    allowed_ip->fib_entry_index = FIB_NODE_INDEX_INVALID;
+  }
+}
+
+static void
+wg_peer_fib_populate (wg_peer_t * peer, u32 fib_index)
+{
+  wg_peer_allowed_ip_t *allowed_ip;
+
+  vec_foreach (allowed_ip, peer->allowed_ips)
+  {
+    allowed_ip->fib_entry_index =
+      fib_table_entry_path_add (fib_index,
+                               &allowed_ip->prefix,
+                               wg_fib_source,
+                               FIB_ENTRY_FLAG_NONE,
+                               fib_proto_to_dpo (allowed_ip->
+                                                 prefix.fp_proto),
+                               &peer->dst.addr, peer->wg_sw_if_index, ~0, 1,
+                               NULL, FIB_ROUTE_PATH_FLAG_NONE);
+  }
+}
+
+static void
+wg_peer_clear (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_timers_stop (peer);
+  noise_remote_clear (vm, &peer->remote);
+  peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1);
+
+  clib_memset (&peer->cookie_maker, 0, sizeof (peer->cookie_maker));
+
+  wg_peer_endpoint_reset (&peer->src);
+  wg_peer_endpoint_reset (&peer->dst);
+
+  if (INDEX_INVALID != peer->adj_index)
+    {
+      adj_unlock (peer->adj_index);
+      wg_peer_by_adj_index[peer->adj_index] = INDEX_INVALID;
+    }
+  wg_peer_fib_flush (peer);
+
+  peer->adj_index = INDEX_INVALID;
+  peer->persistent_keepalive_interval = 0;
+  peer->timer_handshake_attempts = 0;
+  peer->timer_need_another_keepalive = false;
+  peer->is_dead = true;
+  vec_free (peer->allowed_ips);
+}
+
+static void
+wg_peer_init (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_timers_init (peer, vlib_time_now (vm));
+  wg_peer_clear (vm, peer);
+}
+
+static u8 *
+wg_peer_build_rewrite (const wg_peer_t * peer)
+{
+  // v4 only for now
+  ip4_udp_header_t *hdr;
+  u8 *rewrite = NULL;
+
+  vec_validate (rewrite, sizeof (*hdr) - 1);
+  hdr = (ip4_udp_header_t *) rewrite;
+
+  hdr->ip4.ip_version_and_header_length = 0x45;
+  hdr->ip4.ttl = 64;
+  hdr->ip4.src_address = peer->src.addr.ip4;
+  hdr->ip4.dst_address = peer->dst.addr.ip4;
+  hdr->ip4.protocol = IP_PROTOCOL_UDP;
+  hdr->ip4.checksum = ip4_header_checksum (&hdr->ip4);
+
+  hdr->udp.src_port = clib_host_to_net_u16 (peer->src.port);
+  hdr->udp.dst_port = clib_host_to_net_u16 (peer->dst.port);
+  hdr->udp.checksum = 0;
+
+  return (rewrite);
+}
+
+static void
+wg_peer_adj_stack (wg_peer_t * peer)
+{
+  ip_adjacency_t *adj;
+  u32 sw_if_index;
+  wg_if_t *wgi;
+
+  adj = adj_get (peer->adj_index);
+  sw_if_index = adj->rewrite_header.sw_if_index;
+
+  wgi = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index));
+
+  if (!wgi)
+    return;
+
+  if (!vnet_sw_interface_is_admin_up (vnet_get_main (), wgi->sw_if_index))
+    {
+      adj_midchain_delegate_unstack (peer->adj_index);
+    }
+  else
+    {
+      /* *INDENT-OFF* */
+      fib_prefix_t dst = {
+        .fp_len = 32,
+        .fp_proto = FIB_PROTOCOL_IP4,
+        .fp_addr = peer->dst.addr,
+      };
+      /* *INDENT-ON* */
+      u32 fib_index;
+
+      fib_index = fib_table_find (FIB_PROTOCOL_IP4, peer->table_id);
+
+      adj_midchain_delegate_stack (peer->adj_index, fib_index, &dst);
+    }
+}
+
+walk_rc_t
+wg_peer_if_admin_state_change (wg_if_t * wgi, index_t peeri, void *data)
+{
+  wg_peer_adj_stack (wg_peer_get (peeri));
+
+  return (WALK_CONTINUE);
+}
+
+walk_rc_t
+wg_peer_if_table_change (wg_if_t * wgi, index_t peeri, void *data)
+{
+  wg_peer_table_bind_ctx_t *ctx = data;
+  wg_peer_t *peer;
+
+  peer = wg_peer_get (peeri);
+
+  wg_peer_fib_flush (peer);
+  wg_peer_fib_populate (peer, ctx->new_fib_index);
+
+  return (WALK_CONTINUE);
+}
+
+static int
+wg_peer_fill (vlib_main_t * vm, wg_peer_t * peer,
+             u32 table_id,
+             const ip46_address_t * dst,
+             u16 port,
+             u16 persistent_keepalive_interval,
+             const fib_prefix_t * allowed_ips, u32 wg_sw_if_index)
+{
+  wg_peer_endpoint_init (&peer->dst, dst, port);
+
+  peer->table_id = table_id;
+  peer->persistent_keepalive_interval = persistent_keepalive_interval;
+  peer->wg_sw_if_index = wg_sw_if_index;
+  peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1);
+  peer->is_dead = false;
+
+  const wg_if_t *wgi = wg_if_get (wg_if_find_by_sw_if_index (wg_sw_if_index));
+
+  if (NULL == wgi)
+    return (VNET_API_ERROR_INVALID_INTERFACE);
+
+  ip_address_to_46 (&wgi->src_ip, &peer->src.addr);
+  peer->src.port = wgi->port;
+
+  /*
+   * and an adjacency for the endpoint address in the overlay
+   * on the wg interface
+   */
+  peer->rewrite = wg_peer_build_rewrite (peer);
+
+  peer->adj_index = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4,
+                                        VNET_LINK_IP4,
+                                        &peer->dst.addr, wgi->sw_if_index);
+
+  vec_validate_init_empty (wg_peer_by_adj_index,
+                          peer->adj_index, INDEX_INVALID);
+  wg_peer_by_adj_index[peer->adj_index] = peer - wg_main.peers;
+
+  adj_nbr_midchain_update_rewrite (peer->adj_index,
+                                  NULL,
+                                  NULL,
+                                  ADJ_FLAG_MIDCHAIN_IP_STACK,
+                                  vec_dup (peer->rewrite));
+  wg_peer_adj_stack (peer);
+
+  /*
+   * add a route in the overlay to each of the allowed-ips
+   */
+  u32 ii;
+
+  vec_validate (peer->allowed_ips, vec_len (allowed_ips) - 1);
+
+  vec_foreach_index (ii, allowed_ips)
+  {
+    peer->allowed_ips[ii].prefix = allowed_ips[ii];
+  }
+
+  wg_peer_fib_populate (peer,
+                       fib_table_get_index_for_sw_if_index
+                       (FIB_PROTOCOL_IP4, peer->wg_sw_if_index));
+
+  return (0);
+}
+
+int
+wg_peer_add (u32 tun_sw_if_index,
+            const u8 public_key[NOISE_PUBLIC_KEY_LEN],
+            u32 table_id,
+            const ip46_address_t * endpoint,
+            const fib_prefix_t * allowed_ips,
+            u16 port, u16 persistent_keepalive, u32 * peer_index)
+{
+  wg_if_t *wg_if;
+  wg_peer_t *peer;
+  int rv;
+
+  vlib_main_t *vm = vlib_get_main ();
+
+  if (tun_sw_if_index == ~0)
+    return (VNET_API_ERROR_INVALID_SW_IF_INDEX);
+
+  wg_if = wg_if_get (wg_if_find_by_sw_if_index (tun_sw_if_index));
+  if (!wg_if)
+    return (VNET_API_ERROR_INVALID_SW_IF_INDEX);
+
+  /* *INDENT-OFF* */
+  pool_foreach (peer, wg_main.peers,
+  ({
+    if (!memcmp (peer->remote.r_public, public_key, NOISE_PUBLIC_KEY_LEN))
+    {
+      return (VNET_API_ERROR_ENTRY_ALREADY_EXISTS);
+    }
+  }));
+  /* *INDENT-ON* */
+
+  if (pool_elts (wg_main.peers) > MAX_PEERS)
+    return (VNET_API_ERROR_LIMIT_EXCEEDED);
+
+  pool_get (wg_main.peers, peer);
+
+  wg_peer_init (vm, peer);
+
+  rv = wg_peer_fill (vm, peer, table_id, endpoint, (u16) port,
+                    persistent_keepalive, allowed_ips, tun_sw_if_index);
+
+  if (rv)
+    {
+      wg_peer_clear (vm, peer);
+      pool_put (wg_main.peers, peer);
+      return (rv);
+    }
+
+  noise_remote_init (&peer->remote, peer - wg_main.peers, public_key,
+                    &wg_if->local);
+  cookie_maker_init (&peer->cookie_maker, public_key);
+
+  if (peer->persistent_keepalive_interval != 0)
+    {
+      wg_send_keepalive (vm, peer);
+    }
+
+  *peer_index = peer - wg_main.peers;
+  wg_if_peer_add (wg_if, *peer_index);
+
+  return (0);
+}
+
+int
+wg_peer_remove (index_t peeri)
+{
+  wg_main_t *wmp = &wg_main;
+  wg_peer_t *peer = NULL;
+  wg_if_t *wgi;
+
+  if (pool_is_free_index (wmp->peers, peeri))
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  peer = pool_elt_at_index (wmp->peers, peeri);
+
+  wgi = wg_if_get (wg_if_find_by_sw_if_index (peer->wg_sw_if_index));
+  wg_if_peer_remove (wgi, peeri);
+
+  vnet_feature_enable_disable ("ip4-output", "wg-output-tun",
+                              peer->wg_sw_if_index, 0, 0, 0);
+  wg_peer_clear (wmp->vlib_main, peer);
+  pool_put (wmp->peers, peer);
+
+  return (0);
+}
+
+void
+wg_peer_walk (wg_peer_walk_cb_t fn, void *data)
+{
+  index_t peeri;
+
+  /* *INDENT-OFF* */
+  pool_foreach_index(peeri, wg_main.peers,
+  {
+    if (WALK_STOP == fn(peeri, data))
+      break;
+  });
+  /* *INDENT-ON* */
+}
+
+static u8 *
+format_wg_peer_endpoint (u8 * s, va_list * args)
+{
+  wg_peer_endpoint_t *ep = va_arg (*args, wg_peer_endpoint_t *);
+
+  s = format (s, "%U:%d",
+             format_ip46_address, &ep->addr, IP46_TYPE_ANY, ep->port);
+
+  return (s);
+}
+
+u8 *
+format_wg_peer (u8 * s, va_list * va)
+{
+  index_t peeri = va_arg (*va, index_t);
+  wg_peer_allowed_ip_t *allowed_ip;
+  u8 key[NOISE_KEY_LEN_BASE64];
+  wg_peer_t *peer;
+
+  peer = wg_peer_get (peeri);
+  key_to_base64 (peer->remote.r_public, NOISE_PUBLIC_KEY_LEN, key);
+
+  s = format (s, "[%d] key:%=45s endpoint:[%U->%U] %U keep-alive:%d adj:%d",
+             peeri,
+             key,
+             format_wg_peer_endpoint, &peer->src,
+             format_wg_peer_endpoint, &peer->dst,
+             format_vnet_sw_if_index_name, vnet_get_main (),
+             peer->wg_sw_if_index,
+             peer->persistent_keepalive_interval, peer->adj_index);
+
+  s = format (s, "\n  allowed-ips:");
+  vec_foreach (allowed_ip, peer->allowed_ips)
+  {
+    s = format (s, " %U", format_fib_prefix, &allowed_ip->prefix);
+  }
+
+  return s;
+}
+
+static clib_error_t *
+wg_peer_module_init (vlib_main_t * vm)
+{
+  wg_fib_source = fib_source_allocate ("wireguard", 0xb0,      //
+                                      FIB_SOURCE_BH_SIMPLE);
+
+  return (NULL);
+}
+
+VLIB_INIT_FUNCTION (wg_peer_module_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_peer.h b/src/plugins/wireguard/wireguard_peer.h
new file mode 100755 (executable)
index 0000000..99c73f3
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __included_wg_peer_h__
+#define __included_wg_peer_h__
+
+#include <vnet/ip/ip.h>
+
+#include <wireguard/wireguard_cookie.h>
+#include <wireguard/wireguard_timer.h>
+#include <wireguard/wireguard_key.h>
+#include <wireguard/wireguard_messages.h>
+#include <wireguard/wireguard_if.h>
+
+typedef struct ip4_udp_header_t_
+{
+  ip4_header_t ip4;
+  udp_header_t udp;
+} __clib_packed ip4_udp_header_t;
+
+u8 *format_ip4_udp_header (u8 * s, va_list * va);
+
+typedef struct wg_peer_allowed_ip_t_
+{
+  fib_prefix_t prefix;
+  fib_node_index_t fib_entry_index;
+} wg_peer_allowed_ip_t;
+
+typedef struct wg_peer_endpoint_t_
+{
+  ip46_address_t addr;
+  u16 port;
+} wg_peer_endpoint_t;
+
+typedef struct wg_peer
+{
+  noise_remote_t remote;
+  cookie_maker_t cookie_maker;
+
+  /* Peer addresses */
+  wg_peer_endpoint_t dst;
+  wg_peer_endpoint_t src;
+  u32 table_id;
+  adj_index_t adj_index;
+
+  /* rewrite built from address information */
+  u8 *rewrite;
+
+  /* Vector of allowed-ips */
+  wg_peer_allowed_ip_t *allowed_ips;
+
+  /* The WG interface this peer is attached to */
+  u32 wg_sw_if_index;
+
+  /* Timers */
+  tw_timer_wheel_16t_2w_512sl_t timer_wheel;
+  u32 timers[WG_N_TIMERS];
+  u32 timer_handshake_attempts;
+  u16 persistent_keepalive_interval;
+  f64 last_sent_handshake;
+  bool timer_need_another_keepalive;
+
+  bool is_dead;
+} wg_peer_t;
+
+typedef struct wg_peer_table_bind_ctx_t_
+{
+  ip_address_family_t af;
+  u32 new_fib_index;
+  u32 old_fib_index;
+} wg_peer_table_bind_ctx_t;
+
+int wg_peer_add (u32 tun_sw_if_index,
+                const u8 public_key_64[NOISE_PUBLIC_KEY_LEN],
+                u32 table_id,
+                const ip46_address_t * endpoint,
+                const fib_prefix_t * allowed_ips,
+                u16 port, u16 persistent_keepalive, index_t * peer_index);
+int wg_peer_remove (u32 peer_index);
+
+typedef walk_rc_t (*wg_peer_walk_cb_t) (index_t peeri, void *arg);
+void wg_peer_walk (wg_peer_walk_cb_t fn, void *data);
+
+u8 *format_wg_peer (u8 * s, va_list * va);
+wg_peer_t *wg_peer_get (index_t peeri);
+
+walk_rc_t wg_peer_if_admin_state_change (wg_if_t * wgi, index_t peeri,
+                                        void *data);
+walk_rc_t wg_peer_if_table_change (wg_if_t * wgi, index_t peeri, void *data);
+
+/*
+ * Expoed for the data-plane
+ */
+extern index_t *wg_peer_by_adj_index;
+
+static inline wg_peer_t *
+wg_peer_get_by_adj_index (index_t ai)
+{
+  return wg_peer_get (wg_peer_by_adj_index[ai]);
+}
+
+#endif // __included_wg_peer_h__
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_send.c b/src/plugins/wireguard/wireguard_send.c
new file mode 100755 (executable)
index 0000000..a5d8aaf
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <vnet/vnet.h>
+#include <vnet/fib/ip6_fib.h>
+#include <vnet/fib/ip4_fib.h>
+#include <vnet/fib/fib_entry.h>
+#include <vnet/ip/ip6_link.h>
+#include <vnet/pg/pg.h>
+#include <vnet/udp/udp.h>
+#include <vppinfra/error.h>
+#include <wireguard/wireguard.h>
+#include <wireguard/wireguard_send.h>
+
+static int
+ip46_enqueue_packet (vlib_main_t * vm, u32 bi0, int is_ip6)
+{
+  vlib_frame_t *f = 0;
+  u32 lookup_node_index =
+    is_ip6 ? ip6_lookup_node.index : ip4_lookup_node.index;
+
+  f = vlib_get_frame_to_node (vm, lookup_node_index);
+  /* f can not be NULL here - frame allocation failure causes panic */
+
+  u32 *to_next = vlib_frame_vector_args (f);
+  f->n_vectors = 1;
+  to_next[0] = bi0;
+
+  vlib_put_frame_to_node (vm, lookup_node_index, f);
+
+  return f->n_vectors;
+}
+
+static void
+wg_buffer_prepend_rewrite (vlib_buffer_t * b0, const wg_peer_t * peer)
+{
+  ip4_udp_header_t *hdr;
+
+  vlib_buffer_advance (b0, -sizeof (*hdr));
+
+  hdr = vlib_buffer_get_current (b0);
+  clib_memcpy (hdr, peer->rewrite, vec_len (peer->rewrite));
+
+  hdr->udp.length =
+    clib_host_to_net_u16 (b0->current_length - sizeof (ip4_header_t));
+  ip4_header_set_len_w_chksum (&hdr->ip4,
+                              clib_host_to_net_u16 (b0->current_length));
+}
+
+static bool
+wg_create_buffer (vlib_main_t * vm,
+                 const wg_peer_t * peer,
+                 const u8 * packet, u32 packet_len, u32 * bi)
+{
+  u32 n_buf0 = 0;
+  vlib_buffer_t *b0;
+
+  n_buf0 = vlib_buffer_alloc (vm, bi, 1);
+  if (!n_buf0)
+    return false;
+
+  b0 = vlib_get_buffer (vm, *bi);
+
+  u8 *payload = vlib_buffer_get_current (b0);
+  clib_memcpy (payload, packet, packet_len);
+
+  b0->current_length = packet_len;
+
+  wg_buffer_prepend_rewrite (b0, peer);
+
+  return true;
+}
+
+bool
+wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry)
+{
+  wg_main_t *wmp = &wg_main;
+  message_handshake_initiation_t packet;
+
+  if (!is_retry)
+    peer->timer_handshake_attempts = 0;
+
+  if (!wg_birthdate_has_expired (peer->last_sent_handshake,
+                                REKEY_TIMEOUT) || peer->is_dead)
+    {
+      return true;
+    }
+  if (noise_create_initiation (wmp->vlib_main,
+                              &peer->remote,
+                              &packet.sender_index,
+                              packet.unencrypted_ephemeral,
+                              packet.encrypted_static,
+                              packet.encrypted_timestamp))
+    {
+      f64 now = vlib_time_now (vm);
+      packet.header.type = MESSAGE_HANDSHAKE_INITIATION;
+      cookie_maker_mac (&peer->cookie_maker, &packet.macs, &packet,
+                       sizeof (packet));
+      wg_timers_any_authenticated_packet_traversal (peer);
+      wg_timers_any_authenticated_packet_sent (peer);
+      peer->last_sent_handshake = now;
+      wg_timers_handshake_initiated (peer);
+    }
+  else
+    return false;
+  u32 bi0 = 0;
+  if (!wg_create_buffer (vm, peer, (u8 *) & packet, sizeof (packet), &bi0))
+    return false;
+  ip46_enqueue_packet (vm, bi0, false);
+
+  return true;
+}
+
+bool
+wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_main_t *wmp = &wg_main;
+  u32 size_of_packet = message_data_len (0);
+  message_data_t *packet = clib_mem_alloc (size_of_packet);
+  u32 bi0 = 0;
+  bool ret = true;
+  enum noise_state_crypt state;
+
+  if (!peer->remote.r_current)
+    {
+      wg_send_handshake (vm, peer, false);
+      goto out;
+    }
+
+  state =
+    noise_remote_encrypt (wmp->vlib_main,
+                         &peer->remote,
+                         &packet->receiver_index,
+                         &packet->counter, NULL, 0, packet->encrypted_data);
+  switch (state)
+    {
+    case SC_OK:
+      break;
+    case SC_KEEP_KEY_FRESH:
+      wg_send_handshake (vm, peer, false);
+      break;
+    case SC_FAILED:
+      ret = false;
+      goto out;
+    default:
+      break;
+    }
+  packet->header.type = MESSAGE_DATA;
+
+  if (!wg_create_buffer (vm, peer, (u8 *) packet, size_of_packet, &bi0))
+    {
+      ret = false;
+      goto out;
+    }
+
+  ip46_enqueue_packet (vm, bi0, false);
+  wg_timers_any_authenticated_packet_traversal (peer);
+  wg_timers_any_authenticated_packet_sent (peer);
+
+out:
+  clib_mem_free (packet);
+  return ret;
+}
+
+bool
+wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_main_t *wmp = &wg_main;
+  message_handshake_response_t packet;
+
+  peer->last_sent_handshake = vlib_time_now (vm);
+
+  if (noise_create_response (vm,
+                            &peer->remote,
+                            &packet.sender_index,
+                            &packet.receiver_index,
+                            packet.unencrypted_ephemeral,
+                            packet.encrypted_nothing))
+    {
+      f64 now = vlib_time_now (vm);
+      packet.header.type = MESSAGE_HANDSHAKE_RESPONSE;
+      cookie_maker_mac (&peer->cookie_maker, &packet.macs, &packet,
+                       sizeof (packet));
+
+      if (noise_remote_begin_session (wmp->vlib_main, &peer->remote))
+       {
+         wg_timers_session_derived (peer);
+         wg_timers_any_authenticated_packet_traversal (peer);
+         wg_timers_any_authenticated_packet_sent (peer);
+         peer->last_sent_handshake = now;
+
+         u32 bi0 = 0;
+         if (!wg_create_buffer (vm, peer, (u8 *) & packet,
+                                sizeof (packet), &bi0))
+           return false;
+
+         ip46_enqueue_packet (vm, bi0, false);
+       }
+      else
+       return false;
+    }
+  else
+    return false;
+  return true;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_send.h b/src/plugins/wireguard/wireguard_send.h
new file mode 100755 (executable)
index 0000000..8f5e7ab
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __included_wg_send_h__
+#define __included_wg_send_h__
+
+#include <wireguard/wireguard_peer.h>
+
+bool wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer);
+bool wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry);
+bool wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer);
+
+always_inline void
+ip4_header_set_len_w_chksum (ip4_header_t * ip4, u16 len)
+{
+  ip_csum_t sum = ip4->checksum;
+  u8 old = ip4->length;
+  u8 new = len;
+
+  sum = ip_csum_update (sum, old, new, ip4_header_t, length);
+  ip4->checksum = ip_csum_fold (sum);
+  ip4->length = new;
+}
+
+#endif /* __included_wg_send_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_timer.c b/src/plugins/wireguard/wireguard_timer.c
new file mode 100755 (executable)
index 0000000..e4d4030
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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 <wireguard/wireguard.h>
+#include <wireguard/wireguard_send.h>
+#include <wireguard/wireguard_timer.h>
+
+static u32
+get_random_u32_max (u32 max)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  u32 seed = (u32) (vlib_time_now (vm) * 1e6);
+  return random_u32 (&seed) % max;
+}
+
+static void
+stop_timer (wg_peer_t * peer, u32 timer_id)
+{
+  if (peer->timers[timer_id] != ~0)
+    {
+      tw_timer_stop_16t_2w_512sl (&peer->timer_wheel, peer->timers[timer_id]);
+      peer->timers[timer_id] = ~0;
+    }
+}
+
+static void
+start_or_update_timer (wg_peer_t * peer, u32 timer_id, u32 interval)
+{
+  if (peer->timers[timer_id] == ~0)
+    {
+      wg_main_t *wmp = &wg_main;
+      peer->timers[timer_id] =
+       tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers,
+                                    timer_id, interval);
+    }
+  else
+    {
+      tw_timer_update_16t_2w_512sl (&peer->timer_wheel,
+                                   peer->timers[timer_id], interval);
+    }
+}
+
+static void
+wg_expired_retransmit_handshake (vlib_main_t * vm, wg_peer_t * peer)
+{
+
+  if (peer->timer_handshake_attempts > MAX_TIMER_HANDSHAKES)
+    {
+      stop_timer (peer, WG_TIMER_SEND_KEEPALIVE);
+
+      /* We set a timer for destroying any residue that might be left
+       * of a partial exchange.
+       */
+
+      if (peer->timers[WG_TIMER_KEY_ZEROING] == ~0)
+       {
+         wg_main_t *wmp = &wg_main;
+
+         peer->timers[WG_TIMER_KEY_ZEROING] =
+           tw_timer_start_16t_2w_512sl (&peer->timer_wheel,
+                                        peer - wmp->peers,
+                                        WG_TIMER_KEY_ZEROING,
+                                        REJECT_AFTER_TIME * 3 * WHZ);
+       }
+    }
+  else
+    {
+      ++peer->timer_handshake_attempts;
+      wg_send_handshake (vm, peer, true);
+    }
+}
+
+static void
+wg_expired_send_keepalive (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_send_keepalive (vm, peer);
+
+  if (peer->timer_need_another_keepalive)
+    {
+      peer->timer_need_another_keepalive = false;
+      start_or_update_timer (peer, WG_TIMER_SEND_KEEPALIVE,
+                            KEEPALIVE_TIMEOUT * WHZ);
+    }
+}
+
+static void
+wg_expired_send_persistent_keepalive (vlib_main_t * vm, wg_peer_t * peer)
+{
+  if (peer->persistent_keepalive_interval)
+    {
+      wg_send_keepalive (vm, peer);
+    }
+}
+
+static void
+wg_expired_new_handshake (vlib_main_t * vm, wg_peer_t * peer)
+{
+  wg_send_handshake (vm, peer, false);
+}
+
+static void
+wg_expired_zero_key_material (vlib_main_t * vm, wg_peer_t * peer)
+{
+  if (!peer->is_dead)
+    {
+      noise_remote_clear (vm, &peer->remote);
+    }
+}
+
+
+void
+wg_timers_any_authenticated_packet_traversal (wg_peer_t * peer)
+{
+  if (peer->persistent_keepalive_interval)
+    {
+      start_or_update_timer (peer, WG_TIMER_PERSISTENT_KEEPALIVE,
+                            peer->persistent_keepalive_interval * WHZ);
+    }
+}
+
+void
+wg_timers_any_authenticated_packet_sent (wg_peer_t * peer)
+{
+  stop_timer (peer, WG_TIMER_SEND_KEEPALIVE);
+}
+
+void
+wg_timers_handshake_initiated (wg_peer_t * peer)
+{
+  u32 interval =
+    REKEY_TIMEOUT * WHZ + get_random_u32_max (REKEY_TIMEOUT_JITTER);
+  start_or_update_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE, interval);
+}
+
+void
+wg_timers_session_derived (wg_peer_t * peer)
+{
+  start_or_update_timer (peer, WG_TIMER_KEY_ZEROING,
+                        REJECT_AFTER_TIME * 3 * WHZ);
+}
+
+/* Should be called after an authenticated data packet is sent. */
+void
+wg_timers_data_sent (wg_peer_t * peer)
+{
+  u32 interval = (KEEPALIVE_TIMEOUT + REKEY_TIMEOUT) * WHZ +
+    get_random_u32_max (REKEY_TIMEOUT_JITTER);
+
+  if (peer->timers[WG_TIMER_NEW_HANDSHAKE] == ~0)
+    {
+      wg_main_t *wmp = &wg_main;
+      peer->timers[WG_TIMER_NEW_HANDSHAKE] =
+       tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers,
+                                    WG_TIMER_NEW_HANDSHAKE, interval);
+    }
+}
+
+/* Should be called after an authenticated data packet is received. */
+void
+wg_timers_data_received (wg_peer_t * peer)
+{
+  if (peer->timers[WG_TIMER_SEND_KEEPALIVE] == ~0)
+    {
+      wg_main_t *wmp = &wg_main;
+      peer->timers[WG_TIMER_SEND_KEEPALIVE] =
+       tw_timer_start_16t_2w_512sl (&peer->timer_wheel, peer - wmp->peers,
+                                    WG_TIMER_SEND_KEEPALIVE,
+                                    KEEPALIVE_TIMEOUT * WHZ);
+    }
+  else
+    {
+      peer->timer_need_another_keepalive = true;
+    }
+}
+
+/* Should be called after a handshake response message is received and processed
+ * or when getting key confirmation via the first data message.
+ */
+void
+wg_timers_handshake_complete (wg_peer_t * peer)
+{
+  stop_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE);
+
+  peer->timer_handshake_attempts = 0;
+}
+
+void
+wg_timers_any_authenticated_packet_received (wg_peer_t * peer)
+{
+  stop_timer (peer, WG_TIMER_NEW_HANDSHAKE);
+}
+
+static vlib_node_registration_t wg_timer_mngr_node;
+
+static void
+expired_timer_callback (u32 * expired_timers)
+{
+  int i;
+  u32 timer_id;
+  u32 pool_index;
+
+  wg_main_t *wmp = &wg_main;
+  vlib_main_t *vm = wmp->vlib_main;
+
+  wg_peer_t *peer;
+
+  /* Need to invalidate all of them because one can restart other */
+  for (i = 0; i < vec_len (expired_timers); i++)
+    {
+      pool_index = expired_timers[i] & 0x0FFFFFFF;
+      timer_id = expired_timers[i] >> 28;
+
+      peer = pool_elt_at_index (wmp->peers, pool_index);
+      peer->timers[timer_id] = ~0;
+    }
+
+  for (i = 0; i < vec_len (expired_timers); i++)
+    {
+      pool_index = expired_timers[i] & 0x0FFFFFFF;
+      timer_id = expired_timers[i] >> 28;
+
+      peer = pool_elt_at_index (wmp->peers, pool_index);
+      switch (timer_id)
+       {
+       case WG_TIMER_RETRANSMIT_HANDSHAKE:
+         wg_expired_retransmit_handshake (vm, peer);
+         break;
+       case WG_TIMER_PERSISTENT_KEEPALIVE:
+         wg_expired_send_persistent_keepalive (vm, peer);
+         break;
+       case WG_TIMER_SEND_KEEPALIVE:
+         wg_expired_send_keepalive (vm, peer);
+         break;
+       case WG_TIMER_NEW_HANDSHAKE:
+         wg_expired_new_handshake (vm, peer);
+         break;
+       case WG_TIMER_KEY_ZEROING:
+         wg_expired_zero_key_material (vm, peer);
+         break;
+       default:
+         break;
+       }
+    }
+}
+
+void
+wg_timers_init (wg_peer_t * peer, f64 now)
+{
+  for (int i = 0; i < WG_N_TIMERS; i++)
+    {
+      peer->timers[i] = ~0;
+    }
+  tw_timer_wheel_16t_2w_512sl_t *tw = &peer->timer_wheel;
+  tw_timer_wheel_init_16t_2w_512sl (tw,
+                                   expired_timer_callback,
+                                   WG_TICK /* timer period in s */ , ~0);
+  tw->last_run_time = now;
+  peer->adj_index = INDEX_INVALID;
+}
+
+static uword
+wg_timer_mngr_fn (vlib_main_t * vm, vlib_node_runtime_t * rt,
+                 vlib_frame_t * f)
+{
+  wg_main_t *wmp = &wg_main;
+  wg_peer_t *peers;
+  wg_peer_t *peer;
+
+  while (1)
+    {
+      vlib_process_wait_for_event_or_clock (vm, WG_TICK);
+      vlib_process_get_events (vm, NULL);
+
+      peers = wmp->peers;
+      /* *INDENT-OFF* */
+      pool_foreach (peer, peers,
+      ({
+        tw_timer_expire_timers_16t_2w_512sl
+        (&peer->timer_wheel, vlib_time_now (vm));
+      }));
+      /* *INDENT-ON* */
+    }
+
+  return 0;
+}
+
+void
+wg_timers_stop (wg_peer_t * peer)
+{
+  stop_timer (peer, WG_TIMER_RETRANSMIT_HANDSHAKE);
+  stop_timer (peer, WG_TIMER_PERSISTENT_KEEPALIVE);
+  stop_timer (peer, WG_TIMER_SEND_KEEPALIVE);
+  stop_timer (peer, WG_TIMER_NEW_HANDSHAKE);
+  stop_timer (peer, WG_TIMER_KEY_ZEROING);
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (wg_timer_mngr_node, static) = {
+    .function = wg_timer_mngr_fn,
+    .type = VLIB_NODE_TYPE_PROCESS,
+    .name =
+    "wg-timer-manager",
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/wireguard/wireguard_timer.h b/src/plugins/wireguard/wireguard_timer.h
new file mode 100755 (executable)
index 0000000..457dce2
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020 Doc.ai 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.
+ */
+
+#ifndef __included_wg_timer_h__
+#define __included_wg_timer_h__
+
+#include <vlib/vlib.h>
+#include <vppinfra/clib.h>
+#include <vppinfra/tw_timer_16t_2w_512sl.h>
+
+/** WG timers */
+#define foreach_wg_timer                            \
+  _(RETRANSMIT_HANDSHAKE, "RETRANSMIT HANDSHAKE")   \
+  _(PERSISTENT_KEEPALIVE, "PERSISTENT KEEPALIVE")   \
+  _(SEND_KEEPALIVE, "SEND KEEPALIVE")               \
+  _(NEW_HANDSHAKE, "NEW HANDSHAKE")                 \
+  _(KEY_ZEROING, "KEY ZEROING")                     \
+
+typedef enum _wg_timers
+{
+#define _(sym, str) WG_TIMER_##sym,
+  foreach_wg_timer
+#undef _
+  WG_N_TIMERS
+} wg_timers_e;
+
+typedef struct wg_peer wg_peer_t;
+
+void wg_timers_init (wg_peer_t * peer, f64 now);
+void wg_timers_stop (wg_peer_t * peer);
+void wg_timers_data_sent (wg_peer_t * peer);
+void wg_timers_data_received (wg_peer_t * peer);
+void wg_timers_any_authenticated_packet_sent (wg_peer_t * peer);
+void wg_timers_any_authenticated_packet_received (wg_peer_t * peer);
+void wg_timers_handshake_initiated (wg_peer_t * peer);
+void wg_timers_handshake_complete (wg_peer_t * peer);
+void wg_timers_session_derived (wg_peer_t * peer);
+void wg_timers_any_authenticated_packet_traversal (wg_peer_t * peer);
+
+
+static inline bool
+wg_birthdate_has_expired (f64 birthday_seconds, f64 expiration_seconds)
+{
+  f64 now_seconds = vlib_time_now (vlib_get_main ());
+  return (birthday_seconds + expiration_seconds) < now_seconds;
+}
+
+
+#endif /* __included_wg_timer_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index a17c49f..f955f37 100644 (file)
@@ -74,7 +74,7 @@ cryptography==2.9.2 \
     --hash=sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e \
     --hash=sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785 \
     --hash=sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0 \
-    # via -r requirements.txt
+    # via -r requirements.txt, noiseprotocol
 deprecation==2.1.0 \
     --hash=sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff \
     --hash=sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a \
@@ -149,6 +149,9 @@ mccabe==0.6.1 \
     --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
     --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \
     # via flake8
+noiseprotocol==0.3.1 \
+    --hash=sha256:2e1a603a38439636cf0ffd8b3e8b12cee27d368a28b41be7dbe568b2abb23111 \
+    # via -r requirements.txt
 objgraph==3.4.1 \
     --hash=sha256:10d50116d1a66934d143a07308a5b3b3edcc021e1457f66ff792584166cae7cd \
     --hash=sha256:bf29512d7f8b457b53fa0722ea59f516abb8abc59b78f97f0ef81394a0c615a7 \
index 5f9f44c..e934bc0 100644 (file)
@@ -19,3 +19,4 @@ objgraph                                # MIT
 pympler                                 # Apache-2.0
 sphinx<3.0.0                            # BSD,  sphinx 3.0.0 crashes with a traceback
 sphinx-rtd-theme                        # MIT
+noiseprotocol                           # MIT