Initial import

There's still more to do with wiring this up properly.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2021-03-17 09:34:21 -06:00
commit 362884e650
14 changed files with 7858 additions and 0 deletions

17
COPYING Normal file
View File

@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# WireGuard for FreeBSD
This is a kernel module for FreeBSD to support [WireGuard](https://www.wireguard.com/). It is being developed here before its eventual submission to FreeBSD 13.1 or 14.
### Installation instructions
```
TODO
```

11
src/Makefile Normal file
View File

@ -0,0 +1,11 @@
# $FreeBSD$
KMOD= if_wg
.PATH: ${SRCTOP}/sys/dev/if_wg
SRCS= opt_inet.h opt_inet6.h device_if.h bus_if.h ifdi_if.h
SRCS+= if_wg.c wg_noise.c wg_cookie.c crypto.c
.include <bsd.kmod.mk>

1694
src/crypto.c Normal file

File diff suppressed because it is too large Load Diff

103
src/crypto.h Normal file
View File

@ -0,0 +1,103 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#ifndef _WG_CRYPTO
#define _WG_CRYPTO
#include <sys/types.h>
enum chacha20poly1305_lengths {
XCHACHA20POLY1305_NONCE_SIZE = 24,
CHACHA20POLY1305_KEY_SIZE = 32,
CHACHA20POLY1305_AUTHTAG_SIZE = 16
};
void
chacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, const size_t src_len,
const uint8_t *ad, const size_t ad_len,
const uint64_t nonce,
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
bool
chacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, const size_t src_len,
const uint8_t *ad, const size_t ad_len,
const uint64_t nonce,
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
void
xchacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src,
const size_t src_len, const uint8_t *ad,
const size_t ad_len,
const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
bool
xchacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src,
const size_t src_len, const uint8_t *ad,
const size_t ad_len,
const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
enum blake2s_lengths {
BLAKE2S_BLOCK_SIZE = 64,
BLAKE2S_HASH_SIZE = 32,
BLAKE2S_KEY_SIZE = 32
};
struct blake2s_state {
uint32_t h[8];
uint32_t t[2];
uint32_t f[2];
uint8_t buf[BLAKE2S_BLOCK_SIZE];
unsigned int buflen;
unsigned int outlen;
};
void blake2s_init(struct blake2s_state *state, const size_t outlen);
void blake2s_init_key(struct blake2s_state *state, const size_t outlen,
const uint8_t *key, const size_t keylen);
void blake2s_update(struct blake2s_state *state, const uint8_t *in, size_t inlen);
void blake2s_final(struct blake2s_state *state, uint8_t *out);
void blake2s(uint8_t *out, const uint8_t *in, const uint8_t *key,
const size_t outlen, const size_t inlen, const size_t keylen);
void blake2s_hmac(uint8_t *out, const uint8_t *in, const uint8_t *key,
const size_t outlen, const size_t inlen, const size_t keylen);
enum curve25519_lengths {
CURVE25519_KEY_SIZE = 32
};
bool curve25519(uint8_t mypublic[static CURVE25519_KEY_SIZE],
const uint8_t secret[static CURVE25519_KEY_SIZE],
const uint8_t basepoint[static CURVE25519_KEY_SIZE]);
static inline bool
curve25519_generate_public(uint8_t pub[static CURVE25519_KEY_SIZE],
const uint8_t secret[static CURVE25519_KEY_SIZE])
{
static const uint8_t basepoint[CURVE25519_KEY_SIZE] = { 9 };
return curve25519(pub, secret, basepoint);
}
static inline void curve25519_clamp_secret(uint8_t secret[static CURVE25519_KEY_SIZE])
{
secret[0] &= 248;
secret[31] = (secret[31] & 127) | 64;
}
static inline void curve25519_generate_secret(uint8_t secret[CURVE25519_KEY_SIZE])
{
arc4random_buf(secret, CURVE25519_KEY_SIZE);
curve25519_clamp_secret(secret);
}
#endif

3451
src/if_wg.c Normal file

File diff suppressed because it is too large Load Diff

37
src/if_wg.h Normal file
View File

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $FreeBSD$
*/
#ifndef __IF_WG_H__
#define __IF_WG_H__
#include <net/if.h>
#include <netinet/in.h>
struct wg_data_io {
char wgd_name[IFNAMSIZ];
void *wgd_data;
size_t wgd_size;
};
#define WG_KEY_SIZE 32
#define SIOCSWG _IOWR('i', 210, struct wg_data_io)
#define SIOCGWG _IOWR('i', 211, struct wg_data_io)
#endif /* __IF_WG_H__ */

56
src/support.h Normal file
View File

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (C) 2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#ifndef _WG_SUPPORT
#define _WG_SUPPORT
#include <sys/types.h>
#include <sys/limits.h>
#include <sys/endian.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <vm/uma.h>
/* TODO the following is openbsd compat defines to allow us to copy the wg_*
* files from openbsd (almost) verbatim. this will greatly increase maintenance
* across the platforms. it should be moved to it's own file. the only thing
* we're missing from this is struct pool (freebsd: uma_zone_t), which isn't a
* show stopper, but is something worth considering in the future.
* - md */
#define rw_assert_wrlock(x) rw_assert(x, RA_WLOCKED)
#define rw_enter_write rw_wlock
#define rw_exit_write rw_wunlock
#define rw_enter_read rw_rlock
#define rw_exit_read rw_runlock
#define rw_exit rw_unlock
#define RW_DOWNGRADE 1
#define rw_enter(x, y) do { \
CTASSERT(y == RW_DOWNGRADE); \
rw_downgrade(x); \
} while (0)
MALLOC_DECLARE(M_WG);
#include <crypto/siphash/siphash.h>
typedef struct {
uint64_t k0;
uint64_t k1;
} SIPHASH_KEY;
static inline uint64_t
siphash24(const SIPHASH_KEY *key, const void *src, size_t len)
{
SIPHASH_CTX ctx;
return (SipHashX(&ctx, 2, 4, (const uint8_t *)key, src, len));
}
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

427
src/wg_cookie.c Normal file
View File

@ -0,0 +1,427 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/rwlock.h>
#include <sys/malloc.h> /* Because systm doesn't include M_NOWAIT, M_DEVBUF */
#include <sys/socket.h>
#include "support.h"
#include "wg_cookie.h"
static void cookie_precompute_key(uint8_t *,
const uint8_t[COOKIE_INPUT_SIZE], const char *);
static void cookie_macs_mac1(struct cookie_macs *, const void *, size_t,
const uint8_t[COOKIE_KEY_SIZE]);
static void cookie_macs_mac2(struct cookie_macs *, const void *, size_t,
const uint8_t[COOKIE_COOKIE_SIZE]);
static int cookie_timer_expired(struct timespec *, time_t, long);
static void cookie_checker_make_cookie(struct cookie_checker *,
uint8_t[COOKIE_COOKIE_SIZE], struct sockaddr *);
static int ratelimit_init(struct ratelimit *, uma_zone_t);
static void ratelimit_deinit(struct ratelimit *);
static void ratelimit_gc(struct ratelimit *, int);
static int ratelimit_allow(struct ratelimit *, struct sockaddr *);
/* Public Functions */
void
cookie_maker_init(struct cookie_maker *cp, const uint8_t key[COOKIE_INPUT_SIZE])
{
bzero(cp, 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);
rw_init(&cp->cp_lock, "cookie_maker");
}
int
cookie_checker_init(struct cookie_checker *cc, uma_zone_t zone)
{
int res;
bzero(cc, sizeof(*cc));
rw_init(&cc->cc_key_lock, "cookie_checker_key");
rw_init(&cc->cc_secret_lock, "cookie_checker_secret");
if ((res = ratelimit_init(&cc->cc_ratelimit_v4, zone)) != 0)
return res;
#ifdef INET6
if ((res = ratelimit_init(&cc->cc_ratelimit_v6, zone)) != 0) {
ratelimit_deinit(&cc->cc_ratelimit_v4);
return res;
}
#endif
return 0;
}
void
cookie_checker_update(struct cookie_checker *cc,
const uint8_t key[COOKIE_INPUT_SIZE])
{
rw_enter_write(&cc->cc_key_lock);
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 {
bzero(cc->cc_mac1_key, sizeof(cc->cc_mac1_key));
bzero(cc->cc_cookie_key, sizeof(cc->cc_cookie_key));
}
rw_exit_write(&cc->cc_key_lock);
}
void
cookie_checker_deinit(struct cookie_checker *cc)
{
ratelimit_deinit(&cc->cc_ratelimit_v4);
#ifdef INET6
ratelimit_deinit(&cc->cc_ratelimit_v6);
#endif
}
void
cookie_checker_create_payload(struct cookie_checker *cc,
struct cookie_macs *cm, uint8_t nonce[COOKIE_NONCE_SIZE],
uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], struct sockaddr *sa)
{
uint8_t cookie[COOKIE_COOKIE_SIZE];
cookie_checker_make_cookie(cc, cookie, sa);
arc4random_buf(nonce, COOKIE_NONCE_SIZE);
rw_enter_read(&cc->cc_key_lock);
xchacha20poly1305_encrypt(ecookie, cookie, COOKIE_COOKIE_SIZE,
cm->mac1, COOKIE_MAC_SIZE, nonce, cc->cc_cookie_key);
rw_exit_read(&cc->cc_key_lock);
explicit_bzero(cookie, sizeof(cookie));
}
int
cookie_maker_consume_payload(struct cookie_maker *cp,
uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE])
{
int ret = 0;
uint8_t cookie[COOKIE_COOKIE_SIZE];
rw_enter_write(&cp->cp_lock);
if (cp->cp_mac1_valid == 0) {
ret = ETIMEDOUT;
goto error;
}
if (xchacha20poly1305_decrypt(cookie, ecookie, COOKIE_ENCRYPTED_SIZE,
cp->cp_mac1_last, COOKIE_MAC_SIZE, nonce, cp->cp_cookie_key) == 0) {
ret = EINVAL;
goto error;
}
memcpy(cp->cp_cookie, cookie, COOKIE_COOKIE_SIZE);
getnanouptime(&cp->cp_birthdate);
cp->cp_mac1_valid = 0;
error:
rw_exit_write(&cp->cp_lock);
return ret;
}
void
cookie_maker_mac(struct cookie_maker *cp, struct cookie_macs *cm, void *buf,
size_t len)
{
rw_enter_read(&cp->cp_lock);
cookie_macs_mac1(cm, buf, len, cp->cp_mac1_key);
memcpy(cp->cp_mac1_last, cm->mac1, COOKIE_MAC_SIZE);
cp->cp_mac1_valid = 1;
if (!cookie_timer_expired(&cp->cp_birthdate,
COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY, 0))
cookie_macs_mac2(cm, buf, len, cp->cp_cookie);
else
bzero(cm->mac2, COOKIE_MAC_SIZE);
rw_exit_read(&cp->cp_lock);
}
int
cookie_checker_validate_macs(struct cookie_checker *cc, struct cookie_macs *cm,
void *buf, size_t len, int busy, struct sockaddr *sa)
{
struct cookie_macs our_cm;
uint8_t cookie[COOKIE_COOKIE_SIZE];
/* Validate incoming MACs */
rw_enter_read(&cc->cc_key_lock);
cookie_macs_mac1(&our_cm, buf, len, cc->cc_mac1_key);
rw_exit_read(&cc->cc_key_lock);
/* If mac1 is invald, we want to drop the packet */
if (timingsafe_bcmp(our_cm.mac1, cm->mac1, COOKIE_MAC_SIZE) != 0)
return EINVAL;
if (busy != 0) {
cookie_checker_make_cookie(cc, cookie, sa);
cookie_macs_mac2(&our_cm, buf, len, cookie);
/* If the mac2 is invalid, we want to send a cookie response */
if (timingsafe_bcmp(our_cm.mac2, cm->mac2, COOKIE_MAC_SIZE) != 0)
return EAGAIN;
/* If the mac2 is valid, we may want rate limit the peer.
* ratelimit_allow will return either 0 or ECONNREFUSED,
* implying there is no ratelimiting, or we should ratelimit
* (refuse) respectively. */
if (sa->sa_family == AF_INET)
return ratelimit_allow(&cc->cc_ratelimit_v4, sa);
#ifdef INET6
else if (sa->sa_family == AF_INET6)
return ratelimit_allow(&cc->cc_ratelimit_v6, sa);
#endif
else
return EAFNOSUPPORT;
}
return 0;
}
/* Private functions */
static void
cookie_precompute_key(uint8_t *key, const uint8_t input[COOKIE_INPUT_SIZE],
const char *label)
{
struct blake2s_state blake;
blake2s_init(&blake, COOKIE_KEY_SIZE);
blake2s_update(&blake, label, strlen(label));
blake2s_update(&blake, input, COOKIE_INPUT_SIZE);
/* TODO we shouldn't need to provide outlen to _final. we can align
* this with openbsd after fixing the blake library. */
blake2s_final(&blake, key);
}
static void
cookie_macs_mac1(struct cookie_macs *cm, const void *buf, size_t len,
const uint8_t key[COOKIE_KEY_SIZE])
{
struct blake2s_state state;
blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE);
blake2s_update(&state, buf, len);
blake2s_final(&state, cm->mac1);
}
static void
cookie_macs_mac2(struct cookie_macs *cm, const void *buf, size_t len,
const uint8_t key[COOKIE_COOKIE_SIZE])
{
struct blake2s_state 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);
}
static int
cookie_timer_expired(struct timespec *birthdate, time_t sec, long nsec)
{
struct timespec uptime;
struct timespec expire = { .tv_sec = sec, .tv_nsec = nsec };
if (birthdate->tv_sec == 0 && birthdate->tv_nsec == 0)
return ETIMEDOUT;
getnanouptime(&uptime);
timespecadd(birthdate, &expire, &expire);
return timespeccmp(&uptime, &expire, >) ? ETIMEDOUT : 0;
}
static void
cookie_checker_make_cookie(struct cookie_checker *cc,
uint8_t cookie[COOKIE_COOKIE_SIZE], struct sockaddr *sa)
{
struct blake2s_state state;
rw_enter_write(&cc->cc_secret_lock);
if (cookie_timer_expired(&cc->cc_secret_birthdate,
COOKIE_SECRET_MAX_AGE, 0)) {
arc4random_buf(cc->cc_secret, COOKIE_SECRET_SIZE);
getnanouptime(&cc->cc_secret_birthdate);
}
blake2s_init_key(&state, COOKIE_COOKIE_SIZE, cc->cc_secret,
COOKIE_SECRET_SIZE);
rw_exit_write(&cc->cc_secret_lock);
if (sa->sa_family == AF_INET) {
blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_addr,
sizeof(struct in_addr));
blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_port,
sizeof(in_port_t));
blake2s_final(&state, cookie);
#ifdef INET6
} else if (sa->sa_family == AF_INET6) {
blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_addr,
sizeof(struct in6_addr));
blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_port,
sizeof(in_port_t));
blake2s_final(&state, cookie);
#endif
} else {
arc4random_buf(cookie, COOKIE_COOKIE_SIZE);
}
}
static int
ratelimit_init(struct ratelimit *rl, uma_zone_t zone)
{
rw_init(&rl->rl_lock, "ratelimit_lock");
arc4random_buf(&rl->rl_secret, sizeof(rl->rl_secret));
rl->rl_table = hashinit_flags(RATELIMIT_SIZE, M_DEVBUF,
&rl->rl_table_mask, M_NOWAIT);
rl->rl_zone = zone;
rl->rl_table_num = 0;
return rl->rl_table == NULL ? ENOBUFS : 0;
}
static void
ratelimit_deinit(struct ratelimit *rl)
{
rw_enter_write(&rl->rl_lock);
ratelimit_gc(rl, 1);
hashdestroy(rl->rl_table, M_DEVBUF, rl->rl_table_mask);
rw_exit_write(&rl->rl_lock);
}
static void
ratelimit_gc(struct ratelimit *rl, int force)
{
size_t i;
struct ratelimit_entry *r, *tr;
struct timespec expiry;
rw_assert_wrlock(&rl->rl_lock);
if (force) {
for (i = 0; i < RATELIMIT_SIZE; i++) {
LIST_FOREACH_SAFE(r, &rl->rl_table[i], r_entry, tr) {
rl->rl_table_num--;
LIST_REMOVE(r, r_entry);
uma_zfree(rl->rl_zone, r);
}
}
return;
}
if ((cookie_timer_expired(&rl->rl_last_gc, ELEMENT_TIMEOUT, 0) &&
rl->rl_table_num > 0)) {
getnanouptime(&rl->rl_last_gc);
getnanouptime(&expiry);
expiry.tv_sec -= ELEMENT_TIMEOUT;
for (i = 0; i < RATELIMIT_SIZE; i++) {
LIST_FOREACH_SAFE(r, &rl->rl_table[i], r_entry, tr) {
if (timespeccmp(&r->r_last_time, &expiry, <)) {
rl->rl_table_num--;
LIST_REMOVE(r, r_entry);
uma_zfree(rl->rl_zone, r);
}
}
}
}
}
static int
ratelimit_allow(struct ratelimit *rl, struct sockaddr *sa)
{
uint64_t key, tokens;
struct timespec diff;
struct ratelimit_entry *r;
int ret = ECONNREFUSED;
if (sa->sa_family == AF_INET)
/* TODO siphash24 is the FreeBSD siphash, OK? */
key = siphash24(&rl->rl_secret, &satosin(sa)->sin_addr,
IPV4_MASK_SIZE);
#ifdef INET6
else if (sa->sa_family == AF_INET6)
key = siphash24(&rl->rl_secret, &satosin6(sa)->sin6_addr,
IPV6_MASK_SIZE);
#endif
else
return ret;
rw_enter_write(&rl->rl_lock);
LIST_FOREACH(r, &rl->rl_table[key & rl->rl_table_mask], r_entry) {
if (r->r_af != sa->sa_family)
continue;
if (r->r_af == AF_INET && bcmp(&r->r_in,
&satosin(sa)->sin_addr, IPV4_MASK_SIZE) != 0)
continue;
#ifdef INET6
if (r->r_af == AF_INET6 && bcmp(&r->r_in6,
&satosin6(sa)->sin6_addr, IPV6_MASK_SIZE) != 0)
continue;
#endif
/* If we get to here, we've found an entry for the endpoint.
* We apply standard token bucket, by calculating the time
* lapsed since our last_time, adding that, ensuring that we
* cap the tokens at TOKEN_MAX. If the endpoint has no tokens
* left (that is tokens <= INITIATION_COST) then we block the
* request, otherwise we subtract the INITITIATION_COST and
* return OK. */
diff = r->r_last_time;
getnanouptime(&r->r_last_time);
timespecsub(&r->r_last_time, &diff, &diff);
tokens = r->r_tokens + diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec;
if (tokens > TOKEN_MAX)
tokens = TOKEN_MAX;
if (tokens >= INITIATION_COST) {
r->r_tokens = tokens - INITIATION_COST;
goto ok;
} else {
r->r_tokens = tokens;
goto error;
}
}
/* If we get to here, we didn't have an entry for the endpoint. */
ratelimit_gc(rl, 0);
/* Hard limit on number of entries */
if (rl->rl_table_num >= RATELIMIT_SIZE_MAX)
goto error;
/* Goto error if out of memory */
if ((r = uma_zalloc(rl->rl_zone, M_NOWAIT)) == NULL)
goto error;
rl->rl_table_num++;
/* Insert entry into the hashtable and ensure it's initialised */
LIST_INSERT_HEAD(&rl->rl_table[key & rl->rl_table_mask], r, r_entry);
r->r_af = sa->sa_family;
if (r->r_af == AF_INET)
memcpy(&r->r_in, &satosin(sa)->sin_addr, IPV4_MASK_SIZE);
#ifdef INET6
else if (r->r_af == AF_INET6)
memcpy(&r->r_in6, &satosin6(sa)->sin6_addr, IPV6_MASK_SIZE);
#endif
getnanouptime(&r->r_last_time);
r->r_tokens = TOKEN_MAX - INITIATION_COST;
ok:
ret = 0;
error:
rw_exit_write(&rl->rl_lock);
return ret;
}

114
src/wg_cookie.h Normal file
View File

@ -0,0 +1,114 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#ifndef __COOKIE_H__
#define __COOKIE_H__
#include <sys/types.h>
#include <sys/time.h>
#include <sys/rwlock.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include "crypto.h"
#define COOKIE_MAC_SIZE 16
#define COOKIE_KEY_SIZE 32
#define COOKIE_NONCE_SIZE XCHACHA20POLY1305_NONCE_SIZE
#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 */
struct cookie_macs {
uint8_t mac1[COOKIE_MAC_SIZE];
uint8_t mac2[COOKIE_MAC_SIZE];
};
struct ratelimit_entry {
LIST_ENTRY(ratelimit_entry) r_entry;
sa_family_t r_af;
union {
struct in_addr r_in;
#ifdef INET6
struct in6_addr r_in6;
#endif
};
struct timespec r_last_time; /* nanouptime */
uint64_t r_tokens;
};
struct ratelimit {
SIPHASH_KEY rl_secret;
uma_zone_t rl_zone;
struct rwlock rl_lock;
LIST_HEAD(, ratelimit_entry) *rl_table;
u_long rl_table_mask;
size_t rl_table_num;
struct timespec rl_last_gc; /* nanouptime */
};
struct cookie_maker {
uint8_t cp_mac1_key[COOKIE_KEY_SIZE];
uint8_t cp_cookie_key[COOKIE_KEY_SIZE];
struct rwlock cp_lock;
uint8_t cp_cookie[COOKIE_COOKIE_SIZE];
struct timespec cp_birthdate; /* nanouptime */
int cp_mac1_valid;
uint8_t cp_mac1_last[COOKIE_MAC_SIZE];
};
struct cookie_checker {
struct ratelimit cc_ratelimit_v4;
#ifdef INET6
struct ratelimit cc_ratelimit_v6;
#endif
struct rwlock cc_key_lock;
uint8_t cc_mac1_key[COOKIE_KEY_SIZE];
uint8_t cc_cookie_key[COOKIE_KEY_SIZE];
struct rwlock cc_secret_lock;
struct timespec cc_secret_birthdate; /* nanouptime */
uint8_t cc_secret[COOKIE_SECRET_SIZE];
};
void cookie_maker_init(struct cookie_maker *, const uint8_t[COOKIE_INPUT_SIZE]);
int cookie_checker_init(struct cookie_checker *, uma_zone_t);
void cookie_checker_update(struct cookie_checker *,
const uint8_t[COOKIE_INPUT_SIZE]);
void cookie_checker_deinit(struct cookie_checker *);
void cookie_checker_create_payload(struct cookie_checker *,
struct cookie_macs *cm, uint8_t[COOKIE_NONCE_SIZE],
uint8_t [COOKIE_ENCRYPTED_SIZE], struct sockaddr *);
int cookie_maker_consume_payload(struct cookie_maker *,
uint8_t[COOKIE_NONCE_SIZE], uint8_t[COOKIE_ENCRYPTED_SIZE]);
void cookie_maker_mac(struct cookie_maker *, struct cookie_macs *,
void *, size_t);
int cookie_checker_validate_macs(struct cookie_checker *,
struct cookie_macs *, void *, size_t, int, struct sockaddr *);
#endif /* __COOKIE_H__ */

952
src/wg_noise.c Normal file
View File

@ -0,0 +1,952 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/rwlock.h>
#include "support.h"
#include "wg_noise.h"
/* Private functions */
static struct noise_keypair *
noise_remote_keypair_allocate(struct noise_remote *);
static void
noise_remote_keypair_free(struct noise_remote *,
struct noise_keypair *);
static uint32_t noise_remote_handshake_index_get(struct noise_remote *);
static void noise_remote_handshake_index_drop(struct noise_remote *);
static uint64_t noise_counter_send(struct noise_counter *);
static int noise_counter_recv(struct noise_counter *, 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 int 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 int 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(uint8_t *, const uint8_t *, size_t,
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
uint8_t [NOISE_HASH_LEN]);
static int noise_msg_decrypt(uint8_t *, const uint8_t *, size_t,
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
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 int noise_timer_expired(struct timespec *, time_t, long);
/* Set/Get noise parameters */
void
noise_local_init(struct noise_local *l, struct noise_upcall *upcall)
{
bzero(l, sizeof(*l));
rw_init(&l->l_identity_lock, "noise_local_identity");
l->l_upcall = *upcall;
}
void
noise_local_lock_identity(struct noise_local *l)
{
rw_enter_write(&l->l_identity_lock);
}
void
noise_local_unlock_identity(struct noise_local *l)
{
rw_exit_write(&l->l_identity_lock);
}
int
noise_local_set_private(struct noise_local *l,
const uint8_t private[NOISE_PUBLIC_KEY_LEN])
{
rw_assert_wrlock(&l->l_identity_lock);
memcpy(l->l_private, private, NOISE_PUBLIC_KEY_LEN);
curve25519_clamp_secret(l->l_private);
l->l_has_identity = curve25519_generate_public(l->l_public, private);
return l->l_has_identity ? 0 : ENXIO;
}
int
noise_local_keys(struct noise_local *l, uint8_t public[NOISE_PUBLIC_KEY_LEN],
uint8_t private[NOISE_PUBLIC_KEY_LEN])
{
int ret = 0;
rw_enter_read(&l->l_identity_lock);
if (l->l_has_identity) {
if (public != NULL)
memcpy(public, l->l_public, NOISE_PUBLIC_KEY_LEN);
if (private != NULL)
memcpy(private, l->l_private, NOISE_PUBLIC_KEY_LEN);
} else {
ret = ENXIO;
}
rw_exit_read(&l->l_identity_lock);
return ret;
}
void
noise_remote_init(struct noise_remote *r,
const uint8_t public[NOISE_PUBLIC_KEY_LEN], struct noise_local *l)
{
bzero(r, sizeof(*r));
memcpy(r->r_public, public, NOISE_PUBLIC_KEY_LEN);
rw_init(&r->r_handshake_lock, "noise_handshake");
rw_init(&r->r_keypair_lock, "noise_keypair");
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[0], kp_entry);
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[1], kp_entry);
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[2], kp_entry);
KASSERT(l != NULL, ("must provide local"));
r->r_local = l;
rw_enter_write(&l->l_identity_lock);
noise_remote_precompute(r);
rw_exit_write(&l->l_identity_lock);
}
int
noise_remote_set_psk(struct noise_remote *r,
const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
{
int same;
rw_enter_write(&r->r_handshake_lock);
same = !timingsafe_bcmp(r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
if (!same) {
memcpy(r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
}
rw_exit_write(&r->r_handshake_lock);
return same ? EEXIST : 0;
}
int
noise_remote_keys(struct noise_remote *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)
memcpy(public, r->r_public, NOISE_PUBLIC_KEY_LEN);
rw_enter_read(&r->r_handshake_lock);
if (psk != NULL)
memcpy(psk, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
ret = timingsafe_bcmp(r->r_psk, null_psk, NOISE_SYMMETRIC_KEY_LEN);
rw_exit_read(&r->r_handshake_lock);
/* If r_psk != null_psk return 0, else ENOENT (no psk) */
return ret ? 0 : ENOENT;
}
void
noise_remote_precompute(struct noise_remote *r)
{
struct noise_local *l = r->r_local;
rw_assert_wrlock(&l->l_identity_lock);
if (!l->l_has_identity)
bzero(r->r_ss, NOISE_PUBLIC_KEY_LEN);
else if (!curve25519(r->r_ss, l->l_private, r->r_public))
bzero(r->r_ss, NOISE_PUBLIC_KEY_LEN);
rw_enter_write(&r->r_handshake_lock);
noise_remote_handshake_index_drop(r);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
}
/* Handshake functions */
int
noise_create_initiation(struct noise_remote *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])
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_local *l = r->r_local;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
rw_enter_write(&r->r_handshake_lock);
if (!l->l_has_identity)
goto error;
noise_param_init(hs->hs_ck, hs->hs_hash, r->r_public);
/* e */
curve25519_generate_secret(hs->hs_e);
if (curve25519_generate_public(ue, hs->hs_e) == 0)
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) != 0)
goto error;
/* s */
noise_msg_encrypt(es, l->l_public,
NOISE_PUBLIC_KEY_LEN, key, hs->hs_hash);
/* ss */
if (noise_mix_ss(hs->hs_ck, key, r->r_ss) != 0)
goto error;
/* {t} */
noise_tai64n_now(ets);
noise_msg_encrypt(ets, ets,
NOISE_TIMESTAMP_LEN, key, 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 = 0;
error:
rw_exit_write(&r->r_handshake_lock);
rw_exit_read(&l->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
int
noise_consume_initiation(struct noise_local *l, struct noise_remote **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])
{
struct noise_remote *r;
struct noise_handshake hs;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
uint8_t timestamp[NOISE_TIMESTAMP_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
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) != 0)
goto error;
/* s */
if (noise_msg_decrypt(r_public, es,
NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
/* Lookup the remote we received from */
if ((r = l->l_upcall.u_remote_get(l->l_upcall.u_arg, r_public)) == NULL)
goto error;
/* ss */
if (noise_mix_ss(hs.hs_ck, key, r->r_ss) != 0)
goto error;
/* {t} */
if (noise_msg_decrypt(timestamp, ets,
NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
hs.hs_state = CONSUMED_INITIATION;
hs.hs_local_index = 0;
hs.hs_remote_index = s_idx;
memcpy(hs.hs_e, ue, NOISE_PUBLIC_KEY_LEN);
/* We have successfully computed the same results, now we ensure that
* this is not an initiation replay, or a flood attack */
rw_enter_write(&r->r_handshake_lock);
/* Replay */
if (memcmp(timestamp, r->r_timestamp, NOISE_TIMESTAMP_LEN) > 0)
memcpy(r->r_timestamp, timestamp, NOISE_TIMESTAMP_LEN);
else
goto error_set;
/* Flood attack */
if (noise_timer_expired(&r->r_last_init, 0, REJECT_INTERVAL))
getnanouptime(&r->r_last_init);
else
goto error_set;
/* Ok, we're happy to accept this initiation now */
noise_remote_handshake_index_drop(r);
r->r_handshake = hs;
*rp = r;
ret = 0;
error_set:
rw_exit_write(&r->r_handshake_lock);
error:
rw_exit_read(&l->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
explicit_bzero(&hs, sizeof(hs));
return ret;
}
int
noise_create_response(struct noise_remote *r, uint32_t *s_idx, uint32_t *r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN], uint8_t en[0 + NOISE_AUTHTAG_LEN])
{
struct noise_handshake *hs = &r->r_handshake;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t e[NOISE_PUBLIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&r->r_local->l_identity_lock);
rw_enter_write(&r->r_handshake_lock);
if (hs->hs_state != CONSUMED_INITIATION)
goto error;
/* e */
curve25519_generate_secret(e);
if (curve25519_generate_public(ue, e) == 0)
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) != 0)
goto error;
/* se */
if (noise_mix_dh(hs->hs_ck, NULL, e, r->r_public) != 0)
goto error;
/* psk */
noise_mix_psk(hs->hs_ck, hs->hs_hash, key, r->r_psk);
/* {} */
noise_msg_encrypt(en, NULL, 0, key, 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 = 0;
error:
rw_exit_write(&r->r_handshake_lock);
rw_exit_read(&r->r_local->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
explicit_bzero(e, NOISE_PUBLIC_KEY_LEN);
return ret;
}
int
noise_consume_response(struct noise_remote *r, uint32_t s_idx, uint32_t r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN], uint8_t en[0 + NOISE_AUTHTAG_LEN])
{
struct noise_local *l = r->r_local;
struct noise_handshake hs;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t preshared_key[NOISE_PUBLIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
if (!l->l_has_identity)
goto error;
rw_enter_read(&r->r_handshake_lock);
hs = r->r_handshake;
memcpy(preshared_key, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
rw_exit_read(&r->r_handshake_lock);
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) != 0)
goto error;
/* se */
if (noise_mix_dh(hs.hs_ck, NULL, l->l_private, ue) != 0)
goto error;
/* psk */
noise_mix_psk(hs.hs_ck, hs.hs_hash, key, preshared_key);
/* {} */
if (noise_msg_decrypt(NULL, en,
0 + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
hs.hs_remote_index = s_idx;
rw_enter_write(&r->r_handshake_lock);
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 = 0;
}
rw_exit_write(&r->r_handshake_lock);
error:
rw_exit_read(&l->l_identity_lock);
explicit_bzero(&hs, sizeof(hs));
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
int
noise_remote_begin_session(struct noise_remote *r)
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_keypair kp, *next, *current, *previous;
rw_enter_write(&r->r_handshake_lock);
/* We now derive the keypair from the handshake */
if (hs->hs_state == CONSUMED_RESPONSE) {
kp.kp_is_initiator = 1;
noise_kdf(kp.kp_send, kp.kp_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(kp.kp_recv, kp.kp_send, NULL, NULL,
NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
hs->hs_ck);
} else {
rw_exit_write(&r->r_handshake_lock);
return EINVAL;
}
kp.kp_valid = 1;
kp.kp_local_index = hs->hs_local_index;
kp.kp_remote_index = hs->hs_remote_index;
getnanouptime(&kp.kp_birthdate);
bzero(&kp.kp_ctr, sizeof(kp.kp_ctr));
rw_init(&kp.kp_ctr.c_lock, "noise_counter");
/* Now we need to add_new_keypair */
rw_enter_write(&r->r_keypair_lock);
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(r, current);
} else {
r->r_previous = current;
}
noise_remote_keypair_free(r, previous);
r->r_current = noise_remote_keypair_allocate(r);
*r->r_current = kp;
} else {
noise_remote_keypair_free(r, next);
r->r_previous = NULL;
noise_remote_keypair_free(r, previous);
r->r_next = noise_remote_keypair_allocate(r);
*r->r_next = kp;
}
rw_exit_write(&r->r_keypair_lock);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
explicit_bzero(&kp, sizeof(kp));
return 0;
}
void
noise_remote_clear(struct noise_remote *r)
{
rw_enter_write(&r->r_handshake_lock);
noise_remote_handshake_index_drop(r);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
rw_enter_write(&r->r_keypair_lock);
noise_remote_keypair_free(r, r->r_next);
noise_remote_keypair_free(r, r->r_current);
noise_remote_keypair_free(r, r->r_previous);
r->r_next = NULL;
r->r_current = NULL;
r->r_previous = NULL;
rw_exit_write(&r->r_keypair_lock);
}
void
noise_remote_expire_current(struct noise_remote *r)
{
rw_enter_write(&r->r_keypair_lock);
if (r->r_next != NULL)
r->r_next->kp_valid = 0;
if (r->r_current != NULL)
r->r_current->kp_valid = 0;
rw_exit_write(&r->r_keypair_lock);
}
int
noise_remote_ready(struct noise_remote *r)
{
struct noise_keypair *kp;
int ret;
rw_enter_read(&r->r_keypair_lock);
/* kp_ctr isn't locked here, we're happy to accept a racy read. */
if ((kp = r->r_current) == NULL ||
!kp->kp_valid ||
noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
kp->kp_ctr.c_send >= REJECT_AFTER_MESSAGES)
ret = EINVAL;
else
ret = 0;
rw_exit_read(&r->r_keypair_lock);
return ret;
}
int
noise_remote_encrypt(struct noise_remote *r, uint32_t *r_idx, uint64_t *nonce,
uint8_t *buf, size_t buflen)
{
struct noise_keypair *kp;
int ret = EINVAL;
rw_enter_read(&r->r_keypair_lock);
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
*
* kp_ctr isn't locked here, we're happy to accept a racy read. */
if (!kp->kp_valid ||
noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
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_encrypt(buf, buf, buflen,
NULL, 0, *nonce, kp->kp_send);
/* 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 = ESTALE;
if ((kp->kp_valid && *nonce >= REKEY_AFTER_MESSAGES) ||
(kp->kp_is_initiator &&
noise_timer_expired(&kp->kp_birthdate, REKEY_AFTER_TIME, 0)))
goto error;
ret = 0;
error:
rw_exit_read(&r->r_keypair_lock);
return ret;
}
int
noise_remote_decrypt(struct noise_remote *r, uint32_t r_idx, uint64_t nonce,
uint8_t *buf, size_t buflen)
{
struct noise_keypair *kp;
int ret = EINVAL;
/* We retrieve the keypair corresponding to the provided index. We
* attempt the current keypair first as that is most likely. We also
* want to make sure that the keypair is valid as it would be
* catastrophic to decrypt against a zero'ed keypair. */
rw_enter_read(&r->r_keypair_lock);
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 (noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
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. */
if (chacha20poly1305_decrypt(buf, buf, buflen,
NULL, 0, nonce, kp->kp_recv) == 0)
goto error;
if (noise_counter_recv(&kp->kp_ctr, nonce) != 0)
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) {
rw_exit_read(&r->r_keypair_lock);
rw_enter_write(&r->r_keypair_lock);
if (kp == r->r_next && kp->kp_local_index == r_idx) {
noise_remote_keypair_free(r, r->r_previous);
r->r_previous = r->r_current;
r->r_current = r->r_next;
r->r_next = NULL;
ret = ECONNRESET;
goto error;
}
rw_enter(&r->r_keypair_lock, RW_DOWNGRADE);
}
/* 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 = ESTALE;
kp = r->r_current;
if (kp != NULL &&
kp->kp_valid &&
kp->kp_is_initiator &&
noise_timer_expired(&kp->kp_birthdate, REKEY_AFTER_TIME_RECV, 0))
goto error;
ret = 0;
error:
rw_exit(&r->r_keypair_lock);
return ret;
}
/* Private functions - these should not be called outside this file under any
* circumstances. */
static struct noise_keypair *
noise_remote_keypair_allocate(struct noise_remote *r)
{
struct noise_keypair *kp;
kp = SLIST_FIRST(&r->r_unused_keypairs);
SLIST_REMOVE_HEAD(&r->r_unused_keypairs, kp_entry);
return kp;
}
static void
noise_remote_keypair_free(struct noise_remote *r, struct noise_keypair *kp)
{
struct noise_upcall *u = &r->r_local->l_upcall;
if (kp != NULL) {
SLIST_INSERT_HEAD(&r->r_unused_keypairs, kp, kp_entry);
u->u_index_drop(u->u_arg, kp->kp_local_index);
bzero(kp->kp_send, sizeof(kp->kp_send));
bzero(kp->kp_recv, sizeof(kp->kp_recv));
}
}
static uint32_t
noise_remote_handshake_index_get(struct noise_remote *r)
{
struct noise_upcall *u = &r->r_local->l_upcall;
return u->u_index_set(u->u_arg, r);
}
static void
noise_remote_handshake_index_drop(struct noise_remote *r)
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_upcall *u = &r->r_local->l_upcall;
rw_assert_wrlock(&r->r_handshake_lock);
if (hs->hs_state != HS_ZEROED)
u->u_index_drop(u->u_arg, hs->hs_local_index);
}
static uint64_t
noise_counter_send(struct noise_counter *ctr)
{
uint64_t ret;
rw_enter_write(&ctr->c_lock);
ret = ctr->c_send++;
rw_exit_write(&ctr->c_lock);
return ret;
}
static int
noise_counter_recv(struct noise_counter *ctr, uint64_t recv)
{
uint64_t i, top, index_recv, index_ctr;
unsigned long bit;
int ret = EEXIST;
rw_enter_write(&ctr->c_lock);
/* 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 = 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 = 0;
error:
rw_exit_write(&ctr->c_lock);
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];
#ifdef DIAGNOSTIC
MPASS(a_len <= BLAKE2S_HASH_SIZE && b_len <= BLAKE2S_HASH_SIZE &&
c_len <= BLAKE2S_HASH_SIZE);
MPASS(!(b || b_len || c || c_len) || (a && a_len));
MPASS(!(c || c_len) || (b && b_len));
#endif
/* Extract entropy from "x" into sec */
blake2s_hmac(sec, x, ck, BLAKE2S_HASH_SIZE, x_len, NOISE_HASH_LEN);
if (a == NULL || a_len == 0)
goto out;
/* Expand first key: key = sec, data = 0x1 */
out[0] = 1;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, 1, BLAKE2S_HASH_SIZE);
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;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, BLAKE2S_HASH_SIZE + 1,
BLAKE2S_HASH_SIZE);
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;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, BLAKE2S_HASH_SIZE + 1,
BLAKE2S_HASH_SIZE);
memcpy(c, out, c_len);
out:
/* Clear sensitive data from stack */
explicit_bzero(sec, BLAKE2S_HASH_SIZE);
explicit_bzero(out, BLAKE2S_HASH_SIZE + 1);
}
static int
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(dh, private, public))
return EINVAL;
noise_kdf(ck, key, NULL, dh,
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, ck);
explicit_bzero(dh, NOISE_PUBLIC_KEY_LEN);
return 0;
}
static int
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 (timingsafe_bcmp(ss, null_point, NOISE_PUBLIC_KEY_LEN) == 0)
return ENOENT;
noise_kdf(ck, key, NULL, ss,
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, ck);
return 0;
}
static void
noise_mix_hash(uint8_t hash[NOISE_HASH_LEN], const uint8_t *src,
size_t src_len)
{
struct blake2s_state blake;
blake2s_init(&blake, NOISE_HASH_LEN);
blake2s_update(&blake, hash, NOISE_HASH_LEN);
blake2s_update(&blake, src, src_len);
blake2s_final(&blake, hash);
}
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);
explicit_bzero(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])
{
struct blake2s_state blake;
blake2s(ck, (uint8_t *)NOISE_HANDSHAKE_NAME, NULL,
NOISE_HASH_LEN, strlen(NOISE_HANDSHAKE_NAME), 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_mix_hash(hash, s, NOISE_PUBLIC_KEY_LEN);
}
static void
noise_msg_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
uint8_t key[NOISE_SYMMETRIC_KEY_LEN], uint8_t hash[NOISE_HASH_LEN])
{
/* Nonce always zero for Noise_IK */
chacha20poly1305_encrypt(dst, src, src_len,
hash, NOISE_HASH_LEN, 0, key);
noise_mix_hash(hash, dst, src_len + NOISE_AUTHTAG_LEN);
}
static int
noise_msg_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
uint8_t key[NOISE_SYMMETRIC_KEY_LEN], uint8_t hash[NOISE_HASH_LEN])
{
/* Nonce always zero for Noise_IK */
if (!chacha20poly1305_decrypt(dst, src, src_len,
hash, NOISE_HASH_LEN, 0, key))
return EINVAL;
noise_mix_hash(hash, src, src_len);
return 0;
}
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])
{
struct timespec time;
uint64_t sec;
uint32_t nsec;
getnanotime(&time);
/* Round down the nsec counter to limit precise timing leak. */
time.tv_nsec &= REJECT_INTERVAL_MASK;
/* https://cr.yp.to/libtai/tai64.html */
sec = htobe64(0x400000000000000aULL + time.tv_sec);
nsec = htobe32(time.tv_nsec);
/* memcpy to output buffer, assuming output could be unaligned. */
memcpy(output, &sec, sizeof(sec));
memcpy(output + sizeof(sec), &nsec, sizeof(nsec));
}
static int
noise_timer_expired(struct timespec *birthdate, time_t sec, long nsec)
{
struct timespec uptime;
struct timespec expire = { .tv_sec = sec, .tv_nsec = nsec };
/* We don't really worry about a zeroed birthdate, to avoid the extra
* check on every encrypt/decrypt. This does mean that r_last_init
* check may fail if getnanouptime is < REJECT_INTERVAL from 0. */
getnanouptime(&uptime);
timespecadd(birthdate, &expire, &expire);
return timespeccmp(&uptime, &expire, >) ? ETIMEDOUT : 0;
}

180
src/wg_noise.h Normal file
View File

@ -0,0 +1,180 @@
/* SPDX-License-Identifier: ISC
*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#ifndef __NOISE_H__
#define __NOISE_H__
#include <sys/types.h>
#include <sys/time.h>
#include <sys/rwlock.h>
#include "crypto.h"
#define NOISE_PUBLIC_KEY_LEN CURVE25519_KEY_SIZE
#define NOISE_SYMMETRIC_KEY_LEN CHACHA20POLY1305_KEY_SIZE
#define NOISE_TIMESTAMP_LEN (sizeof(uint64_t) + sizeof(uint32_t))
#define NOISE_AUTHTAG_LEN 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 (1000000000 / 50) /* fifty times per sec */
/* 24 = floor(log2(REJECT_INTERVAL)) */
#define REJECT_INTERVAL_MASK (~((1ull<<24)-1))
enum noise_state_hs {
HS_ZEROED = 0,
CREATED_INITIATION,
CONSUMED_INITIATION,
CREATED_RESPONSE,
CONSUMED_RESPONSE,
};
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];
};
struct noise_counter {
struct rwlock c_lock;
uint64_t c_send;
uint64_t c_recv;
unsigned long c_backtrack[COUNTER_NUM];
};
struct noise_keypair {
SLIST_ENTRY(noise_keypair) kp_entry;
int kp_valid;
int kp_is_initiator;
uint32_t kp_local_index;
uint32_t kp_remote_index;
uint8_t kp_send[NOISE_SYMMETRIC_KEY_LEN];
uint8_t kp_recv[NOISE_SYMMETRIC_KEY_LEN];
struct timespec kp_birthdate; /* nanouptime */
struct noise_counter kp_ctr;
};
struct noise_remote {
uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
struct noise_local *r_local;
uint8_t r_ss[NOISE_PUBLIC_KEY_LEN];
struct rwlock r_handshake_lock;
struct noise_handshake r_handshake;
uint8_t r_psk[NOISE_SYMMETRIC_KEY_LEN];
uint8_t r_timestamp[NOISE_TIMESTAMP_LEN];
struct timespec r_last_init; /* nanouptime */
struct rwlock r_keypair_lock;
SLIST_HEAD(,noise_keypair) r_unused_keypairs;
struct noise_keypair *r_next, *r_current, *r_previous;
struct noise_keypair r_keypair[3]; /* 3: next, current, previous. */
};
struct noise_local {
struct rwlock l_identity_lock;
int 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;
struct noise_remote *
(*u_remote_get)(void *, uint8_t[NOISE_PUBLIC_KEY_LEN]);
uint32_t
(*u_index_set)(void *, struct noise_remote *);
void (*u_index_drop)(void *, uint32_t);
} l_upcall;
};
/* Set/Get noise parameters */
void noise_local_init(struct noise_local *, struct noise_upcall *);
void noise_local_lock_identity(struct noise_local *);
void noise_local_unlock_identity(struct noise_local *);
int noise_local_set_private(struct noise_local *,
const uint8_t[NOISE_PUBLIC_KEY_LEN]);
int noise_local_keys(struct noise_local *, uint8_t[NOISE_PUBLIC_KEY_LEN],
uint8_t[NOISE_PUBLIC_KEY_LEN]);
void noise_remote_init(struct noise_remote *,
const uint8_t[NOISE_PUBLIC_KEY_LEN], struct noise_local *);
int noise_remote_set_psk(struct noise_remote *,
const uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
int noise_remote_keys(struct noise_remote *, 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(struct noise_remote *);
/* Cryptographic functions */
int noise_create_initiation(
struct noise_remote *,
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]);
int noise_consume_initiation(
struct noise_local *,
struct noise_remote **,
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]);
int noise_create_response(
struct noise_remote *,
uint32_t *s_idx,
uint32_t *r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t en[0 + NOISE_AUTHTAG_LEN]);
int noise_consume_response(
struct noise_remote *,
uint32_t s_idx,
uint32_t r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t en[0 + NOISE_AUTHTAG_LEN]);
int noise_remote_begin_session(struct noise_remote *);
void noise_remote_clear(struct noise_remote *);
void noise_remote_expire_current(struct noise_remote *);
int noise_remote_ready(struct noise_remote *);
int noise_remote_encrypt(
struct noise_remote *,
uint32_t *r_idx,
uint64_t *nonce,
uint8_t *buf,
size_t buflen);
int noise_remote_decrypt(
struct noise_remote *,
uint32_t r_idx,
uint64_t nonce,
uint8_t *buf,
size_t buflen);
#endif /* __NOISE_H__ */

164
tests/if_wg_test.sh Executable file
View File

@ -0,0 +1,164 @@
# $FreeBSD$
#
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
#
# Copyright (c) 2021 The FreeBSD Foundation
. $(atf_get_srcdir)/../common/vnet.subr
atf_test_case "wg_basic" "cleanup"
wg_basic_head()
{
atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails'
atf_set require.user root
}
wg_basic_body()
{
local epair pri1 pri2 pub1 pub2 wg1 wg2
local endpoint1 endpoint2 tunnel1 tunnel2
kldload -n if_wg
pri1=$(openssl rand -base64 32)
pri2=$(openssl rand -base64 32)
endpoint1=192.168.2.1
endpoint2=192.168.2.2
tunnel1=169.254.0.1
tunnel2=169.254.0.2
epair=$(vnet_mkepair)
vnet_init
vnet_mkjail wgtest1 ${epair}a
vnet_mkjail wgtest2 ${epair}b
# Workaround for PR 254212.
jexec wgtest1 ifconfig lo0 up
jexec wgtest2 ifconfig lo0 up
jexec wgtest1 ifconfig ${epair}a $endpoint1 up
jexec wgtest2 ifconfig ${epair}b $endpoint2 up
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
wg2=$(jexec wgtest2 ifconfig wg create listen-port 12345 private-key "$pri2")
pub2=$(jexec wgtest2 ifconfig $wg2 | awk '/public-key:/ {print $2}')
atf_check -s exit:0 -o ignore \
jexec wgtest1 ifconfig $wg1 peer public-key "$pub2" \
endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/32
atf_check -s exit:0 \
jexec wgtest1 ifconfig $wg1 inet $tunnel1 up
atf_check -s exit:0 -o ignore \
jexec wgtest2 ifconfig $wg2 peer public-key "$pub1" \
endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/32
atf_check -s exit:0 \
jexec wgtest2 ifconfig $wg2 inet $tunnel2 up
# Generous timeout since the handshake takes some time.
atf_check -s exit:0 -o ignore jexec wgtest1 ping -o -t 5 -i 0.25 $tunnel2
atf_check -s exit:0 -o ignore jexec wgtest2 ping -o -t 5 -i 0.25 $tunnel1
}
wg_basic_cleanup()
{
vnet_cleanup
}
# The kernel is expecteld to silently ignore any attempt to add a peer with a
# public key identical to the host's.
atf_test_case "wg_key_peerdev_shared" "cleanup"
wg_key_peerdev_shared_head()
{
atf_set descr 'Create a wg(4) interface with a shared pubkey between device and a peer'
atf_set require.user root
}
wg_key_peerdev_shared_body()
{
local epair pri1 pub1 wg1
local endpoint1 tunnel1
kldload -n if_wg
pri1=$(openssl rand -base64 32)
endpoint1=192.168.2.1
tunnel1=169.254.0.1
vnet_mkjail wgtest1
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
atf_check -s exit:0 \
jexec wgtest1 ifconfig ${wg1} peer public-key "${pub1}" \
allowed-ips "${tunnel1}/32"
atf_check -o empty jexec wgtest1 ifconfig ${wg1} peers
}
wg_key_peerdev_shared_cleanup()
{
vnet_cleanup
}
# When a wg(8) interface has a private key reassigned that corresponds to the
# public key already on a peer, the kernel is expected to deconfigure the peer
# to resolve the conflict.
atf_test_case "wg_key_peerdev_makeshared" "cleanup"
wg_key_peerdev_makeshared_head()
{
atf_set descr 'Create a wg(4) interface and assign peer key to device'
atf_set require.progs wg
}
wg_key_peerdev_makeshared_body()
{
local epair pri1 pub1 pri2 wg1 wg2
local endpoint1 tunnel1
kldload -n if_wg
pri1=$(openssl rand -base64 32)
pri2=$(openssl rand -base64 32)
endpoint1=192.168.2.1
tunnel1=169.254.0.1
vnet_mkjail wgtest1
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
wg2=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri2")
atf_check -s exit:0 -o ignore \
jexec wgtest1 ifconfig ${wg2} peer public-key "${pub1}" \
allowed-ips "${tunnel1}/32"
atf_check -o not-empty jexec wgtest1 ifconfig ${wg2} peers
jexec wgtest1 sh -c "echo '${pri1}' > pri1"
atf_check -s exit:0 \
jexec wgtest1 wg set ${wg2} private-key pri1
atf_check -o empty jexec wgtest1 ifconfig ${wg2} peers
}
wg_key_peerdev_makeshared_cleanup()
{
vnet_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "wg_basic"
atf_add_test_case "wg_key_peerdev_shared"
atf_add_test_case "wg_key_peerdev_makeshared"
}

643
tests/netns.sh Executable file
View File

@ -0,0 +1,643 @@
#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
#
# This script tests the below topology:
#
# ┌─────────────────────┐ ┌──────────────────────────────────┐ ┌─────────────────────┐
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
# │ │ │ │ │ │
# │┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐│
# ││ wg0 │───────────┼───┼────────────│ lo │────────────┼───┼───────────│ wg0 ││
# │├────────┴──────────┐│ │ ┌───────┴────────┴────────┐ │ │┌──────────┴────────┤│
# ││192.168.241.1/24 ││ │ │(ns1) (ns2) │ │ ││192.168.241.2/24 ││
# ││fd00::1/24 ││ │ │127.0.0.1:1 127.0.0.1:2│ │ ││fd00::2/24 ││
# │└───────────────────┘│ │ │[::]:1 [::]:2 │ │ │└───────────────────┘│
# └─────────────────────┘ │ └─────────────────────────┘ │ └─────────────────────┘
# └──────────────────────────────────┘
#
# After the topology is prepared we run a series of TCP/UDP iperf3 tests between the
# wireguard peers in $ns1 and $ns2. Note that $ns0 is the endpoint for the wg0
# interfaces in $ns1 and $ns2. See https://www.wireguard.com/netns/ for further
# details on how this is accomplished.
set -e
# Needs iperf3
exec 3>&1
export LANG=C
export WG_HIDE_KEYS=never
jail0="wg-test-$$-0"
jail1="wg-test-$$-1"
jail2="wg-test-$$-2"
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
pp() { pretty "" "$*"; "$@"; }
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
j0() { pretty 0 "$*"; maybe_exec jexec $jail0 "$@"; }
j1() { pretty 1 "$*"; maybe_exec jexec $jail1 "$@"; }
j2() { pretty 2 "$*"; maybe_exec jexec $jail2 "$@"; }
ifconfig0() { j0 ifconfig "$@"; }
ifconfig1() { j1 ifconfig "$@"; }
ifconfig2() { j2 ifconfig "$@"; }
sleep() { read -t "$1" -N 1 || true; }
#waitiperf() { pretty "${1//*-}" "wait for iperf:${3:-5201} pid $2"; while [[ $(ss -N "$1" -tlpH "sport = ${3:-5201}") != *\"iperf3\",pid=$2,fd=* ]]; do sleep 0.1; done; }
waitiperf() { pretty "${1//*-}" "wait for iperf:${3:-5201} pid $2"; while ! sockstat -qj "$1" -ql -P tcp -p "${3:-5201}" | grep -Eq "iperf3[[:space:]]+$2[[:space:]]"; do sleep 0.1; done; }
waitncatudp() { pretty "${1//*-}" "wait for udp:1111 pid $2"; while [[ $(ss -N "$1" -ulpH 'sport = 1111') != *\"ncat\",pid=$2,fd=* ]]; do sleep 0.1; done; }
waitiface() { pretty "${1//*-}" "wait for $2 to come up"; jexec "$1" bash -c "while ! ifconfig wg0 | grep -qE 'flags.+UP'; do read -t .1 -N 0 || true; done;"; }
cj() { pretty "" "Creating $1"; jail -c path=/ vnet=new name="$1" persist; }
dj() { pretty "" "Deleting $1"; jail -r "$1" >/dev/null; }
cleanup() {
set +e
exec 2>/dev/null
# printf "$orig_message_cost" > /proc/sys/net/core/message_cost
dj $jail0
dj $jail1
dj $jail2
for iface in wg1 wg2; do
pretty "" "Awaiting return of ${iface}"
# Give interfaces a second to return
while ! ifconfig ${iface} &> /dev/null; do
sleep 0.1
done
ifconfig ${iface} destroy
done
exit
}
trap cleanup EXIT
dj $jail0 || true
dj $jail1 || true
dj $jail2 || true
cj $jail0
cj $jail1
cj $jail2
ifconfig wg1 create
ifconfig wg1 vnet ${jail1}
ifconfig wg2 create
ifconfig wg2 vnet ${jail2}
key1="$(pp wg genkey)"
key2="$(pp wg genkey)"
key3="$(pp wg genkey)"
key4="$(pp wg genkey)"
pub1="$(pp wg pubkey <<<"$key1")"
pub2="$(pp wg pubkey <<<"$key2")"
pub3="$(pp wg pubkey <<<"$key3")"
pub4="$(pp wg pubkey <<<"$key4")"
psk="$(pp wg genpsk)"
[[ -n $key1 && -n $key2 && -n $psk ]]
configure_peers() {
ifconfig1 wg1 inet 192.168.241.1/24
ifconfig1 wg1 inet6 fd00::1/112 up
ifconfig2 wg2 inet 192.168.241.2/24
ifconfig2 wg2 inet6 fd00::2/112 up
j1 wg set wg1 \
private-key <(echo "$key1") \
listen-port 1 \
peer "$pub2" \
preshared-key <(echo "$psk") \
allowed-ips 192.168.241.2/32,fd00::2/128
j2 wg set wg2 \
private-key <(echo "$key2") \
listen-port 2 \
peer "$pub1" \
preshared-key <(echo "$psk") \
allowed-ips 192.168.241.1/32,fd00::1/128
}
configure_peers
tests() {
# Ping over IPv4
j2 ping -c 10 -f -W 1 192.168.241.1
j1 ping -c 10 -f -W 1 192.168.241.2
# Ping over IPv6
j2 ping6 -c 10 -f -W 1 fd00::1
j1 ping6 -c 10 -f -W 1 fd00::2
# TCP over IPv4
j2 iperf3 -s -1 -B 192.168.241.2 &
waitiperf $jail2 $!
j1 iperf3 -Z -t 3 -c 192.168.241.2
# TCP over IPv6
j1 iperf3 -s -1 -B fd00::1 &
waitiperf $jail1 $!
j2 iperf3 -Z -t 3 -c fd00::1
# UDP over IPv4
j1 iperf3 -s -1 -B 192.168.241.1 &
waitiperf $jail1 $!
j2 iperf3 -Z -t 3 -b 0 -u -c 192.168.241.1
# UDP over IPv6
j2 iperf3 -s -1 -B fd00::2 &
waitiperf $jail2 $!
j1 iperf3 -Z -t 3 -b 0 -u -c fd00::2
# TCP over IPv4, in parallel
for max in 4 5 50; do
local pids=( )
for ((i=0; i < max; ++i)) do
j2 iperf3 -p $(( 5200 + i )) -s -1 -B 192.168.241.2 &
pids+=( $! ); waitiperf $jail2 $! $(( 5200 + i ))
done
for ((i=0; i < max; ++i)) do
j1 iperf3 -Z -t 3 -p $(( 5200 + i )) -c 192.168.241.2 &
done
wait "${pids[@]}"
done
}
[[ $(ifconfig1 wg1) =~ mtu\ ([0-9]+) ]] && orig_mtu="${BASH_REMATCH[1]}"
#big_mtu=$(( 34816 - 1500 + $orig_mtu ))
# XXX
big_mtu=16304
# Test using IPv4 as outer transport
j1 wg set wg1 peer "$pub2" endpoint 127.0.0.1:2
j2 wg set wg2 peer "$pub1" endpoint 127.0.0.1:1
# Before calling tests, we first make sure that the stats counters and timestamper are working
#j2 ping -c 10 -f -W 1 192.168.241.1
#{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip2 -stats link show dev wg0)
#(( rx_bytes == 1372 && (tx_bytes == 1428 || tx_bytes == 1460) ))
#{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip1 -stats link show dev wg0)
#(( tx_bytes == 1372 && (rx_bytes == 1428 || rx_bytes == 1460) ))
#read _ rx_bytes tx_bytes < <(n2 wg show wg0 transfer)
#(( rx_bytes == 1372 && (tx_bytes == 1428 || tx_bytes == 1460) ))
#read _ rx_bytes tx_bytes < <(n1 wg show wg0 transfer)
#(( tx_bytes == 1372 && (rx_bytes == 1428 || rx_bytes == 1460) ))
#read _ timestamp < <(n1 wg show wg0 latest-handshakes)
#(( timestamp != 0 ))
tests
ifconfig1 wg1 mtu $big_mtu
ifconfig2 wg2 mtu $big_mtu
tests
exit 1
ip1 link set wg0 mtu $orig_mtu
ip2 link set wg0 mtu $orig_mtu
# Test using IPv6 as outer transport
n1 wg set wg0 peer "$pub2" endpoint [::1]:2
n2 wg set wg0 peer "$pub1" endpoint [::1]:1
tests
ip1 link set wg0 mtu $big_mtu
ip2 link set wg0 mtu $big_mtu
tests
# Test that route MTUs work with the padding
ip1 link set wg0 mtu 1300
ip2 link set wg0 mtu 1300
n1 wg set wg0 peer "$pub2" endpoint 127.0.0.1:2
n2 wg set wg0 peer "$pub1" endpoint 127.0.0.1:1
n0 iptables -A INPUT -m length --length 1360 -j DROP
n1 ip route add 192.168.241.2/32 dev wg0 mtu 1299
n2 ip route add 192.168.241.1/32 dev wg0 mtu 1299
n2 ping -c 1 -W 1 -s 1269 192.168.241.1
n2 ip route delete 192.168.241.1/32 dev wg0 mtu 1299
n1 ip route delete 192.168.241.2/32 dev wg0 mtu 1299
n0 iptables -F INPUT
ip1 link set wg0 mtu $orig_mtu
ip2 link set wg0 mtu $orig_mtu
# Test using IPv4 that roaming works
ip0 -4 addr del 127.0.0.1/8 dev lo
ip0 -4 addr add 127.212.121.99/8 dev lo
n1 wg set wg0 listen-port 9999
n1 wg set wg0 peer "$pub2" endpoint 127.0.0.1:2
n1 ping6 -W 1 -c 1 fd00::2
[[ $(n2 wg show wg0 endpoints) == "$pub1 127.212.121.99:9999" ]]
# Test using IPv6 that roaming works
n1 wg set wg0 listen-port 9998
n1 wg set wg0 peer "$pub2" endpoint [::1]:2
n1 ping -W 1 -c 1 192.168.241.2
[[ $(n2 wg show wg0 endpoints) == "$pub1 [::1]:9998" ]]
# Test that crypto-RP filter works
n1 wg set wg0 peer "$pub2" allowed-ips 192.168.241.0/24
exec 4< <(n1 ncat -l -u -p 1111)
ncat_pid=$!
waitncatudp $jail1 $ncat_pid
n2 ncat -u 192.168.241.1 1111 <<<"X"
read -r -N 1 -t 1 out <&4 && [[ $out == "X" ]]
kill $ncat_pid
more_specific_key="$(pp wg genkey | pp wg pubkey)"
n1 wg set wg0 peer "$more_specific_key" allowed-ips 192.168.241.2/32
n2 wg set wg0 listen-port 9997
exec 4< <(n1 ncat -l -u -p 1111)
ncat_pid=$!
waitncatudp $jail1 $ncat_pid
n2 ncat -u 192.168.241.1 1111 <<<"X"
! read -r -N 1 -t 1 out <&4 || false
kill $ncat_pid
n1 wg set wg0 peer "$more_specific_key" remove
[[ $(n1 wg show wg0 endpoints) == "$pub2 [::1]:9997" ]]
# Test that we can change private keys keys and immediately handshake
n1 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk") allowed-ips 192.168.241.2/32 endpoint 127.0.0.1:2
n2 wg set wg0 private-key <(echo "$key2") listen-port 2 peer "$pub1" preshared-key <(echo "$psk") allowed-ips 192.168.241.1/32
n1 ping -W 1 -c 1 192.168.241.2
n1 wg set wg0 private-key <(echo "$key3")
n2 wg set wg0 peer "$pub3" preshared-key <(echo "$psk") allowed-ips 192.168.241.1/32 peer "$pub1" remove
n1 ping -W 1 -c 1 192.168.241.2
n2 wg set wg0 peer "$pub3" remove
# Test that we can route wg through wg
ip1 addr flush dev wg0
ip2 addr flush dev wg0
ip1 addr add fd00::5:1/112 dev wg0
ip2 addr add fd00::5:2/112 dev wg0
n1 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk") allowed-ips fd00::5:2/128 endpoint 127.0.0.1:2
n2 wg set wg0 private-key <(echo "$key2") listen-port 2 peer "$pub1" preshared-key <(echo "$psk") allowed-ips fd00::5:1/128 endpoint 127.212.121.99:9998
ip1 link add wg1 type wireguard
ip2 link add wg1 type wireguard
ip1 addr add 192.168.241.1/24 dev wg1
ip1 addr add fd00::1/112 dev wg1
ip2 addr add 192.168.241.2/24 dev wg1
ip2 addr add fd00::2/112 dev wg1
ip1 link set mtu 1340 up dev wg1
ip2 link set mtu 1340 up dev wg1
n1 wg set wg1 listen-port 5 private-key <(echo "$key3") peer "$pub4" allowed-ips 192.168.241.2/32,fd00::2/128 endpoint [fd00::5:2]:5
n2 wg set wg1 listen-port 5 private-key <(echo "$key4") peer "$pub3" allowed-ips 192.168.241.1/32,fd00::1/128 endpoint [fd00::5:1]:5
tests
# Try to set up a routing loop between the two namespaces
ip1 link set netns $jail0 dev wg1
ip0 addr add 192.168.241.1/24 dev wg1
ip0 link set up dev wg1
n0 ping -W 1 -c 1 192.168.241.2
n1 wg set wg0 peer "$pub2" endpoint 192.168.241.2:7
ip2 link del wg0
ip2 link del wg1
! n0 ping -W 1 -c 10 -f 192.168.241.2 || false # Should not crash kernel
ip0 link del wg1
ip1 link del wg0
# Test using NAT. We now change the topology to this:
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────────────┐ ┌────────────────────────────────────────┐
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
# │ │ │ │ │ │
# │ ┌─────┐ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ ┌─────┐ │
# │ │ wg0 │─────────────│vethc│───────────┼────┼────│vethrc│ │vethrs│──────────────┼─────┼──│veths│────────────│ wg0 │ │
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├──────┴─────────┐ ├──────┴────────────┐ │ │ ├─────┴──────────┐ ├─────┴──────────┐ │
# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.1/24 │ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │
# │ │fd00::1/24 │ │ ││ │ │ │ │SNAT:192.168.1.0/24│ │ │ │ │ │fd00::2/24 │ │
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └───────────────────┘ │ │ └────────────────┘ └────────────────┘ │
# └────────────────────────────────────────┘ └────────────────────────────────────────────────┘ └────────────────────────────────────────┘
ip1 link add dev wg0 type wireguard
ip2 link add dev wg0 type wireguard
configure_peers
ip0 link add vethrc type veth peer name vethc
ip0 link add vethrs type veth peer name veths
ip0 link set vethc netns $jail1
ip0 link set veths netns $jail2
ip0 link set vethrc up
ip0 link set vethrs up
ip0 addr add 192.168.1.1/24 dev vethrc
ip0 addr add 10.0.0.1/24 dev vethrs
ip1 addr add 192.168.1.100/24 dev vethc
ip1 link set vethc up
ip1 route add default via 192.168.1.1
ip2 addr add 10.0.0.100/24 dev veths
ip2 link set veths up
waitiface $jail0 vethrc
waitiface $jail0 vethrs
waitiface $jail1 vethc
waitiface $jail2 veths
n0 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout'
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream'
n0 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to 10.0.0.1
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.100:2 persistent-keepalive 1
n1 ping -W 1 -c 1 192.168.241.2
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
# Demonstrate n2 can still send packets to n1, since persistent-keepalive will prevent connection tracking entry from expiring (to see entries: `n0 conntrack -L`).
pp sleep 3
n2 ping -W 1 -c 1 192.168.241.1
n1 wg set wg0 peer "$pub2" persistent-keepalive 0
# Test that sk_bound_dev_if works
n1 ping -I wg0 -c 1 -W 1 192.168.241.2
# What about when the mark changes and the packet must be rerouted?
n1 iptables -t mangle -I OUTPUT -j MARK --set-xmark 1
n1 ping -c 1 -W 1 192.168.241.2 # First the boring case
n1 ping -I wg0 -c 1 -W 1 192.168.241.2 # Then the sk_bound_dev_if case
n1 iptables -t mangle -D OUTPUT -j MARK --set-xmark 1
# Test that onion routing works, even when it loops
n1 wg set wg0 peer "$pub3" allowed-ips 192.168.242.2/32 endpoint 192.168.241.2:5
ip1 addr add 192.168.242.1/24 dev wg0
ip2 link add wg1 type wireguard
ip2 addr add 192.168.242.2/24 dev wg1
n2 wg set wg1 private-key <(echo "$key3") listen-port 5 peer "$pub1" allowed-ips 192.168.242.1/32
ip2 link set wg1 up
n1 ping -W 1 -c 1 192.168.242.2
ip2 link del wg1
n1 wg set wg0 peer "$pub3" endpoint 192.168.242.2:5
! n1 ping -W 1 -c 1 192.168.242.2 || false # Should not crash kernel
n1 wg set wg0 peer "$pub3" remove
ip1 addr del 192.168.242.1/24 dev wg0
# Do a wg-quick(8)-style policy routing for the default route, making sure vethc has a v6 address to tease out bugs.
ip1 -6 addr add fc00::9/96 dev vethc
ip1 -6 route add default via fc00::1
ip2 -4 addr add 192.168.99.7/32 dev wg0
ip2 -6 addr add abab::1111/128 dev wg0
n1 wg set wg0 fwmark 51820 peer "$pub2" allowed-ips 192.168.99.7,abab::1111
ip1 -6 route add default dev wg0 table 51820
ip1 -6 rule add not fwmark 51820 table 51820
ip1 -6 rule add table main suppress_prefixlength 0
ip1 -4 route add default dev wg0 table 51820
ip1 -4 rule add not fwmark 51820 table 51820
ip1 -4 rule add table main suppress_prefixlength 0
# Flood the pings instead of sending just one, to trigger routing table reference counting bugs.
n1 ping -W 1 -c 100 -f 192.168.99.7
n1 ping -W 1 -c 100 -f abab::1111
# Have ns2 NAT into wg0 packets from ns0, but return an icmp error along the right route.
n2 iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.241.0/24 -j SNAT --to 192.168.241.2
n0 iptables -t filter -A INPUT \! -s 10.0.0.0/24 -i vethrs -j DROP # Manual rpfilter just to be explicit.
n2 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
ip0 -4 route add 192.168.241.1 via 10.0.0.100
n2 wg set wg0 peer "$pub1" remove
[[ $(! n0 ping -W 1 -c 1 192.168.241.1 || false) == *"From 10.0.0.100 icmp_seq=1 Destination Host Unreachable"* ]]
n0 iptables -t nat -F
n0 iptables -t filter -F
n2 iptables -t nat -F
ip0 link del vethrc
ip0 link del vethrs
ip1 link del wg0
ip2 link del wg0
# Test that saddr routing is sticky but not too sticky, changing to this topology:
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
# │ $ns1 namespace │ │ $ns2 namespace │
# │ │ │ │
# │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
# │ │ wg0 │─────────────│veth1│───────────┼────┼──│veth2│────────────│ wg0 │ │
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├─────┴──────────┐ ├─────┴──────────┐ │
# │ │192.168.241.1/24│ │10.0.0.1/24 ││ │ │10.0.0.2/24 │ │192.168.241.2/24│ │
# │ │fd00::1/24 │ │fd00:aa::1/96 ││ │ │fd00:aa::2/96 │ │fd00::2/24 │ │
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └────────────────┘ │
# └────────────────────────────────────────┘ └────────────────────────────────────────┘
ip1 link add dev wg0 type wireguard
ip2 link add dev wg0 type wireguard
configure_peers
ip1 link add veth1 type veth peer name veth2
ip1 link set veth2 netns $jail2
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/all/accept_dad'
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/all/accept_dad'
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth1/accept_dad'
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth2/accept_dad'
n1 bash -c 'printf 1 > /proc/sys/net/ipv4/conf/veth1/promote_secondaries'
# First we check that we aren't overly sticky and can fall over to new IPs when old ones are removed
ip1 addr add 10.0.0.1/24 dev veth1
ip1 addr add fd00:aa::1/96 dev veth1
ip2 addr add 10.0.0.2/24 dev veth2
ip2 addr add fd00:aa::2/96 dev veth2
ip1 link set veth1 up
ip2 link set veth2 up
waitiface $jail1 veth1
waitiface $jail2 veth2
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.2:2
n1 ping -W 1 -c 1 192.168.241.2
ip1 addr add 10.0.0.10/24 dev veth1
ip1 addr del 10.0.0.1/24 dev veth1
n1 ping -W 1 -c 1 192.168.241.2
n1 wg set wg0 peer "$pub2" endpoint [fd00:aa::2]:2
n1 ping -W 1 -c 1 192.168.241.2
ip1 addr add fd00:aa::10/96 dev veth1
ip1 addr del fd00:aa::1/96 dev veth1
n1 ping -W 1 -c 1 192.168.241.2
# Now we show that we can successfully do reply to sender routing
ip1 link set veth1 down
ip2 link set veth2 down
ip1 addr flush dev veth1
ip2 addr flush dev veth2
ip1 addr add 10.0.0.1/24 dev veth1
ip1 addr add 10.0.0.2/24 dev veth1
ip1 addr add fd00:aa::1/96 dev veth1
ip1 addr add fd00:aa::2/96 dev veth1
ip2 addr add 10.0.0.3/24 dev veth2
ip2 addr add fd00:aa::3/96 dev veth2
ip1 link set veth1 up
ip2 link set veth2 up
waitiface $jail1 veth1
waitiface $jail2 veth2
n2 wg set wg0 peer "$pub1" endpoint 10.0.0.1:1
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
n2 wg set wg0 peer "$pub1" endpoint [fd00:aa::1]:1
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 [fd00:aa::1]:1" ]]
n2 wg set wg0 peer "$pub1" endpoint 10.0.0.2:1
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.2:1" ]]
n2 wg set wg0 peer "$pub1" endpoint [fd00:aa::2]:1
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 [fd00:aa::2]:1" ]]
# What happens if the inbound destination address belongs to a different interface as the default route?
ip1 link add dummy0 type dummy
ip1 addr add 10.50.0.1/24 dev dummy0
ip1 link set dummy0 up
ip2 route add 10.50.0.0/24 dev veth2
n2 wg set wg0 peer "$pub1" endpoint 10.50.0.1:1
n2 ping -W 1 -c 1 192.168.241.1
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.50.0.1:1" ]]
ip1 link del dummy0
ip1 addr flush dev veth1
ip2 addr flush dev veth2
ip1 route flush dev veth1
ip2 route flush dev veth2
# Now we see what happens if another interface route takes precedence over an ongoing one
ip1 link add veth3 type veth peer name veth4
ip1 link set veth4 netns $jail2
ip1 addr add 10.0.0.1/24 dev veth1
ip2 addr add 10.0.0.2/24 dev veth2
ip1 addr add 10.0.0.3/24 dev veth3
ip1 link set veth1 up
ip2 link set veth2 up
ip1 link set veth3 up
ip2 link set veth4 up
waitiface $jail1 veth1
waitiface $jail2 veth2
waitiface $jail1 veth3
waitiface $jail2 veth4
ip1 route flush dev veth1
ip1 route flush dev veth3
ip1 route add 10.0.0.0/24 dev veth1 src 10.0.0.1 metric 2
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.2:2
n1 ping -W 1 -c 1 192.168.241.2
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
ip1 route add 10.0.0.0/24 dev veth3 src 10.0.0.3 metric 1
n1 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter'
n2 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/veth4/rp_filter'
n1 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
n2 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
n1 ping -W 1 -c 1 192.168.241.2
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.3:1" ]]
ip1 link del veth1
ip1 link del veth3
ip1 link del wg0
ip2 link del wg0
# We test that Netlink/IPC is working properly by doing things that usually cause split responses
ip0 link add dev wg0 type wireguard
config=( "[Interface]" "PrivateKey=$(wg genkey)" "[Peer]" "PublicKey=$(wg genkey)" )
for a in {1..255}; do
for b in {0..255}; do
config+=( "AllowedIPs=$a.$b.0.0/16,$a::$b/128" )
done
done
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
i=0
for ip in $(n0 wg show wg0 allowed-ips); do
((++i))
done
((i == 255*256*2+1))
ip0 link del wg0
ip0 link add dev wg0 type wireguard
config=( "[Interface]" "PrivateKey=$(wg genkey)" )
for a in {1..40}; do
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
for b in {1..52}; do
config+=( "AllowedIPs=$a.$b.0.0/16" )
done
done
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
i=0
while read -r line; do
j=0
for ip in $line; do
((++j))
done
((j == 53))
((++i))
done < <(n0 wg show wg0 allowed-ips)
((i == 40))
ip0 link del wg0
ip0 link add wg0 type wireguard
config=( )
for i in {1..29}; do
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
done
config+=( "[Peer]" "PublicKey=$(wg genkey)" "AllowedIPs=255.2.3.4/32,abcd::255/128" )
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
n0 wg showconf wg0 > /dev/null
ip0 link del wg0
allowedips=( )
for i in {1..197}; do
allowedips+=( abcd::$i )
done
saved_ifs="$IFS"
IFS=,
allowedips="${allowedips[*]}"
IFS="$saved_ifs"
ip0 link add wg0 type wireguard
n0 wg set wg0 peer "$pub1"
n0 wg set wg0 peer "$pub2" allowed-ips "$allowedips"
{
read -r pub allowedips
[[ $pub == "$pub1" && $allowedips == "(none)" ]]
read -r pub allowedips
[[ $pub == "$pub2" ]]
i=0
for _ in $allowedips; do
((++i))
done
((i == 197))
} < <(n0 wg show wg0 allowed-ips)
ip0 link del wg0
! n0 wg show doesnotexist || false
ip0 link add wg0 type wireguard
n0 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk")
[[ $(n0 wg show wg0 private-key) == "$key1" ]]
[[ $(n0 wg show wg0 preshared-keys) == "$pub2 $psk" ]]
n0 wg set wg0 private-key /dev/null peer "$pub2" preshared-key /dev/null
[[ $(n0 wg show wg0 private-key) == "(none)" ]]
[[ $(n0 wg show wg0 preshared-keys) == "$pub2 (none)" ]]
n0 wg set wg0 peer "$pub2"
n0 wg set wg0 private-key <(echo "$key2")
[[ $(n0 wg show wg0 public-key) == "$pub2" ]]
[[ -z $(n0 wg show wg0 peers) ]]
n0 wg set wg0 peer "$pub2"
[[ -z $(n0 wg show wg0 peers) ]]
n0 wg set wg0 private-key <(echo "$key1")
n0 wg set wg0 peer "$pub2"
[[ $(n0 wg show wg0 peers) == "$pub2" ]]
n0 wg set wg0 private-key <(echo "/${key1:1}")
[[ $(n0 wg show wg0 private-key) == "+${key1:1}" ]]
n0 wg set wg0 peer "$pub2" allowed-ips 0.0.0.0/0,10.0.0.0/8,100.0.0.0/10,172.16.0.0/12,192.168.0.0/16
n0 wg set wg0 peer "$pub2" allowed-ips 0.0.0.0/0
n0 wg set wg0 peer "$pub2" allowed-ips ::/0,1700::/111,5000::/4,e000::/37,9000::/75
n0 wg set wg0 peer "$pub2" allowed-ips ::/0
n0 wg set wg0 peer "$pub2" remove
for low_order_point in AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 4Ot6fDtBuK4WVuP68Z/EatoJjeucMrH9hmIFFl9JuAA= X5yVvKNQjCSx0LFVnIPvWwREXMRYHI6G2CJO3dCfEVc= 7P///////////////////////////////////////38= 7f///////////////////////////////////////38= 7v///////////////////////////////////////38=; do
n0 wg set wg0 peer "$low_order_point" persistent-keepalive 1 endpoint 127.0.0.1:1111
done
[[ -n $(n0 wg show wg0 peers) ]]
exec 4< <(n0 ncat -l -u -p 1111)
ncat_pid=$!
waitncatudp $jail0 $ncat_pid
ip0 link set wg0 up
! read -r -n 1 -t 2 <&4 || false
kill $ncat_pid
ip0 link del wg0
# Ensure there aren't circular reference loops
ip1 link add wg1 type wireguard
ip2 link add wg2 type wireguard
ip1 link set wg1 netns $jail2
ip2 link set wg2 netns $jail1
pp ip netns delete $jail1
pp ip netns delete $jail2
pp ip netns add $jail1
pp ip netns add $jail2
sleep 2 # Wait for cleanup and grace periods
declare -A objects
while read -t 0.1 -r line 2>/dev/null || [[ $? -ne 142 ]]; do
[[ $line =~ .*(wg[0-9]+:\ [A-Z][a-z]+\ ?[0-9]*)\ .*(created|destroyed).* ]] || continue
objects["${BASH_REMATCH[1]}"]+="${BASH_REMATCH[2]}"
done < /dev/kmsg
alldeleted=1
for object in "${!objects[@]}"; do
if [[ ${objects["$object"]} != *createddestroyed ]]; then
echo "Error: $object: merely ${objects["$object"]}" >&3
alldeleted=0
fi
done
[[ $alldeleted -eq 1 ]]
pretty "" "Objects that were created were also destroyed."