Index: conf/GENERIC =================================================================== RCS file: /cvs/src/sys/conf/GENERIC,v retrieving revision 1.257 diff -u -p -r1.257 GENERIC --- conf/GENERIC 20 Dec 2018 23:00:55 -0000 1.257 +++ conf/GENERIC 10 Feb 2019 03:49:56 -0000 @@ -102,6 +102,7 @@ pseudo-device pppx # PPP multiplexer pseudo-device sppp 1 # Sync PPP/HDLC pseudo-device trunk # Trunking support pseudo-device tun # network tunneling over tty (tun & tap) +pseudo-device wg # wireguard pseudo-device vether # Virtual ethernet pseudo-device vxlan # Virtual extensible LAN pseudo-device vlan # IEEE 802.1Q VLAN Index: conf/files =================================================================== RCS file: /cvs/src/sys/conf/files,v retrieving revision 1.666 diff -u -p -r1.666 files --- conf/files 20 Dec 2018 23:00:55 -0000 1.666 +++ conf/files 10 Feb 2019 03:49:56 -0000 @@ -549,6 +549,7 @@ pseudo-device loop: ifnet pseudo-device pair: ifnet, ether pseudo-device ppp: ifnet pseudo-device tun: ifnet +pseudo-device wg: ifnet pseudo-device bpfilter: ifnet pseudo-device enc: ifnet pseudo-device etherip: ifnet, ether, ifmedia @@ -794,6 +795,7 @@ file net/ppp_tty.c ppp file net/bsd-comp.c ppp_bsdcomp file net/ppp-deflate.c ppp_deflate file net/if_tun.c tun needs-count +file net/if_wg.c wg needs-count file net/if_bridge.c bridge needs-count file net/bridgectl.c bridge file net/bridgestp.c bridge @@ -892,6 +894,7 @@ file crypto/hmac.c wlan | (softraid & file crypto/gmac.c ipsec | crypto file crypto/key_wrap.c wlan file crypto/idgen.c inet6 | nfsclient | nfsserver +file crypto/chacha.c file crypto/chachapoly.c ipsec | crypto file crypto/poly1305.c ipsec | crypto file crypto/siphash.c Index: crypto/chacha.c =================================================================== RCS file: crypto/chacha.c diff -N crypto/chacha.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ crypto/chacha.c 10 Feb 2019 03:49:56 -0000 @@ -0,0 +1,301 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2018 David Gwynne + * + * 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. + */ + +#include +#include + +#include + +static void +memxor(void *dst, const void *src, const void *xor, size_t len) +{ + uint8_t *d = dst; + const uint8_t *s = src; + const uint8_t *x = xor; + size_t i; + + for (i = 0; i < len; i++) + d[i] = s[i] ^ x[i]; +} + +#if (_BYTE_ORDER == _LITTLE_ENDIAN) +#define memcpyle32(_dst, _src, _len) \ + memcpy((_dst), (_src), (_len)) +#define memxorle32(_dst, _src, _xor, _len) \ + memxor((_dst), (_src), (_xor), (_len)) +#else +static void +memcpyle32(void *dst, const void *src, size_t len) +{ + uint8_t *d = dst; + const uint8_t *s = src; + + while (len > 0) { + size_t i = len; + + switch (i) { + default: + i = 4; + case 4: + d[3] = s[0]; + case 3: + d[2] = s[1]; + case 2: + d[1] = s[2]; + case 1: + d[0] = s[3]; + } + + d += 4; + s += 4; + + len -= i; + } +} + +static void +memxorle32(void *dst, const void *src, const void *xor, size_t len) +{ + uint8_t *d = dst; + const uint8_t *s = src; + const uint8_t *x = xor; + + while (len > 0) { + size_t i = len; + + switch (i) { + default: + i = 4; + case 4: + d[3] = s[3] ^ x[0]; + case 3: + d[2] = s[2] ^ x[1]; + case 2: + d[1] = s[1] ^ x[2]; + case 1: + d[0] = s[0] ^ x[3]; + } + + d += 4; + s += 4; + x += 4; + + len -= i; + } +} +#endif + +static inline uint32_t +rotl32(uint32_t v, unsigned int n) +{ + return ((v << n) | (v >> (32 - n))); +} + +#define ROTATE(v,c) rotl32((v), (c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) ((v) + (w)) +#define PLUSONE(v) (PLUS((v), 1)) + +#define QUARTERROUND(a,b,c,d) do { \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); \ +} while (0) + +static const char sigma[16] = "expand 32-byte k"; +#ifdef notyet +static const char tau[16] = "expand 16-byte k"; +#endif + +void +chacha_keysetup(struct chacha_ctx *ctx, const struct chacha_key *k) +{ + memcpyle32(&ctx->input[0], sigma, sizeof(sigma)); + memcpyle32(&ctx->input[4], k, sizeof(*k)); +} + +void +chacha_ivsetup(CHACHA_CTX *ctx, const struct chacha_nonce *n, uint64_t c) +{ + ctx->input[12] = c; + ctx->input[13] = c >> 32; + memcpyle32(&ctx->input[14], n, sizeof(*n)); +} + +void +chacha_xform_ivsetup(CHACHA_CTX *ctx, const struct chacha_nonce *n, + const struct chacha_salt *s, uint32_t c) +{ + ctx->input[12] = c; + memcpyle32(&ctx->input[13], s, sizeof(*s)); + memcpyle32(&ctx->input[14], n, sizeof(*n)); +} + +void +chacha_ietf_ivsetup(CHACHA_CTX *ctx, const struct chacha_ietf_nonce *n, + uint32_t c) +{ + ctx->input[12] = c; + memcpyle32(&ctx->input[13], n, sizeof(*n)); +} + +static void +chacha_permute(struct chacha_ctx *X, CHACHA_CTX *ctx) +{ + uint32_t *x = X->input; + unsigned int i; + + *X = *ctx; + + for (i = 0; i < 20; i += 2) { + QUARTERROUND(x[0], x[4], x[8],x[12]); + QUARTERROUND(x[1], x[5], x[9],x[13]); + QUARTERROUND(x[2], x[6],x[10],x[14]); + QUARTERROUND(x[3], x[7],x[11],x[15]); + QUARTERROUND(x[0], x[5],x[10],x[15]); + QUARTERROUND(x[1], x[6],x[11],x[12]); + QUARTERROUND(x[2], x[7], x[8],x[13]); + QUARTERROUND(x[3], x[4], x[9],x[14]); + } + + for (i = 0; i < nitems(ctx->input); i++) + x[i] += ctx->input[i]; + + if (0 == ++ctx->input[12]) + ++ctx->input[13]; +} + +void +chacha_bytes(CHACHA_CTX *ctx, void *d, size_t len) +{ + uint8_t *dst = d; + CHACHA_CTX x; + + if (len == 0) + return; + + for (;;) { + chacha_permute(&x, ctx); + + memcpyle32(dst, x.input, MIN(len, CHACHA_BLOCKSIZE)); + + if (len <= CHACHA_BLOCKSIZE) + break; + + len -= CHACHA_BLOCKSIZE; + dst += CHACHA_BLOCKSIZE; + } +} + +void +chacha_xor(CHACHA_CTX *ctx, void *d, const void *s, size_t len) +{ + const uint8_t *src = s; + uint8_t *dst = d; + CHACHA_CTX x; + + if (len == 0) + return; + + for (;;) { + chacha_permute(&x, ctx); + + memxorle32(dst, src, x.input, MIN(len, CHACHA_BLOCKSIZE)); + + if (len <= CHACHA_BLOCKSIZE) + break; + + len -= CHACHA_BLOCKSIZE; + dst += CHACHA_BLOCKSIZE; + src += CHACHA_BLOCKSIZE; + } +} + +void +chacha_stream_keysetup(struct chacha_stream *str, const struct chacha_key *k) +{ + chacha_keysetup(&str->ctx, k); +} + +void +chacha_stream_ivsetup(struct chacha_stream *str, + const struct chacha_nonce *n, uint64_t c) +{ + chacha_ivsetup(&str->ctx, n, c); + bzero(&str->buf, sizeof(str->buf)); + str->used = 0; +} + +void +chacha_stream_ietf_setup(struct chacha_stream *str, + const struct chacha_ietf_nonce *n, uint32_t c) +{ + chacha_ietf_ivsetup(&str->ctx, n, c); + bzero(&str->buf, sizeof(str->buf)); + str->used = 0; +} + +void +chacha_stream_update(struct chacha_stream *str, + void *d, const void *s, size_t len) +{ + uint8_t *dst = d; + const uint8_t *src = s; + CHACHA_CTX *ctx = &str->ctx; + CHACHA_CTX x; + + if (len == 0) + return; + + if (str->used > 0) { + size_t left = CHACHA_BLOCKSIZE - str->used; + size_t copy = min(left, len); + const uint8_t *xor = (uint8_t *)&str->buf; + + memxor(dst, src, xor + str->used, copy); + + len -= copy; + src += copy; + dst += copy; + } + + while (len >= CHACHA_BLOCKSIZE) { + chacha_permute(&x, &str->ctx); + + memxorle32(dst, src, x.input, CHACHA_BLOCKSIZE); + + len -= CHACHA_BLOCKSIZE; + src += CHACHA_BLOCKSIZE; + dst += CHACHA_BLOCKSIZE; + } + + if (len) { + unsigned int i; + + chacha_permute(&x, ctx); + + /* we know this is aligned */ + for (i = 0; i < nitems(x.input); i++) + htolem32(&str->buf.input[i], x.input[i]); + + memxor(dst, src, &str->buf, len); + } + + str->used = len; +} Index: crypto/chacha.h =================================================================== RCS file: crypto/chacha.h diff -N crypto/chacha.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ crypto/chacha.h 10 Feb 2019 03:49:56 -0000 @@ -0,0 +1,71 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2018 David Gwynne ain> + * + * 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. + */ + +#ifndef _CHACHA_H_ +#define _CHACHA_H_ + +typedef struct chacha_ctx { + uint32_t input[16]; +} CHACHA_CTX; + +struct chacha_stream { + CHACHA_CTX ctx; + CHACHA_CTX buf; + unsigned int used; +}; + +struct chacha_key { + uint8_t bytes[32]; +}; + +struct chacha_nonce { + uint8_t bytes[8]; +}; + +struct chacha_salt { + uint8_t bytes[4]; +}; + +struct chacha_ietf_nonce { + uint8_t bytes[12]; +}; + +#define CHACHA_KEYSIZE sizeof(struct chacha_key) +#define CHACHA_BLOCKSIZE sizeof(struct chacha_ctx) + +void chacha_keysetup(CHACHA_CTX *, const struct chacha_key *); +void chacha_ivsetup(CHACHA_CTX *, + const struct chacha_nonce *, uint64_t); +void chacha_xform_ivsetup(CHACHA_CTX *, + const struct chacha_nonce *, const struct chacha_salt *, uint32_t); +void chacha_ietf_ivsetup(CHACHA_CTX *, + const struct chacha_ietf_nonce *, uint32_t); +void chacha_bytes(CHACHA_CTX *, void *, size_t); +void chacha_xor(CHACHA_CTX *, void *, const void *, size_t); + +void chacha_stream_keysetup(struct chacha_stream *, + const struct chacha_key *); +void chacha_stream_ivsetup(struct chacha_stream *, + const struct chacha_nonce *, uint64_t); +void chacha_stream_ietf_ivsetup(struct chacha_stream *, + const struct chacha_ietf_nonce *, uint32_t); +void chacha_stream_bytes(struct chacha_stream *, void *, size_t); +void chacha_stream_update(struct chacha_stream *, + void *, const void *, size_t); + +#endif /* _CHACHA_H_ */ Index: crypto/chachapoly.c =================================================================== RCS file: /cvs/src/sys/crypto/chachapoly.c,v retrieving revision 1.3 diff -u -p -r1.3 chachapoly.c --- crypto/chachapoly.c 12 Nov 2015 16:47:00 -0000 1.3 +++ crypto/chachapoly.c 10 Feb 2019 03:49:56 -0000 @@ -17,23 +17,21 @@ #include #include -#include -#include #include int chacha20_setkey(void *sched, u_int8_t *key, int len) { struct chacha20_ctx *ctx = (struct chacha20_ctx *)sched; + struct chacha_key *k = (struct chacha_key *)key; if (len != CHACHA20_KEYSIZE + CHACHA20_SALT) return (-1); - /* initial counter is 1 */ - ctx->nonce[0] = 1; - memcpy(ctx->nonce + CHACHA20_CTR, key + CHACHA20_KEYSIZE, - CHACHA20_SALT); - chacha_keysetup((chacha_ctx *)&ctx->block, key, CHACHA20_KEYSIZE * 8); + /* salt is provided with the key material */ + memcpy(&ctx->salt, k + 1, sizeof(ctx->salt)); + + chacha_keysetup(&ctx->chacha, k); return (0); } @@ -41,8 +39,10 @@ void chacha20_reinit(caddr_t key, u_int8_t *iv) { struct chacha20_ctx *ctx = (struct chacha20_ctx *)key; + const struct chacha_nonce *n = (const struct chacha_nonce *)iv; - chacha_ivsetup((chacha_ctx *)ctx->block, iv, ctx->nonce); + /* initial counter is 1 */ + chacha_xform_ivsetup(&ctx->chacha, n, &ctx->salt, 1); } void @@ -50,8 +50,7 @@ chacha20_crypt(caddr_t key, u_int8_t *da { struct chacha20_ctx *ctx = (struct chacha20_ctx *)key; - chacha_encrypt_bytes((chacha_ctx *)ctx->block, data, data, - CHACHA20_BLOCK_LEN); + chacha_xor(&ctx->chacha, data, data, CHACHA20_BLOCK_LEN); } void @@ -66,39 +65,38 @@ void Chacha20_Poly1305_Setkey(void *xctx, const uint8_t *key, uint16_t klen) { CHACHA20_POLY1305_CTX *ctx = xctx; + const struct chacha_key *k = (const struct chacha_key *)key; /* salt is provided with the key material */ - memcpy(ctx->nonce + CHACHA20_CTR, key + CHACHA20_KEYSIZE, - CHACHA20_SALT); - chacha_keysetup((chacha_ctx *)&ctx->chacha, key, CHACHA20_KEYSIZE * 8); + memcpy(&ctx->salt, k + 1, sizeof(ctx->salt)); + + chacha_keysetup(&ctx->chacha, k); } void Chacha20_Poly1305_Reinit(void *xctx, const uint8_t *iv, uint16_t ivlen) { CHACHA20_POLY1305_CTX *ctx = xctx; + const struct chacha_nonce *n = (const struct chacha_nonce *)iv; + uint8_t key[POLY1305_KEYLEN]; /* initial counter is 0 */ - chacha_ivsetup((chacha_ctx *)&ctx->chacha, iv, ctx->nonce); - chacha_encrypt_bytes((chacha_ctx *)&ctx->chacha, ctx->key, ctx->key, - POLY1305_KEYLEN); - poly1305_init((poly1305_state *)&ctx->poly, ctx->key); + chacha_xform_ivsetup(&ctx->chacha, n, &ctx->salt, 0); + + /* the 0th block is used to key poly1305 */ + poly1305_init(&ctx->poly, key); + explicit_bzero(key, sizeof(key)); } int Chacha20_Poly1305_Update(void *xctx, const uint8_t *data, uint16_t len) { - static const char zeroes[POLY1305_BLOCK_LEN]; CHACHA20_POLY1305_CTX *ctx = xctx; - size_t rem; poly1305_update((poly1305_state *)&ctx->poly, data, len); - /* number of bytes in the last 16 byte block */ - rem = (len + POLY1305_BLOCK_LEN) & (POLY1305_BLOCK_LEN - 1); - if (rem > 0) - poly1305_update((poly1305_state *)&ctx->poly, zeroes, - POLY1305_BLOCK_LEN - rem); + ctx->datalen += len; + return (0); } @@ -106,7 +104,15 @@ void Chacha20_Poly1305_Final(uint8_t tag[POLY1305_TAGLEN], void *xctx) { CHACHA20_POLY1305_CTX *ctx = xctx; + uint64_t rem = ctx->datalen % 16; + + if (rem) { + uint8_t zeroes[16]; + + bzero(zeroes, sizeof(zeroes)); + poly1305_update(&ctx->poly, zeroes, 16 - rem); + } - poly1305_finish((poly1305_state *)&ctx->poly, tag); + poly1305_finish(&ctx->poly, tag); explicit_bzero(ctx, sizeof(*ctx)); } Index: crypto/chachapoly.h =================================================================== RCS file: /cvs/src/sys/crypto/chachapoly.h,v retrieving revision 1.2 diff -u -p -r1.2 chachapoly.h --- crypto/chachapoly.h 4 Nov 2015 12:32:37 -0000 1.2 +++ crypto/chachapoly.h 10 Feb 2019 03:49:56 -0000 @@ -17,6 +17,9 @@ #ifndef _CHACHAPOLY_H_ #define _CHACHAPOLY_H_ +#include +#include + #define CHACHA20_KEYSIZE 32 #define CHACHA20_CTR 4 #define CHACHA20_SALT 4 @@ -24,8 +27,8 @@ #define CHACHA20_BLOCK_LEN 64 struct chacha20_ctx { - uint8_t block[CHACHA20_BLOCK_LEN]; - uint8_t nonce[CHACHA20_NONCE]; + struct chacha_ctx chacha; + struct chacha_salt salt; }; int chacha20_setkey(void *, u_int8_t *, int); @@ -46,11 +49,10 @@ struct poly1305_ctx { }; typedef struct { - uint8_t key[POLY1305_KEYLEN]; - /* counter, salt */ - uint8_t nonce[CHACHA20_NONCE]; - struct chacha20_ctx chacha; - struct poly1305_ctx poly; + struct poly1305_state poly; + struct chacha_ctx chacha; + struct chacha_salt salt; + uint64_t datalen; } CHACHA20_POLY1305_CTX; void Chacha20_Poly1305_Init(void *); Index: dev/rnd.c =================================================================== RCS file: /cvs/src/sys/dev/rnd.c,v retrieving revision 1.199 diff -u -p -r1.199 rnd.c --- dev/rnd.c 28 Apr 2018 15:44:59 -0000 1.199 +++ dev/rnd.c 10 Feb 2019 03:49:56 -0000 @@ -127,8 +127,7 @@ #include -#define KEYSTREAM_ONLY -#include +#include #include @@ -427,7 +426,7 @@ struct mutex rndlock = MUTEX_INITIALIZER struct timeout arc4_timeout; struct task arc4_task = TASK_INITIALIZER(arc4_init, NULL); -static chacha_ctx rs; /* chacha context for random keystream */ +static struct chacha_ctx rs; /* chacha context for random keystream */ /* keystream blocks (also chacha seed from boot) */ static u_char rs_buf[RSBUFSZ]; u_char rs_buf0[RSBUFSZ] __attribute__((section(".openbsd.randomdata"))); @@ -469,8 +468,8 @@ static inline void _rs_init(u_char *buf, size_t n) { KASSERT(n >= KEYSZ + IVSZ); - chacha_keysetup(&rs, buf, KEYSZ * 8); - chacha_ivsetup(&rs, buf + KEYSZ, NULL); + chacha_keysetup(&rs, (struct chacha_key *)buf); + chacha_ivsetup(&rs, (struct chacha_nonce *)(buf + KEYSZ), 0); } static void @@ -570,11 +569,8 @@ _rs_clearseed(const void *p, size_t s) static inline void _rs_rekey(u_char *dat, size_t datlen) { -#ifndef KEYSTREAM_ONLY - memset(rs_buf, 0, RSBUFSZ); -#endif /* fill rs_buf with the keystream */ - chacha_encrypt_bytes(&rs, rs_buf, rs_buf, RSBUFSZ); + chacha_bytes(&rs, rs_buf, RSBUFSZ); /* mix in optional user provided data */ if (dat) { size_t i, m; @@ -650,13 +646,16 @@ arc4random_buf(void *buf, size_t n) struct arc4random_ctx * arc4random_ctx_new() { - char keybuf[KEYSZ + IVSZ]; - - chacha_ctx *ctx = malloc(sizeof(chacha_ctx), M_TEMP, M_WAITOK); - arc4random_buf(keybuf, KEYSZ + IVSZ); - chacha_keysetup(ctx, keybuf, KEYSZ * 8); - chacha_ivsetup(ctx, keybuf + KEYSZ, NULL); - explicit_bzero(keybuf, sizeof(keybuf)); + struct { + struct chacha_key k; + struct chacha_nonce n; + } keybuf; + + struct chacha_ctx *ctx = malloc(sizeof(*ctx), M_TEMP, M_WAITOK); + arc4random_buf(&keybuf, sizeof(keybuf)); + chacha_keysetup(ctx, &keybuf.k); + chacha_ivsetup(ctx, &keybuf.n, 0); + explicit_bzero(&keybuf, sizeof(keybuf)); return (struct arc4random_ctx *)ctx; } @@ -666,8 +665,8 @@ arc4random_ctx_new() void arc4random_ctx_free(struct arc4random_ctx *ctx) { - explicit_bzero(ctx, sizeof(chacha_ctx)); - free(ctx, M_TEMP, sizeof(chacha_ctx)); + explicit_bzero(ctx, sizeof(struct chacha_ctx)); + free(ctx, M_TEMP, sizeof(struct chacha_ctx)); } /* @@ -676,7 +675,7 @@ arc4random_ctx_free(struct arc4random_ct void arc4random_ctx_buf(struct arc4random_ctx *ctx, void *buf, size_t n) { - chacha_encrypt_bytes((chacha_ctx *)ctx, buf, buf, n); + chacha_bytes((struct chacha_ctx *)ctx, buf, n); } /* @@ -788,8 +787,11 @@ randomclose(dev_t dev, int flag, int mod int randomread(dev_t dev, struct uio *uio, int ioflag) { - u_char lbuf[KEYSZ+IVSZ]; - chacha_ctx lctx; + struct { + struct chacha_key k; + struct chacha_nonce n; + } lbuf; + struct chacha_ctx lctx; size_t total = uio->uio_resid; u_char *buf; int myctx = 0, ret = 0; @@ -799,10 +801,10 @@ randomread(dev_t dev, struct uio *uio, i buf = malloc(POOLBYTES, M_TEMP, M_WAITOK); if (total > ARC4_MAIN_MAX_BYTES) { - arc4random_buf(lbuf, sizeof(lbuf)); - chacha_keysetup(&lctx, lbuf, KEYSZ * 8); - chacha_ivsetup(&lctx, lbuf + KEYSZ, NULL); - explicit_bzero(lbuf, sizeof(lbuf)); + arc4random_buf(&lbuf, sizeof(lbuf)); + chacha_keysetup(&lctx, &lbuf.k); + chacha_ivsetup(&lctx, &lbuf.n, 0); + explicit_bzero(&lbuf, sizeof(lbuf)); myctx = 1; } @@ -810,10 +812,7 @@ randomread(dev_t dev, struct uio *uio, i size_t n = ulmin(POOLBYTES, uio->uio_resid); if (myctx) { -#ifndef KEYSTREAM_ONLY - memset(buf, 0, n); -#endif - chacha_encrypt_bytes(&lctx, buf, buf, n); + chacha_bytes(&lctx, buf, n); } else arc4random_buf(buf, n); ret = uiomove(buf, n, uio); Index: net/if_wg.c =================================================================== RCS file: net/if_wg.c diff -N net/if_wg.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/if_wg.c 10 Feb 2019 03:49:56 -0000 @@ -0,0 +1,1991 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2018, 2019 David Gwynne + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "bpfilter.h" +#if NBPFILTER > 0 +#include +#endif + +#include +#include + +#include + +struct wg_data_hdr { + uint32_t msg_type; + uint32_t index; + uint32_t counter_lo; + uint32_t counter_hi; +} __packed __aligned(4); + +CTASSERT(sizeof(struct chacha_key) == sizeof(struct wg_aead_key)); + +#define DPRINTF(_ifp, _a...) do { \ + if (ISSET((_ifp)->if_flags, IFF_DEBUG)) { \ + printf("%s: ", (_ifp)->if_xname); \ + printf(_a); \ + printf("\n"); \ + } \ +} while (0) + +struct wg_data_keys { + TAILQ_ENTRY(wg_data_keys) + wk_entry; + uint32_t wk_tx_idx; + uint32_t wk_rx_idx; + uint64_t wk_tx_seq; + uint64_t wk_rx_seq; + struct chacha_key wk_tx_key; + struct chacha_key wk_rx_key; + + int wk_born; + unsigned int wk_rekeyed; +}; + +TAILQ_HEAD(wg_data_keys_list, wg_data_keys); + +struct wg_device; + +struct wg_softc { + unsigned int sc_unit; /* must be first */ + struct ifnet sc_if; + + RBT_ENTRY(wg_softc) sc_entry; + TAILQ_ENTRY(wg_softc) sc_lentry; + struct wg_device *sc_device; + + struct file * volatile sc_fp; + + unsigned int sc_initiator; + + int sc_up_stamp; + + int sc_rk_stamp; + TAILQ_ENTRY(wg_softc) sc_rk_entry; + unsigned int sc_rk_onqueue; + struct timeout sc_rk_timer; + + struct mbuf_queue sc_tx_queue; + struct task sc_tx_task; + + int sc_tx_stamp; + struct timeout sc_tx_timer; + struct task sc_tx_keepalive; + + /* sc_data_keys is used by wg_start under the ifq serialiaser */ + struct wg_data_keys *sc_tx_data_keys; + /* the list is used in the rx path under the lock */ + struct rwlock sc_rx_data_keys_lk; + struct wg_data_keys_list sc_rx_data_keys; + + int sc_rx_stamp; + struct timeout sc_rx_timer; +}; + +RBT_HEAD(wg_if_tree, wg_softc); +TAILQ_HEAD(wg_if_list, wg_softc); + +struct wg_device { + dev_t wgd_dev; /* must be first */ + RBT_ENTRY(wg_device) wgd_entry; + struct wg_if_list wgd_ifaces; + + struct mutex wgd_rk_mtx; + struct wg_if_list wgd_rk_list; + unsigned int wgd_rk_reading; + + struct selinfo wgd_rsel; + struct selinfo wgd_wsel; + struct mutex wgd_sel_mtx; + int wgd_nbio; +}; + +RBT_HEAD(wg_dv_tree, wg_device); + +struct { + struct rwlock wg_dv_lock; + struct rwlock wg_if_lock; + struct wg_dv_tree wg_dv_tree; + struct wg_if_tree wg_if_tree; +} wg_state = { + RWLOCK_INITIALIZER("wgdevs"), + RWLOCK_INITIALIZER("wgifs"), + RBT_INITIALIZER(), + RBT_INITIALIZER(), +}; + +static int wg_filt_read(struct knote *, long); +static void wg_filt_read_detach(struct knote *); +static int wg_filt_write(struct knote *, long); +static void wg_filt_write_detach(struct knote *); + +static struct filterops wg_filtops_read = { + 1, NULL, wg_filt_read_detach, wg_filt_read +}; + +static struct filterops wg_filtops_write = { + 1, NULL, wg_filt_write_detach, wg_filt_write +}; + +static struct wg_device * + wg_dev_lookup_locked(dev_t); +static struct wg_device * + wg_dev_lookup(dev_t); + +static inline int + wg_if_cmp(const struct wg_softc *, const struct wg_softc *); +static inline int + wg_dv_cmp(const struct wg_device *, const struct wg_device *); + +RBT_PROTOTYPE(wg_if_tree, wg_softc, sc_entry, wg_if_cmp); +RBT_PROTOTYPE(wg_dv_tree, wg_device, wgd_entry, wg_dv_cmp); + +struct wg_aead_ctx { + struct chacha_stream + chacha20; + poly1305_state poly1305; + size_t datalen; +}; + +struct wg_aead_tag { + uint8_t tag[WG_POLY1305_TAG_LEN]; +} __packed __aligned(4); + +static void wg_aead_init(struct wg_aead_ctx *, + const struct chacha_key *, uint64_t); +static void wg_aead_encrypt(struct wg_aead_ctx *, void *, size_t len); +static void wg_aead_verify(struct wg_aead_ctx *, void *, size_t len); +static void wg_aead_decrypt(struct wg_aead_ctx *, void *, size_t len); +static void wg_aead_final(struct wg_aead_ctx *, struct wg_aead_tag *); + +static int wg_up(struct wg_softc *); +static int wg_down(struct wg_softc *); + +static struct mbuf * + wg_input(void *, struct mbuf *, int); +static int wg_output(struct ifnet *, struct mbuf *, + struct sockaddr *, struct rtentry *rt); +static void wg_start(struct ifqueue *); +static int wg_ioctl(struct ifnet *, u_long, caddr_t); +static void wg_send(void *); +static void wg_link_state(struct wg_softc *, int); +static void wg_link_up(struct wg_softc *); +static void wg_link_down(struct wg_softc *, int); + +static void wg_rekey(struct wg_softc *, int); +static void wg_send_keepalive(void *); +static void wg_rekey_timer(void *); +static void wg_tx_timer(void *); +static void wg_rx_timer(void *); + +void +wgattach(int n) +{ + +} + +int +wgopen(dev_t dev, int flag, int mode, struct proc *p) +{ + struct wg_device *wgd; + int rv; + + rv = suser(p); + if (rv != 0) + return (rv); + + rv = rw_enter(&wg_state.wg_dv_lock, RW_WRITE | RW_INTR); + if (rv != 0) + return (rv); + + wgd = wg_dev_lookup_locked(dev); + if (wgd != NULL) { + rv = EBUSY; + goto out; + } + + wgd = malloc(sizeof(*wgd), M_DEVBUF, M_WAITOK|M_CANFAIL); + if (wgd == NULL) { + rv = ENOMEM; + goto out; + } + wgd->wgd_dev = dev; + memset(&wgd->wgd_rsel, 0, sizeof(wgd->wgd_rsel)); + memset(&wgd->wgd_wsel, 0, sizeof(wgd->wgd_wsel)); + mtx_init(&wgd->wgd_sel_mtx, IPL_SOFTNET); + wgd->wgd_nbio = ISSET(flag, FNONBLOCK) ? IO_NDELAY : 0; + TAILQ_INIT(&wgd->wgd_ifaces); + + mtx_init(&wgd->wgd_rk_mtx, IPL_SOFTNET); + TAILQ_INIT(&wgd->wgd_rk_list); + wgd->wgd_rk_reading = 0; + + if (RBT_INSERT(wg_dv_tree, &wg_state.wg_dv_tree, wgd) != NULL) + panic("wg dev tree modified while lock was held"); + +out: + rw_exit(&wg_state.wg_dv_lock); + + return (rv); +} + +int +wgclose(dev_t dev, int flag, int mode, struct proc *p) +{ + struct wg_device *wgd; + struct wg_softc *sc; + struct file *fp; + + rw_enter(&wg_state.wg_dv_lock, RW_WRITE); + wgd = wg_dev_lookup_locked(dev); + KASSERTMSG(wgd != NULL, "wg device missing in close"); + + RBT_REMOVE(wg_dv_tree, &wg_state.wg_dv_tree, wgd); + rw_exit(&wg_state.wg_dv_lock); + + rw_enter(&wg_state.wg_if_lock, RW_WRITE); + TAILQ_FOREACH(sc, &wgd->wgd_ifaces, sc_lentry) { + struct ifnet *ifp = &sc->sc_if; + + if (ifp->if_index != 0) { + NET_LOCK(); + if (ISSET(ifp->if_flags, IFF_RUNNING)) + wg_down(sc); + NET_UNLOCK(); + + fp = sc->sc_fp; + if (fp != NULL) { + struct socket *so; + struct inpcb *inp; + int s; + + so = (struct socket *)fp->f_data; + s = solock(so); + inp = sotoinpcb(so); + if (inp->inp_upcall != NULL) { + inp->inp_upcall_arg = NULL; + inp->inp_upcall = NULL; + } + sounlock(so, s); + + sc->sc_fp = NULL; + FRELE(fp, p); + } + + if_detach(ifp); + } + + RBT_REMOVE(wg_if_tree, &wg_state.wg_if_tree, sc); + } + rw_exit(&wg_state.wg_if_lock); + + while ((sc = TAILQ_FIRST(&wgd->wgd_ifaces)) != NULL) { + struct wg_data_keys *wk; + + TAILQ_REMOVE(&wgd->wgd_ifaces, sc, sc_lentry); + + while ((wk = TAILQ_FIRST(&sc->sc_rx_data_keys)) != NULL) { + TAILQ_REMOVE(&sc->sc_rx_data_keys, wk, wk_entry); + free(wk, M_DEVBUF, sizeof(*wk)); + } + + task_del(systq, &sc->sc_tx_task); + mq_purge(&sc->sc_tx_queue); + + free(sc, M_DEVBUF, sizeof(*sc)); + } + + free(wgd, M_DEVBUF, sizeof(*wgd)); + + return (0); +} + +static struct wg_softc * +wg_if_lookup(struct wg_device *wgd, unsigned int unit) +{ + struct wg_softc *sc; + + sc = RBT_FIND(wg_if_tree, &wg_state.wg_if_tree, + (struct wg_softc *)&unit); + if (sc == NULL || sc->sc_device != wgd) + return (NULL); + + return (sc); +} + +static int +wg_if_create(struct wg_device *wgd, unsigned int unit) +{ + struct wg_softc *sc, *osc; + int error; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO); + if (sc == NULL) + return (ENOMEM); + + sc->sc_unit = unit; + sc->sc_device = wgd; + sc->sc_initiator = 1; + rw_init(&sc->sc_rx_data_keys_lk, "wgrxkeys"); + TAILQ_INIT(&sc->sc_rx_data_keys); + + mq_init(&sc->sc_tx_queue, 128, IPL_SOFTNET); + task_set(&sc->sc_tx_task, wg_send, sc); + task_set(&sc->sc_tx_keepalive, wg_send_keepalive, sc); + + timeout_set(&sc->sc_rk_timer, wg_rekey_timer, sc); + timeout_set(&sc->sc_tx_timer, wg_tx_timer, sc); + timeout_set(&sc->sc_rx_timer, wg_rx_timer, sc); + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + goto fail; + + osc = RBT_INSERT(wg_if_tree, &wg_state.wg_if_tree, sc); + if (osc != NULL) { + error = (osc->sc_device == wgd) ? EEXIST : EBUSY; + goto unlock; + } + + TAILQ_INSERT_TAIL(&wgd->wgd_ifaces, sc, sc_lentry); + rw_exit(&wg_state.wg_if_lock); + + return (0); +unlock: + rw_exit(&wg_state.wg_if_lock); +fail: + free(sc, M_DEVBUF, sizeof(*sc)); + return (error); +} + +static int +wg_if_destroy(struct wg_device *wgd, unsigned int unit) +{ + struct wg_softc *sc; + int error; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + goto fail; + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + if (sc->sc_if.if_index != 0) { + error = EBUSY; + goto unlock; + } + + TAILQ_REMOVE(&wgd->wgd_ifaces, sc, sc_lentry); + RBT_REMOVE(wg_if_tree, &wg_state.wg_if_tree, sc); + rw_exit(&wg_state.wg_if_lock); + + task_del(systq, &sc->sc_tx_task); + mq_purge(&sc->sc_tx_queue); + + free(sc, M_DEVBUF, sizeof(*sc)); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); +fail: + return (error); +} + +static int +wg_if_attach(struct wg_device *wgd, unsigned int unit) +{ + struct wg_softc *sc; + struct ifnet *ifp; + int error; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + if (sc->sc_if.if_index != 0) { + error = EBUSY; + goto unlock; + } + + ifp = &sc->sc_if; + snprintf(ifp->if_xname, sizeof(ifp->if_xname), "wg%u", sc->sc_unit); + ifp->if_softc = sc; + ifp->if_type = IFT_TUNNEL; + ifp->if_mtu = 1280; + ifp->if_flags = IFF_POINTOPOINT|IFF_MULTICAST; + ifp->if_xflags = IFXF_CLONED|IFXF_MPSAFE; + ifp->if_output = wg_output; + ifp->if_qstart = wg_start; + ifp->if_ioctl = wg_ioctl; + ifp->if_rtrequest = p2p_rtrequest; + ifp->if_link_state = LINK_STATE_DOWN; + + if_attach(ifp); + if_alloc_sadl(ifp); + + if_addgroup(ifp, "wg"); + +#if NBPFILTER > 0 + bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t)); +#endif + + KASSERT(ifp->if_index != 0); + rw_exit(&wg_state.wg_if_lock); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static int +wg_if_detach(struct wg_device *wgd, unsigned int unit) +{ + struct wg_softc *sc; + struct ifnet *ifp; + int error; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + if (sc->sc_if.if_index == 0) { + error = ENXIO; + goto unlock; + } + + if (sc->sc_fp != NULL) { + error = EBUSY; + goto unlock; + } + + NET_LOCK(); + if (ISSET(ifp->if_flags, IFF_RUNNING)) + wg_down(sc); + NET_UNLOCK(); + + if_detach(ifp); + + ifp->if_index = 0; + rw_exit(&wg_state.wg_if_lock); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static void +wg_frele(struct file *fp, struct proc *p) +{ + struct socket *so; + struct inpcb *inp; + int s; + + so = (struct socket *)fp->f_data; + inp = sotoinpcb(so); + + s = solock(so); + inp->inp_upcall = NULL; + inp->inp_upcall_arg = NULL; + sounlock(so, s); + + FRELE(fp, p); +} + +static int +wg_if_set_sock(struct wg_device *wgd, struct proc *p, + const struct wg_if_sock *data) +{ + unsigned int unit = data->wg_unit; + struct wg_softc *sc; + struct ifnet *ifp; + struct file *fp, *ofp; + struct socket *so; + struct inpcb *inp; + int error; + int s; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + ifp = &sc->sc_if; + + if (ifp->if_index == 0) { + error = ENXIO; + goto unlock; + } + + ofp = sc->sc_fp; + + error = getsock(p, data->wg_sock, &fp); + if (error != 0) + goto unlock; + + so = (struct socket *)fp->f_data; + if (so->so_proto->pr_protocol != IPPROTO_UDP) { + FRELE(fp, p); + error = EPROTONOSUPPORT; + goto unlock; + } + if (!ISSET(so->so_state, SS_ISCONNECTED)) { + FRELE(fp, p); + error = ENOTCONN; + goto unlock; + } + + s = solock(so); + inp = sotoinpcb(so); + if (inp->inp_upcall != NULL) { + FRELE(fp, p); + sounlock(so, s); + error = EISCONN; + goto unlock; + } + + inp->inp_upcall_arg = sc; + inp->inp_upcall = wg_input; + sounlock(so, s); + + sc->sc_fp = fp; + ifq_barrier(&ifp->if_snd); + + rw_exit(&wg_state.wg_if_lock); + + if (ofp) + wg_frele(ofp, p); + else { + KASSERT(ifp->if_link_state == LINK_STATE_DOWN); + wg_link_state(sc, LINK_STATE_KALIVE_DOWN); + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + if (sc->sc_initiator) { + /* force a rekey */ + wg_rekey(sc, 1); + } + } + } + + return (0); + + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static int +wg_if_clr_sock(struct wg_device *wgd, struct proc *p, unsigned int unit) +{ + struct wg_softc *sc; + struct file *fp; + int error; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + fp = sc->sc_fp; + if (fp == NULL) { + error = ENOTCONN; + goto unlock; + } + + sc->sc_fp = NULL; + ifq_barrier(&sc->sc_if.if_snd); + + rw_exit(&wg_state.wg_if_lock); + + wg_frele(fp, p); + wg_link_down(sc, LINK_STATE_DOWN); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static struct wg_data_keys * +wg_if_insert_data_keys(struct wg_softc *sc, struct wg_data_keys *wk) +{ + struct wg_data_keys *owk; + + TAILQ_FOREACH(owk, &sc->sc_rx_data_keys, wk_entry) { + if (owk->wk_rx_idx == wk->wk_rx_idx || + owk->wk_tx_idx == wk->wk_tx_idx) + return (owk); + } + + TAILQ_INSERT_HEAD(&sc->sc_rx_data_keys, wk, wk_entry); + + return (NULL); +} + +static int +wg_if_add_keys(struct wg_device *wgd, const struct wg_if_data_keys *keys) +{ + struct wg_softc *sc; + struct wg_data_keys *wk; + unsigned int unit = keys->wg_unit; + int error; + + wk = malloc(sizeof(*wk), M_DEVBUF, M_WAITOK|M_CANFAIL); + if (wk == NULL) + return (ENOMEM); + + wk->wk_tx_idx = keys->wg_tx_index; + wk->wk_rx_idx = keys->wg_rx_index; + wk->wk_tx_seq = 0; + wk->wk_rx_seq = 0; + memcpy(&wk->wk_tx_key, &keys->wg_tx_key, sizeof(wk->wk_tx_key)); + memcpy(&wk->wk_rx_key, &keys->wg_rx_key, sizeof(wk->wk_rx_key)); + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + goto free; + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + rw_enter_write(&sc->sc_rx_data_keys_lk); + if (wg_if_insert_data_keys(sc, wk) != NULL) + error = EEXIST; + rw_exit_write(&sc->sc_rx_data_keys_lk); + if (error != 0) + goto unlock; + + sc->sc_up_stamp = ticks; + + sc->sc_tx_data_keys = wk; + /* make sure wg_start isn't using the old head */ + ifq_barrier(&sc->sc_if.if_snd); + + rw_exit(&wg_state.wg_if_lock); + + /* XXX locking */ + if (sc->sc_if.if_link_state == LINK_STATE_KALIVE_DOWN) { + sc->sc_tx_stamp = ticks; + wg_link_up(sc); + } + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); +free: + free(wk, M_DEVBUF, sizeof(*wk)); + return (error); +} + +static int +wg_if_clr_keys(struct wg_device *wgd, const struct wg_if_data_keys *keys) +{ + struct wg_softc *sc; + struct wg_data_keys *wk; + unsigned int unit = keys->wg_unit; + int error; + + error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + rw_enter_write(&sc->sc_rx_data_keys_lk); + TAILQ_FOREACH(wk, &sc->sc_rx_data_keys, wk_entry) { + if (wk->wk_rx_idx == keys->wg_rx_index && + wk->wk_tx_idx == keys->wg_tx_index) { + TAILQ_REMOVE(&sc->sc_rx_data_keys, wk, wk_entry); + break; + } + } + rw_exit_write(&sc->sc_rx_data_keys_lk); + + if (wk == NULL) { + error = ENOENT; + goto unlock; + } + + if (sc->sc_tx_data_keys == wk) { + sc->sc_tx_data_keys = TAILQ_FIRST(&sc->sc_rx_data_keys);; + /* make sure wg_start isn't using the old head */ + ifq_barrier(&sc->sc_if.if_snd); + } + + rw_exit(&wg_state.wg_if_lock); + + free(wk, M_DEVBUF, sizeof(*wk)); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static int +wg_if_set_role(struct wg_device *wgd, const struct wg_if_role *role) +{ + struct wg_softc *sc; + int error = 0; + + error = rw_enter(&wg_state.wg_if_lock, RW_READ | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, role->wg_unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + switch (role->wg_role) { + case WG_DATA_INITIATOR: + sc->sc_initiator = 1; + break; + case WG_DATA_RESPONDER: + sc->sc_initiator = 0; + break; + default: + error = EINVAL; + break; + } + + rw_exit(&wg_state.wg_if_lock); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +static int +wg_if_get_role(struct wg_device *wgd, struct wg_if_role *role) +{ + struct wg_softc *sc; + int error = 0; + + error = rw_enter(&wg_state.wg_if_lock, RW_READ | RW_INTR); + if (error != 0) + return (error); + + sc = wg_if_lookup(wgd, role->wg_unit); + if (sc == NULL) { + error = ESRCH; + goto unlock; + } + + role->wg_role = sc->sc_initiator ? + WG_DATA_INITIATOR : WG_DATA_RESPONDER; + + rw_exit(&wg_state.wg_if_lock); + + return (0); + +unlock: + rw_exit(&wg_state.wg_if_lock); + return (error); +} + +int +wgioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) +{ + struct wg_device *wgd = wg_dev_lookup(dev); + + switch (cmd) { + case FIONBIO: + wgd->wgd_nbio = *(int *)data ? IO_NDELAY : 0; + break; + case FIONREAD: + *(int *)data = TAILQ_EMPTY(&wgd->wgd_rk_list) ? + 0 : sizeof(struct wg_msg); + break; + + case WGIFCREATE: + return (wg_if_create(wgd, *(unsigned int *)data)); + case WGIFATTACH: + return (wg_if_attach(wgd, *(unsigned int *)data)); + case WGIFDETACH: + return (wg_if_detach(wgd, *(unsigned int *)data)); + case WGIFDESTROY: + return (wg_if_destroy(wgd, *(unsigned int *)data)); + + case WGIFSSOCK: + return (wg_if_set_sock(wgd, p, + (const struct wg_if_sock *)data)); + case WGIFDSOCK: + return (wg_if_clr_sock(wgd, p, *(unsigned int *)data)); + + case WGIFADDKEYS: + return (wg_if_add_keys(wgd, + (const struct wg_if_data_keys *)data)); + case WGIFDELKEYS: + return (wg_if_clr_keys(wgd, + (const struct wg_if_data_keys *)data)); + + case WGIFSROLE: + return (wg_if_set_role(wgd, (struct wg_if_role *)data)); + case WGIFGROLE: + return (wg_if_get_role(wgd, (struct wg_if_role *)data)); + + default: + return (ENOTTY); + } + + return (0); +} + +int +wgread(dev_t dev, struct uio *uio, int ioflag) +{ + struct wg_device *wgd = wg_dev_lookup(dev); + struct wg_softc *sc; + struct wg_msg msg; + int error; + + if (uio->uio_resid < 0) + return (EINVAL); + + ioflag |= wgd->wgd_nbio; + + mtx_enter(&wgd->wgd_rk_mtx); + + sc = TAILQ_FIRST(&wgd->wgd_rk_list); + if (sc == NULL) { + if (ioflag & IO_NDELAY) { + mtx_leave(&wgd->wgd_rk_mtx); + return (EWOULDBLOCK); + } + + wgd->wgd_rk_reading++; + do { + error = msleep(&wgd->wgd_rk_list, &wgd->wgd_rk_mtx, + (PZERO + 1)|PCATCH, "wgread", 0); + if (error != 0) { + wgd->wgd_rk_reading--; + mtx_leave(&wgd->wgd_rk_mtx); + return (error); + } + + sc = TAILQ_FIRST(&wgd->wgd_rk_list); + } while (sc == NULL); + wgd->wgd_rk_reading--; + } + + sc->sc_rk_stamp = ticks; + sc->sc_rk_onqueue = 0; + TAILQ_REMOVE(&wgd->wgd_rk_list, sc, sc_rk_entry); + + msg.wg_unit = sc->sc_unit; + msg.wg_type = WG_MSG_REKEY; + DPRINTF(&sc->sc_if, "rekey"); + + mtx_leave(&wgd->wgd_rk_mtx); + + return (uiomove(&msg, ulmin(uio->uio_resid, sizeof(msg)), uio)); +} + +int +wgwrite(dev_t dev, struct uio *uio, int ioflag) +{ + return (EOPNOTSUPP); +} + +int +wgpoll(dev_t dev, int events, struct proc *p) +{ + struct wg_device *wgd = wg_dev_lookup(dev); + int mevents, revents = 0; + + mevents = ISSET(events, POLLIN | POLLRDNORM); + if (mevents) { + if (!TAILQ_EMPTY(&wgd->wgd_rk_list)) + SET(revents, events & mevents); + else + selrecord(p, &wgd->wgd_rsel); + } + + mevents = ISSET(events, POLLOUT | POLLWRNORM); + if (mevents) + SET(revents, events & mevents); + + return (revents); +} + +int +wgkqfilter(dev_t dev, struct knote *kn) +{ + struct wg_device *wgd = wg_dev_lookup(dev); + struct klist *klist; + + switch (kn->kn_filter) { + case EVFILT_READ: + klist = &wgd->wgd_rsel.si_note; + kn->kn_fop = &wg_filtops_read; + break; + case EVFILT_WRITE: + klist = &wgd->wgd_wsel.si_note; + kn->kn_fop = &wg_filtops_write; + break; + default: + return (EINVAL); + } + + kn->kn_hook = wgd; + + mtx_enter(&wgd->wgd_sel_mtx); + SLIST_INSERT_HEAD(klist, kn, kn_selnext); + mtx_leave(&wgd->wgd_sel_mtx); + + return (0); +} + +static void +wg_filt_read_detach(struct knote *kn) +{ + struct wg_device *wgd = kn->kn_hook; + struct klist *klist = &wgd->wgd_rsel.si_note; + + mtx_enter(&wgd->wgd_sel_mtx); + SLIST_REMOVE(klist, kn, knote, kn_selnext); + mtx_leave(&wgd->wgd_sel_mtx); +} + +static int +wg_filt_read(struct knote *kn, long hint) +{ + struct wg_device *wgd = kn->kn_hook; + + kn->kn_data = TAILQ_EMPTY(&wgd->wgd_rk_list) ? + 0 : sizeof(struct wg_msg); + + return (kn->kn_data != 0); +} + +static void +wg_filt_write_detach(struct knote *kn) +{ + struct wg_device *wgd = kn->kn_hook; + struct klist *klist = &wgd->wgd_wsel.si_note; + + mtx_enter(&wgd->wgd_sel_mtx); + SLIST_REMOVE(klist, kn, knote, kn_selnext); + mtx_leave(&wgd->wgd_sel_mtx); +} + +static int +wg_filt_write(struct knote *kn, long hint) +{ + kn->kn_data = 0; + + return (0); +} + +static struct wg_device * +wg_dev_lookup_locked(dev_t dev) +{ + return (RBT_FIND(wg_dv_tree, &wg_state.wg_dv_tree, + (const struct wg_device *)&dev)); +} + +static struct wg_device * +wg_dev_lookup(dev_t dev) +{ + struct wg_device *wgd; + + rw_enter_read(&wg_state.wg_dv_lock); + wgd = wg_dev_lookup_locked(dev); + rw_exit_read(&wg_state.wg_dv_lock); + + return (wgd); +} + +static inline int +wg_dv_cmp(const struct wg_device *a, const struct wg_device *b) +{ + if (a->wgd_dev > b->wgd_dev) + return (1); + if (a->wgd_dev < b->wgd_dev) + return (-1); + return (0); +} + +static inline int +wg_if_cmp(const struct wg_softc *a, const struct wg_softc *b) +{ + if (a->sc_unit > b->sc_unit) + return (1); + if (a->sc_unit < b->sc_unit) + return (-1); + return (0); +} + +RBT_GENERATE(wg_if_tree, wg_softc, sc_entry, wg_if_cmp); +RBT_GENERATE(wg_dv_tree, wg_device, wgd_entry, wg_dv_cmp); + +#define WG_MS2TICKS(_m) (((_m) * 1000) / tick) + +static inline int +wg_ratecheck(int stamp, int interval) +{ + int diff; + + diff = ticks - stamp; + return (diff >= interval); +} + +static void +wg_rekey(struct wg_softc *sc, int force) +{ + struct wg_device *wgd = sc->sc_device; + int tmo = WG_MS2TICKS(WG_REKEY_TIMEOUT); + int wake = 0; + int next = 0; + + if (sc->sc_rk_onqueue) + return; + + if (!force && !wg_ratecheck(sc->sc_rk_stamp, tmo)) + return; + + mtx_enter(&wgd->wgd_rk_mtx); + if (!sc->sc_rk_onqueue) { + if (force || wg_ratecheck(sc->sc_rk_stamp, tmo)) { + TAILQ_INSERT_TAIL(&wgd->wgd_rk_list, sc, sc_rk_entry); + sc->sc_rk_onqueue = 1; + + wake = wgd->wgd_rk_reading; + } + next = 1; + } + mtx_leave(&wgd->wgd_rk_mtx); + + if (wake) + wakeup(&wgd->wgd_rk_list); + selwakeup(&wgd->wgd_rsel); + + if (next) { + timeout_add_msec(&sc->sc_rk_timer, + WG_REKEY_TIMEOUT + arc4random_uniform(WG_REKEY_JITTER)); + } + + sc->sc_tx_stamp = ticks; +} + +static int +wg_up(struct wg_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + + SET(ifp->if_flags, IFF_RUNNING); + + if (ifp->if_link_state == LINK_STATE_KALIVE_DOWN) { + if (sc->sc_initiator) { + /* force a rekey */ + wg_rekey(sc, 1); + } + } + + return (0); +} + +static void +wg_link_state(struct wg_softc *sc, int link_state) +{ + struct ifnet *ifp = &sc->sc_if; + + ifp->if_link_state = link_state; + if_link_state_change(ifp); +} + +static void +wg_rekey_timer(void *arg) +{ + struct wg_softc *sc = arg; + struct ifnet *ifp = &sc->sc_if; + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + if (ifp->if_link_state == LINK_STATE_UP && + wg_ratecheck(sc->sc_up_stamp, WG_MS2TICKS(WG_REKEY_MAX))) + wg_link_down(sc, LINK_STATE_KALIVE_DOWN); + + wg_rekey(sc, 0); +} + +static void +wg_link_up(struct wg_softc *sc) +{ + DPRINTF(&sc->sc_if, "link up"); + timeout_add_msec(&sc->sc_tx_timer, WG_KEEPALIVE + WG_REKEY_TIMEOUT); + timeout_add_msec(&sc->sc_rx_timer, WG_KEEPALIVE); + wg_link_state(sc, LINK_STATE_UP); +} + +static void +wg_link_down(struct wg_softc *sc, int link_state) +{ + DPRINTF(&sc->sc_if, "link down"); + timeout_del(&sc->sc_rx_timer); + timeout_del(&sc->sc_tx_timer); + + if (link_state == LINK_STATE_DOWN) + timeout_del(&sc->sc_rk_timer); + + timeout_barrier(&sc->sc_rx_timer); /* implies tx and rk bar too */ + + wg_link_state(sc, link_state); +} + +static void +wg_rx_timer(void *arg) +{ + struct wg_softc *sc = arg; + struct ifnet *ifp = &sc->sc_if; + const int tmo = WG_MS2TICKS(WG_KEEPALIVE); + int diff; + + diff = ticks - sc->sc_rx_stamp; + DPRINTF(ifp, "%s, tmo %d, diff %d = ticks %d - stamp %d", __func__, + tmo, diff, ticks, sc->sc_rx_stamp); + if (diff >= tmo) { + DPRINTF(ifp, "rx timer expired, sending keepalive"); + ifq_serialize(&ifp->if_snd, &sc->sc_tx_keepalive); + diff = 0; + } + + DPRINTF(ifp, "%s, timeout_add rx timer %d", __func__, tmo - diff); + timeout_add(&sc->sc_rx_timer, tmo - diff); +} + +static void +wg_tx_timer(void *arg) +{ + struct wg_softc *sc = arg; + struct ifnet *ifp = &sc->sc_if; + const int tmo = WG_MS2TICKS(WG_KEEPALIVE + WG_REKEY_TIMEOUT); + int diff; + + diff = ticks - sc->sc_tx_stamp; + DPRINTF(ifp, "%s, tmo %d, diff %d = ticks %d - stamp %d", __func__, + tmo, diff, ticks, sc->sc_tx_stamp); + if (diff >= tmo) { + DPRINTF(ifp, "tx timer expired, sending rekey"); + wg_rekey(sc, 0); + diff = 0; + } + + DPRINTF(ifp, "%s, timeout_add tx timer %d", __func__, tmo - diff); + timeout_add(&sc->sc_tx_timer, tmo - diff); +} + +static int +wg_down(struct wg_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + + CLR(ifp->if_flags, IFF_RUNNING); + ifq_barrier(&ifp->if_snd); + + wg_link_down(sc, sc->sc_fp == NULL ? + LINK_STATE_DOWN : LINK_STATE_KALIVE_DOWN); + + return (0); +} + +static int +wg_get_tunnel(struct wg_softc *sc, struct if_laddrreq *req) +{ + struct file *fp; + struct socket *so; + struct inpcb *inp; +// int s; + + fp = sc->sc_fp; + if (fp == NULL) + return (EADDRNOTAVAIL); + + so = (struct socket *)fp->f_data; +// s = solock(so); + inp = sotoinpcb(so); + + if (inp->inp_flags & INP_IPV6) { +#ifdef INET6 + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)&req->addr; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + in6_recoverscope(sin6, &inp->inp_laddr6); + sin6->sin6_port = inp->inp_lport; + + sin6 = (struct sockaddr_in6 *)&req->dstaddr; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + in6_recoverscope(sin6, &inp->inp_faddr6); + sin6->sin6_port = inp->inp_fport; +#else /* INET6 */ + unhandled_af(AF_INET6); +#endif /* INET6 */ + } else { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)&req->addr; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_addr = inp->inp_laddr; + sin->sin_port = inp->inp_lport; + + sin = (struct sockaddr_in *)&req->dstaddr; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_addr = inp->inp_faddr; + sin->sin_port = inp->inp_fport; + } + +// sounlock(so, s); + + return (0); +} + +static int +wg_get_rdomain(struct wg_softc *sc, struct ifreq *ifr) +{ + struct file *fp; + struct socket *so; + struct inpcb *inp; +// int s; + + fp = sc->sc_fp; + if (fp == NULL) + return (EADDRNOTAVAIL); + + so = (struct socket *)fp->f_data; +// s = solock(so); + inp = sotoinpcb(so); + + ifr->ifr_rdomainid = inp->inp_rtableid; + +// sounlock(so, s); + + return (0); +} + +static int +wg_get_ttl(struct wg_softc *sc, struct ifreq *ifr) +{ + struct file *fp; + struct socket *so; + struct inpcb *inp; +// int s; + + fp = sc->sc_fp; + if (fp == NULL) + return (EADDRNOTAVAIL); + + so = (struct socket *)fp->f_data; +// s = solock(so); + inp = sotoinpcb(so); + + if (inp->inp_flags & INP_IPV6) { +#ifdef INET6 + ifr->ifr_ttl = inp->inp_ipv6.ip6_hlim; +#else /* INET6 */ + unhandled_af(AF_INET6); +#endif /* INET6 */ + } else { + ifr->ifr_ttl = inp->inp_ip.ip_ttl; + } + +// sounlock(so, s); + + return (0); +} + +static int +wg_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct wg_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int error = 0; + + switch (cmd) { + case SIOCSIFADDR: + break; + + case SIOCSIFFLAGS: + if (ISSET(ifp->if_flags, IFF_UP)) { + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + error = wg_up(sc); + else + error = 0; + } else { + if (ISSET(ifp->if_flags, IFF_RUNNING)) + error = wg_down(sc); + } + break; + + case SIOCGLIFPHYADDR: + error = wg_get_tunnel(sc, (struct if_laddrreq *)data); + break; + case SIOCGLIFPHYRTABLE: + error = wg_get_rdomain(sc, ifr); + break; + case SIOCGLIFPHYTTL: + error = wg_get_ttl(sc, ifr); + break; + + default: + error = ENOTTY; + break; + } + + return (error); +} + +static int +wg_decap(struct ifnet *ifp, struct mbuf *m0, + const struct chacha_key *rxkey, uint64_t counter) +{ + struct wg_aead_ctx ctx; + struct wg_aead_tag ptag, ctag; + struct mbuf *mn, *m; + unsigned int len = m0->m_pkthdr.len; + unsigned int diff; + int rv = 0; + + if (len < sizeof(ptag)) { + /* must be long enough to contain the tag */ + ifp->if_iqdrops++; + return (-1); + } + + len -= sizeof(ptag); + if (len % 16) { + /* be padded */ + ifp->if_iqdrops++; + return (-1); + } + + wg_aead_init(&ctx, rxkey, counter); + + if (len) { + /* m_apply, but different */ + mn = m0; + do { + m = mn; + KASSERT(m != NULL); + + diff = min(m->m_len, len); + if (diff) { + wg_aead_verify(&ctx, m->m_data, diff); + len -= diff; + } + + mn = m->m_next; + } while (len > 0); + } else { + m = m0; + diff = 0; + } + + m_copydata(m, diff, sizeof(ptag), (caddr_t)&ptag); + + wg_aead_final(&ctx, &ctag); + if (memcmp(&ctag, &ptag, sizeof(ctag)) != 0) { + /* mac didnt match */ + ifp->if_ierrors++; + rv = -1; + goto out; + } + + /* chop the poly bit off */ + m_freem(m->m_next); + m->m_next = NULL; + m->m_len = diff; + m0->m_pkthdr.len -= sizeof(ptag); + + if (len) { + m = m0; + do { + wg_aead_decrypt(&ctx, m->m_data, m->m_len); + m = m->m_next; + } while (m != NULL); + } + +out: + explicit_bzero(&ctx, sizeof(ctx)); + + return (rv); +} + +static struct wg_data_keys * +wg_match_rx_data_keys(struct wg_softc *sc, uint32_t index) +{ + struct wg_data_keys *wk; + + TAILQ_FOREACH(wk, &sc->sc_rx_data_keys, wk_entry) { + if (wk->wk_rx_idx == index) + return (wk); + } + + return (NULL); +} + +static void +wg_send_rekey(struct wg_softc *sc) +{ + +} + +static struct mbuf * +wg_input(void *ctx, struct mbuf *m, int iphlen) +{ + struct wg_softc *sc = ctx; + struct ifnet *ifp = &sc->sc_if; + struct wg_data_hdr *hdr; + int hlen = iphlen + sizeof(*hdr); + struct wg_data_keys *wk; + struct mbuf *n; + uint64_t counter; + uint64_t diff; + int high; + int rv; + void (*input)(struct ifnet *, struct mbuf *); + + soassertlocked((struct socket *)sc->sc_fp->f_data); + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) { + /* no point if we're not up */ + return (m); + } + + if (TAILQ_EMPTY(&sc->sc_rx_data_keys)) { + /* not set up with any keys */ + return (m); + } + + if (m->m_pkthdr.len < hlen) { + /* decline short packets */ + return (m); + } + + if (m->m_len < hlen) { + m = m_pullup(m, hlen); + if (m == NULL) { + /* oops */ + return (NULL); + } + } + + hdr = (struct wg_data_hdr *)(mtod(m, uint8_t *) + iphlen); + if (hdr->msg_type != htole32(WG_MSG_DATA)) { + /* we only handle data in the kernel */ + return (m); + } + + counter = lemtoh32(&hdr->counter_lo) | + ((uint64_t)lemtoh32(&hdr->counter_hi) << 32); + + if (counter >= WG_REJECT_MSGS) + goto drop; + + /* might be ours now */ + + rw_enter_write(&sc->sc_rx_data_keys_lk); + + wk = wg_match_rx_data_keys(sc, hdr->index); + if (wk == NULL) { + /* not this connection */ + goto unlock; + } + + /* avoid {under,over}flow */ + high = (counter >= wk->wk_rx_seq); + if (high) + diff = counter - wk->wk_rx_seq; + else + diff = wk->wk_rx_seq - counter; + + if (diff >= WG_MSGS_WINDOW) + goto unlock; + + m_adj(m, hlen); + + rv = wg_decap(ifp, m, &wk->wk_rx_key, counter); + + if (rv != 0) + goto drop; + + /* actually ours now according to auth, so move forward */ + if (high) + wk->wk_rx_seq = counter; + + sc->sc_rx_stamp = ticks; + + rw_exit_write(&sc->sc_rx_data_keys_lk); + + if (counter >= WG_REKEY_MSGS) + wg_send_rekey(sc); + + if (m->m_pkthdr.len == 0) { + /* keepalive */ + return (NULL); + } + + n = m; + while (n->m_len == 0) { + n = n->m_next; + if (n == NULL) { + ifp->if_ierrors++; + goto drop; + } + } + + switch (*mtod(n, uint8_t *) >> 4) { + case 4: + input = ipv4_input; + m->m_pkthdr.ph_family = AF_INET; + break; + +#ifdef INET6 + case 6: + input = ipv6_input; + m->m_pkthdr.ph_family = AF_INET6; + break; +#endif + default: + ifp->if_noproto++; + goto drop; + } + + m->m_flags &= ~(M_MCAST|M_BCAST); + m->m_pkthdr.ph_ifidx = ifp->if_index; + m->m_pkthdr.ph_rtableid = ifp->if_rdomain; + m->m_pkthdr.ph_flowid = 0; + +#if NPF > 0 + pf_pkt_addr_changed(m); +#endif + + ifp->if_ipackets++; + ifp->if_ibytes += m->m_pkthdr.len; + +#if NBPFILTER > 0 + { + caddr_t if_bpf = ifp->if_bpf; + if (if_bpf) { + bpf_mtap_af(if_bpf, m->m_pkthdr.ph_family, m, + BPF_DIRECTION_IN); + } + } +#endif + + (*input)(ifp, m); + + return (NULL); + +unlock: + rw_exit_write(&sc->sc_rx_data_keys_lk); +drop: + m_freem(m); + return (NULL); +} + +static int +wg_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, + struct rtentry *rt) +{ + struct wg_softc *sc = ifp->if_softc; + struct m_tag *mtag; + int error = 0; + + if (!ISSET(ifp->if_flags, IFF_RUNNING) || + sc->sc_fp == NULL || + sc->sc_tx_data_keys == NULL) { + error = ENETDOWN; + goto drop; + } + + switch (dst->sa_family) { + case AF_INET: +#ifdef INET6 + case AF_INET6: +#endif + break; + default: + error = EAFNOSUPPORT; + goto drop; + } + + /* Try to limit infinite recursion through misconfiguration. */ + for (mtag = m_tag_find(m, PACKET_TAG_GRE, NULL); mtag; + mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) { + if (memcmp(mtag + 1, &ifp->if_index, + sizeof(ifp->if_index)) == 0) { + error = EIO; + goto drop; + } + } + + mtag = m_tag_get(PACKET_TAG_GRE, sizeof(ifp->if_index), M_NOWAIT); + if (mtag == NULL) { + error = ENOBUFS; + goto drop; + } + memcpy(mtag + 1, &ifp->if_index, sizeof(ifp->if_index)); + m_tag_prepend(m, mtag); + + m->m_pkthdr.ph_family = dst->sa_family; + + error = if_enqueue(ifp, m); + if (error) + ifp->if_oerrors++; + return (error); +drop: + m_freem(m); + return (error); +} + +static int +wg_encap(struct wg_softc *sc, struct wg_data_keys *wk, struct mbuf *m0) +{ + struct wg_aead_ctx ctx; + struct wg_aead_tag *tag; + struct mbuf *mn, *m; + struct wg_data_hdr *hdr; + uint64_t counter = wk->wk_tx_seq++; + int padlen; + + wg_aead_init(&ctx, &wk->wk_tx_key, counter); + + mn = m0; + do { + m = mn; + mn = m->m_next; + + if (m->m_len) + wg_aead_encrypt(&ctx, mtod(m, void *), m->m_len); + } while (mn != NULL); + + padlen = m0->m_pkthdr.len % 16; + if (padlen) { + uint8_t *zero; + + padlen = 16 - padlen; + + if (m_trailingspace(m) < padlen) { + mn = m_get(M_DONTWAIT, MT_DATA); + if (mn == NULL) + goto drop; + + m->m_next = mn; + m = mn; + + m->m_len = 0; + } + + zero = mtod(m, uint8_t *) + m->m_len; + memset(zero, 0, padlen); + wg_aead_encrypt(&ctx, zero, padlen); + + m0->m_pkthdr.len += padlen; + m->m_len += padlen; + } + + if (m_trailingspace(m) < sizeof(*tag)) { + mn = m_get(M_DONTWAIT, MT_DATA); + if (mn == NULL) + goto drop; + + m->m_next = mn; + m = mn; + + m->m_len = 0; + } + + tag = (struct wg_aead_tag *)(mtod(m, uint8_t *) + m->m_len); + wg_aead_final(&ctx, tag); + explicit_bzero(&ctx, sizeof(ctx)); + + m0->m_pkthdr.len += sizeof(*tag); + m->m_len += sizeof(*tag); + + m0 = m_prepend(m0, sizeof(*hdr), M_DONTWAIT); + if (m0 == NULL) + return (-1); + + hdr = mtod(m0, struct wg_data_hdr *); + hdr->msg_type = htole32(WG_MSG_DATA); + hdr->index = wk->wk_tx_idx; + htolem32(&hdr->counter_lo, counter); + htolem32(&hdr->counter_hi, counter >> 32); + + if (mq_enqueue(&sc->sc_tx_queue, m0) != 0) + return (-1); + + task_add(systq, &sc->sc_tx_task); + + return (0); + +drop: + m_freem(m0); + explicit_bzero(&ctx, sizeof(ctx)); + return (-1); +} + +static void +wg_send_keepalive(void *v) +{ + struct wg_softc *sc = v; + struct wg_data_keys *wk = sc->sc_tx_data_keys; + struct mbuf *m; + + if (wk == NULL) + return; + + m = m_gethdr(M_DONTWAIT, MT_DATA); + if (m == NULL) + return; + + m_align(m, sizeof(struct wg_aead_tag)); + m->m_pkthdr.len = m->m_len = 0; + + wg_encap(sc, wk, m); +} + +static void +wg_send(void *v) +{ + struct wg_softc *sc = v; + struct ifnet *ifp = &sc->sc_if; + struct file *fp; + struct socket *so; + struct mbuf_list ml; + struct mbuf *m; + int error; + int s; + + mq_delist(&sc->sc_tx_queue, &ml); + if (ml_empty(&ml)) + return; + + fp = sc->sc_fp; + if (fp == NULL || (so = (struct socket *)fp->f_data) == NULL) { + ml_purge(&ml); + return; + } + + /* XXX this knows too much about how udp_usrreq works internally. */ + + s = solock(so); + while ((m = ml_dequeue(&ml)) != NULL) { + error = udp_usrreq(so, PRU_SEND, m, NULL, NULL, NULL); + if (error) + ifp->if_oerrors++; + } + sounlock(so, s); + + sc->sc_tx_stamp = ticks; + +} + +static void +wg_start(struct ifqueue *ifq) +{ + struct ifnet *ifp = ifq->ifq_if; + struct wg_softc *sc = ifp->if_softc; + struct wg_data_keys *wk = sc->sc_tx_data_keys; + struct mbuf *m; + + if (sc->sc_fp == NULL || wk == NULL) { + ifq_purge(ifq); + return; + } + + while ((m = ifq_dequeue(ifq)) != NULL) { +#if NBPFILTER + { + caddr_t if_bpf = ifp->if_bpf; + if (if_bpf) { + bpf_mtap_af(if_bpf, m->m_pkthdr.ph_family, m, + BPF_DIRECTION_OUT); + } + } +#endif + + if (!wg_encap(sc, wk, m)) { + ifq->ifq_errors++; + continue; + } + } +} + +static void +wg_aead_init(struct wg_aead_ctx *ctx, const struct chacha_key *key, + uint64_t counter) +{ + uint8_t block0[CHACHA_BLOCKSIZE]; + + chacha_stream_keysetup(&ctx->chacha20, key); + + /* + * AEAD-ChaCha20-Poly1305 uses the IETF construction with the 96bit + * nonce and 32 bit counter starting from 0. WireGuard uses this AEAD + * with the counter off the wire as the nonce. That nonce is padded + * with zeros on the left, so the layout of the chacha state is + * predictable. Set it up directly, rather than stuff bits just so + * they can be unstuffed straight away. + */ + ctx->chacha20.ctx.input[12] = 0; /* counter starts at 0 */ + ctx->chacha20.ctx.input[13] = 0; /* nonce padding is 0 */ + ctx->chacha20.ctx.input[14] = counter; /* the rest of the "nonce" */ + ctx->chacha20.ctx.input[15] = counter >> 32; + ctx->chacha20.used = 0; + + /* AEAD-ChaCha20-Poly1305 uses the first block for the poly key */ + bzero(block0, sizeof(block0)); + chacha_stream_update(&ctx->chacha20, block0, block0, sizeof(block0)); + poly1305_init(&ctx->poly1305, block0); + explicit_bzero(block0, sizeof(block0)); + + /* data has no aad */ + + ctx->datalen = 0; +} + +static void +wg_aead_encrypt(struct wg_aead_ctx *ctx, void *mem, size_t len) +{ + chacha_stream_update(&ctx->chacha20, mem, mem, len); + poly1305_update(&ctx->poly1305, mem, len); + ctx->datalen += len; +} + +static void +wg_aead_verify(struct wg_aead_ctx *ctx, void *mem, size_t len) +{ + poly1305_update(&ctx->poly1305, mem, len); + ctx->datalen += len; +} + +static void +wg_aead_decrypt(struct wg_aead_ctx *ctx, void *mem, size_t len) +{ + chacha_stream_update(&ctx->chacha20, mem, mem, len); +} + +static void +wg_aead_final(struct wg_aead_ctx *ctx, struct wg_aead_tag *tag) +{ + uint8_t len[8]; + uint64_t j; + unsigned int i; + + /* data has no aad, so aad len is 0 */ + memset(len, 0, sizeof(len)); + poly1305_update(&ctx->poly1305, len, sizeof(len)); + + j = ctx->datalen; + for (i = 0; i < sizeof(len); i++) { + len[i] = j; + j >>= 8; + } + poly1305_update(&ctx->poly1305, len, sizeof(len)); + + poly1305_finish(&ctx->poly1305, tag->tag); +} Index: net/if_wg.h =================================================================== RCS file: net/if_wg.h diff -N net/if_wg.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/if_wg.h 10 Feb 2019 03:49:56 -0000 @@ -0,0 +1,158 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2018 David Gwynne + * + * 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. + */ + +#ifndef _NET_IF_WG_H_ +#define _NET_IF_WG_H_ + +/* the timing side of messages */ + +#define WG_MSGS_WINDOW 2000 + +#define WG_REKEY_MSGS (~0ULL - 0xffffULL) +#define WG_REKEY_TIME 120000 /* msec */ +#define WG_KEEPALIVE 10000 /* msec */ +#define WG_REKEY_TIMEOUT 5000 /* msec */ +#define WG_REKEY_JITTER 333 /* msec */ +#define WG_REKEY_MAX 90000 /* msec */ +#define WG_REJECT_MSGS (~0ULL - WG_MSGS_WINDOW) +#define WG_REJECT_TIME 180000 /* msec */ + + +/* the bytes side of messages */ + +#define WG_POLY1305_TAG_LEN 16 +#define WG_CURVE25519_KEY_LEN 32 +#define WG_AEAD_CHACHA20_POLY1305_KEY_LEN \ + 32 +#define WG_BLAKE2S_OUTPUT_LEN 16 +#define WG_CHACHA20_POLY1305_NONCE_LEN \ + 24 + +#define WG_AEAD_LEN(_l) ((_l) + WG_POLY1305_TAG_LEN) + +#define WG_ENCRYPTED_STATIC_LEN WG_AEAD_LEN(WG_CURVE25519_KEY_LEN) + +#define WG_MSG_HANDSHAKE_INIT 1 +#define WG_MSG_HANDSHAKE_RESP 2 +#define WG_MSG_COOKIE_RESP 3 +#define WG_MSG_DATA 4 + +struct wg_header { + uint8_t msg_type; + uint8_t zero[3]; +} __packed __aligned(4); + +struct wg_tai64n { + uint64_t tai_seconds; /* BE */ + uint32_t tai_nanoseconds; /* BE */ +} __packed __aligned(4); + +#define WG_ENCRYPTED_TAI64N_LEN WG_AEAD_LEN(sizeof(struct wg_tai64n)) + +struct wg_handshake_init { + struct wg_header hdr; + uint32_t sndr_idx; /* LE */ + uint8_t unencrypted_ephemeral[WG_CURVE25519_KEY_LEN]; + uint8_t encrypted_static[WG_ENCRYPTED_STATIC_LEN]; + uint8_t encrypted_timestamp[WG_ENCRYPTED_TAI64N_LEN]; + uint8_t mac1[WG_BLAKE2S_OUTPUT_LEN]; + uint8_t mac2[WG_BLAKE2S_OUTPUT_LEN]; +} __packed __aligned(4); + +struct wg_handshake_resp { + struct wg_header hdr; + uint32_t sndr_idx; /* LE */ + uint32_t rcvr_idx; /* LE */ + uint8_t unencrypted_ephemeral[WG_CURVE25519_KEY_LEN]; + uint8_t encrypted_nothing[WG_AEAD_LEN(0)]; + uint8_t mac1[WG_BLAKE2S_OUTPUT_LEN]; + uint8_t mac2[WG_BLAKE2S_OUTPUT_LEN]; +} __packed __aligned(4); + +struct wg_cookie_resp { + struct wg_header hdr; + uint32_t rcvr_idx; /* LE */ + uint8_t nonce[24]; + uint8_t encrypted_cookie[WG_AEAD_LEN(0)]; +} __packed __aligned(4); + +struct wg_data { + struct wg_header hdr; + uint32_t rcvr_idx; + uint32_t counter_lo; + uint32_t counter_hi; + + /* followed by encrypted data */ +} __packed __aligned(4); + +/* + * ioctl interface + */ + +#include + +#define WGIFCREATE _IOW('w', 10, unsigned int) +#define WGIFATTACH _IOW('w', 11, unsigned int) +#define WGIFDETACH _IOW('w', 12, unsigned int) +#define WGIFDESTROY _IOW('w', 13, unsigned int) + +struct wg_if_sock { + unsigned int wg_unit; + int wg_sock; +}; + +#define WGIFSSOCK _IOW('w', 14, struct wg_if_sock) +#define WGIFDSOCK _IOW('w', 15, unsigned int) + +struct wg_aead_key { + uint8_t key[WG_AEAD_CHACHA20_POLY1305_KEY_LEN]; +}; + +struct wg_if_data_keys { + unsigned int wg_unit; + uint32_t wg_tx_index; /* LE */ + uint32_t wg_rx_index; /* LE */ + struct wg_aead_key wg_tx_key; + struct wg_aead_key wg_rx_key; +}; + +#define WGIFADDKEYS _IOW('w', 16, struct wg_if_data_keys) +#define WGIFDELKEYS _IOW('w', 17, struct wg_if_data_keys) + +struct wg_if_role { + unsigned int wg_unit; + unsigned int wg_role; +#define WG_DATA_INITIATOR 0x696e6974 +#define WG_DATA_RESPONDER 0x72657370 +}; + +#define WGIFSROLE _IOW('w', 18, struct wg_if_role) +#define WGIFGROLE _IOWR('w', 20, struct wg_if_role) + +/* + * messages via reads + */ + +#define WG_MSG_REKEY 0x746b6579 + +struct wg_msg { + unsigned int wg_unit; + unsigned int wg_type; +}; + +#endif /* _NET_IF_WG_H_ */ Index: netinet/in_pcb.h =================================================================== RCS file: /cvs/src/sys/netinet/in_pcb.h,v retrieving revision 1.115 diff -u -p -r1.115 in_pcb.h --- netinet/in_pcb.h 4 Oct 2018 17:33:41 -0000 1.115 +++ netinet/in_pcb.h 10 Feb 2019 03:49:56 -0000 @@ -143,6 +143,8 @@ struct inpcb { #endif struct icmp6_filter *inp_icmp6filt; struct pf_state_key *inp_pf_sk; + struct mbuf *(*inp_upcall)(void *, struct mbuf *, int); + void *inp_upcall_arg; u_int inp_rtableid; int inp_pipex; /* pipex indication */ }; Index: netinet/udp_usrreq.c =================================================================== RCS file: /cvs/src/sys/netinet/udp_usrreq.c,v retrieving revision 1.254 diff -u -p -r1.254 udp_usrreq.c --- netinet/udp_usrreq.c 10 Nov 2018 18:40:34 -0000 1.254 +++ netinet/udp_usrreq.c 10 Feb 2019 03:49:56 -0000 @@ -579,12 +579,20 @@ bad: void udp_sbappend(struct inpcb *inp, struct mbuf *m, struct ip *ip, - struct ip6_hdr *ip6, int iphlen, struct udphdr *uh, + struct ip6_hdr *ip6, int hlen, struct udphdr *uh, struct sockaddr *srcaddr, u_int32_t ipsecflowinfo) { struct socket *so = inp->inp_socket; struct mbuf *opts = NULL; + hlen += sizeof(*uh); + + if (inp->inp_upcall != NULL) { + m = (*inp->inp_upcall)(inp->inp_upcall_arg, m, hlen); + if (m == NULL) + return; + } + #ifdef INET6 if (ip6 && (inp->inp_flags & IN6P_CONTROLOPTS || so->so_options & SO_TIMESTAMP)) @@ -621,7 +629,7 @@ udp_sbappend(struct inpcb *inp, struct m sizeof(u_int32_t), IP_IPSECFLOWINFO, IPPROTO_IP); } #endif - m_adj(m, iphlen + sizeof(struct udphdr)); + m_adj(m, hlen); if (sbappendaddr(so, &so->so_rcv, srcaddr, m, opts) == 0) { udpstat_inc(udps_fullsock); m_freem(m); Index: sys/conf.h =================================================================== RCS file: /cvs/src/sys/sys/conf.h,v retrieving revision 1.145 diff -u -p -r1.145 conf.h --- sys/conf.h 31 Aug 2018 04:20:37 -0000 1.145 +++ sys/conf.h 10 Feb 2019 03:49:56 -0000 @@ -265,6 +265,13 @@ extern struct cdevsw cdevsw[]; 0, 0, dev_init(c,n,kqfilter) } /* open, close, read, write, ioctl, poll, kqfilter -- XXX should be generic device */ +#define cdev_wg_init(c,n) { \ + dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read), \ + dev_init(c,n,write), dev_init(c,n,ioctl), (dev_type_stop((*))) enodev, \ + 0, dev_init(c,n,poll), (dev_type_mmap((*))) enodev, \ + 0, 0, dev_init(c,n,kqfilter) } + +/* open, close, read, write, ioctl, poll, kqfilter -- XXX should be generic device */ #define cdev_switch_init(c,n) { \ dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read), \ dev_init(c,n,write), dev_init(c,n,ioctl), (dev_type_stop((*))) enodev, \ @@ -583,6 +590,7 @@ cdev_decl(pf); cdev_decl(tun); cdev_decl(tap); +cdev_decl(wg); cdev_decl(switch); cdev_decl(pppx);