xref: /dflybsd-src/sys/net/wg/wg_cookie.c (revision 1fdfe5a969e04f1629ee490b9160f3226911835b)
1a6bca3d2SAaron LI /* SPDX-License-Identifier: ISC
2a6bca3d2SAaron LI  *
3a6bca3d2SAaron LI  * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
4a6bca3d2SAaron LI  * Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
5a6bca3d2SAaron LI  */
6a6bca3d2SAaron LI 
7a6bca3d2SAaron LI #include "opt_inet.h"
8a6bca3d2SAaron LI #include "opt_inet6.h"
9a6bca3d2SAaron LI 
10a6bca3d2SAaron LI #include <sys/param.h>
11a6bca3d2SAaron LI #include <sys/systm.h>
12a6bca3d2SAaron LI #include <sys/kernel.h>
13a6bca3d2SAaron LI #include <sys/lock.h>
14be332237SAaron LI #include <sys/objcache.h>
15*1fdfe5a9SAaron LI #include <sys/queue.h>
16a6bca3d2SAaron LI #include <sys/socket.h>
17a6bca3d2SAaron LI #include <crypto/siphash/siphash.h>
18a6bca3d2SAaron LI #include <netinet/in.h>
19a6bca3d2SAaron LI 
20a6bca3d2SAaron LI #include "wg_cookie.h"
21a6bca3d2SAaron LI 
22a6bca3d2SAaron LI #define COOKIE_MAC1_KEY_LABEL	"mac1----"
23a6bca3d2SAaron LI #define COOKIE_COOKIE_KEY_LABEL	"cookie--"
24a6bca3d2SAaron LI #define COOKIE_SECRET_MAX_AGE	120
25a6bca3d2SAaron LI #define COOKIE_SECRET_LATENCY	5
26a6bca3d2SAaron LI 
27a6bca3d2SAaron LI /* Constants for initiation rate limiting */
28a6bca3d2SAaron LI #define RATELIMIT_SIZE		(1 << 13)
29a6bca3d2SAaron LI #define RATELIMIT_MASK		(RATELIMIT_SIZE - 1)
30a6bca3d2SAaron LI #define RATELIMIT_SIZE_MAX	(RATELIMIT_SIZE * 8)
31a6bca3d2SAaron LI #define INITIATIONS_PER_SECOND	20
32a6bca3d2SAaron LI #define INITIATIONS_BURSTABLE	5
33a6bca3d2SAaron LI #define INITIATION_COST		(SBT_1S / INITIATIONS_PER_SECOND)
34a6bca3d2SAaron LI #define TOKEN_MAX		(INITIATION_COST * INITIATIONS_BURSTABLE)
35a6bca3d2SAaron LI #define ELEMENT_TIMEOUT		1
36a6bca3d2SAaron LI #define IPV4_MASK_SIZE		4 /* Use all 4 bytes of IPv4 address */
37a6bca3d2SAaron LI #define IPV6_MASK_SIZE		8 /* Use top 8 bytes (/64) of IPv6 address */
38a6bca3d2SAaron LI 
39a6bca3d2SAaron LI struct ratelimit_key {
40a6bca3d2SAaron LI 	struct vnet *vnet;
41a6bca3d2SAaron LI 	uint8_t ip[IPV6_MASK_SIZE];
42a6bca3d2SAaron LI };
43a6bca3d2SAaron LI 
44a6bca3d2SAaron LI struct ratelimit_entry {
45a6bca3d2SAaron LI 	LIST_ENTRY(ratelimit_entry)	r_entry;
46a6bca3d2SAaron LI 	struct ratelimit_key		r_key;
47a6bca3d2SAaron LI 	sbintime_t			r_last_time;	/* sbinuptime */
48a6bca3d2SAaron LI 	uint64_t			r_tokens;
49a6bca3d2SAaron LI };
50a6bca3d2SAaron LI 
51a6bca3d2SAaron LI struct ratelimit {
52a6bca3d2SAaron LI 	uint8_t				rl_secret[SIPHASH_KEY_LENGTH];
53139109cdSAaron LI 	struct lock			rl_mtx;
54a6bca3d2SAaron LI 	struct callout			rl_gc;
55a6bca3d2SAaron LI 	LIST_HEAD(, ratelimit_entry)	rl_table[RATELIMIT_SIZE];
56a6bca3d2SAaron LI 	size_t				rl_table_num;
57a6bca3d2SAaron LI 	bool				rl_initialized;
58a6bca3d2SAaron LI };
59a6bca3d2SAaron LI 
60a6bca3d2SAaron LI static void	precompute_key(uint8_t *,
61a6bca3d2SAaron LI 			const uint8_t[COOKIE_INPUT_SIZE], const char *);
62a6bca3d2SAaron LI static void	macs_mac1(struct cookie_macs *, const void *, size_t,
63a6bca3d2SAaron LI 			const uint8_t[COOKIE_KEY_SIZE]);
64a6bca3d2SAaron LI static void	macs_mac2(struct cookie_macs *, const void *, size_t,
65a6bca3d2SAaron LI 			const uint8_t[COOKIE_COOKIE_SIZE]);
66a6bca3d2SAaron LI static int	timer_expired(sbintime_t, uint32_t, uint32_t);
67a6bca3d2SAaron LI static void	make_cookie(struct cookie_checker *,
68a6bca3d2SAaron LI 			uint8_t[COOKIE_COOKIE_SIZE], struct sockaddr *);
69a6bca3d2SAaron LI static void	ratelimit_init(struct ratelimit *);
70a6bca3d2SAaron LI static void	ratelimit_deinit(struct ratelimit *);
71a6bca3d2SAaron LI static void	ratelimit_gc_callout(void *);
72a6bca3d2SAaron LI static void	ratelimit_gc_schedule(struct ratelimit *);
73a6bca3d2SAaron LI static void	ratelimit_gc(struct ratelimit *, bool);
74a6bca3d2SAaron LI static int	ratelimit_allow(struct ratelimit *, struct sockaddr *, struct vnet *);
75a6bca3d2SAaron LI static uint64_t siphash13(const uint8_t [SIPHASH_KEY_LENGTH], const void *, size_t);
76a6bca3d2SAaron LI 
77a6bca3d2SAaron LI static struct ratelimit ratelimit_v4;
78a6bca3d2SAaron LI #ifdef INET6
79a6bca3d2SAaron LI static struct ratelimit ratelimit_v6;
80a6bca3d2SAaron LI #endif
81be332237SAaron LI 
82be332237SAaron LI static struct objcache *ratelimit_zone;
83be332237SAaron LI MALLOC_DEFINE(M_WG_RATELIMIT, "WG ratelimit", "wireguard ratelimit");
84a6bca3d2SAaron LI 
85a6bca3d2SAaron LI /* Public Functions */
86a6bca3d2SAaron LI int
87a6bca3d2SAaron LI cookie_init(void)
88a6bca3d2SAaron LI {
89be332237SAaron LI 	ratelimit_zone = objcache_create_simple(M_WG_RATELIMIT,
90be332237SAaron LI 	    sizeof(struct ratelimit_entry));
91be332237SAaron LI 	if (ratelimit_zone == NULL)
92a6bca3d2SAaron LI 		return ENOMEM;
93a6bca3d2SAaron LI 
94a6bca3d2SAaron LI 	ratelimit_init(&ratelimit_v4);
95a6bca3d2SAaron LI #ifdef INET6
96a6bca3d2SAaron LI 	ratelimit_init(&ratelimit_v6);
97a6bca3d2SAaron LI #endif
98a6bca3d2SAaron LI 	return (0);
99a6bca3d2SAaron LI }
100a6bca3d2SAaron LI 
101a6bca3d2SAaron LI void
102a6bca3d2SAaron LI cookie_deinit(void)
103a6bca3d2SAaron LI {
104a6bca3d2SAaron LI 	ratelimit_deinit(&ratelimit_v4);
105a6bca3d2SAaron LI #ifdef INET6
106a6bca3d2SAaron LI 	ratelimit_deinit(&ratelimit_v6);
107a6bca3d2SAaron LI #endif
108a6bca3d2SAaron LI 	if (ratelimit_zone != NULL)
109be332237SAaron LI 		objcache_destroy(ratelimit_zone);
110a6bca3d2SAaron LI }
111a6bca3d2SAaron LI 
112a6bca3d2SAaron LI void
113a6bca3d2SAaron LI cookie_checker_init(struct cookie_checker *cc)
114a6bca3d2SAaron LI {
115a6bca3d2SAaron LI 	bzero(cc, sizeof(*cc));
116a6bca3d2SAaron LI 
1177ef217feSAaron LI 	lockinit(&cc->cc_key_lock, "cookie_checker_key", 0, 0);
118139109cdSAaron LI 	lockinit(&cc->cc_secret_mtx, "cookie_checker_secret", 0, 0);
119a6bca3d2SAaron LI }
120a6bca3d2SAaron LI 
121a6bca3d2SAaron LI void
122a6bca3d2SAaron LI cookie_checker_free(struct cookie_checker *cc)
123a6bca3d2SAaron LI {
1247ef217feSAaron LI 	lockuninit(&cc->cc_key_lock);
125139109cdSAaron LI 	lockuninit(&cc->cc_secret_mtx);
126a6bca3d2SAaron LI 	explicit_bzero(cc, sizeof(*cc));
127a6bca3d2SAaron LI }
128a6bca3d2SAaron LI 
129a6bca3d2SAaron LI void
130a6bca3d2SAaron LI cookie_checker_update(struct cookie_checker *cc,
131a6bca3d2SAaron LI     const uint8_t key[COOKIE_INPUT_SIZE])
132a6bca3d2SAaron LI {
1337ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_EXCLUSIVE);
134a6bca3d2SAaron LI 	if (key) {
135a6bca3d2SAaron LI 		precompute_key(cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
136a6bca3d2SAaron LI 		precompute_key(cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
137a6bca3d2SAaron LI 	} else {
138a6bca3d2SAaron LI 		bzero(cc->cc_mac1_key, sizeof(cc->cc_mac1_key));
139a6bca3d2SAaron LI 		bzero(cc->cc_cookie_key, sizeof(cc->cc_cookie_key));
140a6bca3d2SAaron LI 	}
1417ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_RELEASE);
142a6bca3d2SAaron LI }
143a6bca3d2SAaron LI 
144a6bca3d2SAaron LI void
145a6bca3d2SAaron LI cookie_checker_create_payload(struct cookie_checker *cc,
146a6bca3d2SAaron LI     struct cookie_macs *macs, uint8_t nonce[COOKIE_NONCE_SIZE],
147a6bca3d2SAaron LI     uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], struct sockaddr *sa)
148a6bca3d2SAaron LI {
149a6bca3d2SAaron LI 	uint8_t cookie[COOKIE_COOKIE_SIZE];
150a6bca3d2SAaron LI 
151a6bca3d2SAaron LI 	make_cookie(cc, cookie, sa);
152a6bca3d2SAaron LI 	arc4random_buf(nonce, COOKIE_NONCE_SIZE);
153a6bca3d2SAaron LI 
1547ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_SHARED);
155a6bca3d2SAaron LI 	xchacha20poly1305_encrypt(ecookie, cookie, COOKIE_COOKIE_SIZE,
156a6bca3d2SAaron LI 	    macs->mac1, COOKIE_MAC_SIZE, nonce, cc->cc_cookie_key);
1577ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_RELEASE);
158a6bca3d2SAaron LI 
159a6bca3d2SAaron LI 	explicit_bzero(cookie, sizeof(cookie));
160a6bca3d2SAaron LI }
161a6bca3d2SAaron LI 
162a6bca3d2SAaron LI void
163a6bca3d2SAaron LI cookie_maker_init(struct cookie_maker *cm, const uint8_t key[COOKIE_INPUT_SIZE])
164a6bca3d2SAaron LI {
165a6bca3d2SAaron LI 	bzero(cm, sizeof(*cm));
166a6bca3d2SAaron LI 	precompute_key(cm->cm_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
167a6bca3d2SAaron LI 	precompute_key(cm->cm_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
1687ef217feSAaron LI 	lockinit(&cm->cm_lock, "cookie_maker", 0, 0);
169a6bca3d2SAaron LI }
170a6bca3d2SAaron LI 
171a6bca3d2SAaron LI void
172a6bca3d2SAaron LI cookie_maker_free(struct cookie_maker *cm)
173a6bca3d2SAaron LI {
1747ef217feSAaron LI 	lockuninit(&cm->cm_lock);
175a6bca3d2SAaron LI 	explicit_bzero(cm, sizeof(*cm));
176a6bca3d2SAaron LI }
177a6bca3d2SAaron LI 
178a6bca3d2SAaron LI int
179a6bca3d2SAaron LI cookie_maker_consume_payload(struct cookie_maker *cm,
180a6bca3d2SAaron LI     uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE])
181a6bca3d2SAaron LI {
182a6bca3d2SAaron LI 	uint8_t cookie[COOKIE_COOKIE_SIZE];
183a6bca3d2SAaron LI 	int ret;
184a6bca3d2SAaron LI 
1857ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_SHARED);
186a6bca3d2SAaron LI 	if (!cm->cm_mac1_sent) {
187a6bca3d2SAaron LI 		ret = ETIMEDOUT;
188a6bca3d2SAaron LI 		goto error;
189a6bca3d2SAaron LI 	}
190a6bca3d2SAaron LI 
191a6bca3d2SAaron LI 	if (!xchacha20poly1305_decrypt(cookie, ecookie, COOKIE_ENCRYPTED_SIZE,
192a6bca3d2SAaron LI 	    cm->cm_mac1_last, COOKIE_MAC_SIZE, nonce, cm->cm_cookie_key)) {
193a6bca3d2SAaron LI 		ret = EINVAL;
194a6bca3d2SAaron LI 		goto error;
195a6bca3d2SAaron LI 	}
1967ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_RELEASE);
197a6bca3d2SAaron LI 
1987ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_EXCLUSIVE);
199a6bca3d2SAaron LI 	memcpy(cm->cm_cookie, cookie, COOKIE_COOKIE_SIZE);
200a6bca3d2SAaron LI 	cm->cm_cookie_birthdate = getsbinuptime();
201a6bca3d2SAaron LI 	cm->cm_cookie_valid = true;
202a6bca3d2SAaron LI 	cm->cm_mac1_sent = false;
2037ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_RELEASE);
204a6bca3d2SAaron LI 
205a6bca3d2SAaron LI 	return 0;
206a6bca3d2SAaron LI error:
2077ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_RELEASE);
208a6bca3d2SAaron LI 	return ret;
209a6bca3d2SAaron LI }
210a6bca3d2SAaron LI 
211a6bca3d2SAaron LI void
212a6bca3d2SAaron LI cookie_maker_mac(struct cookie_maker *cm, struct cookie_macs *macs, void *buf,
213a6bca3d2SAaron LI     size_t len)
214a6bca3d2SAaron LI {
2157ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_EXCLUSIVE);
216a6bca3d2SAaron LI 	macs_mac1(macs, buf, len, cm->cm_mac1_key);
217a6bca3d2SAaron LI 	memcpy(cm->cm_mac1_last, macs->mac1, COOKIE_MAC_SIZE);
218a6bca3d2SAaron LI 	cm->cm_mac1_sent = true;
219a6bca3d2SAaron LI 
220a6bca3d2SAaron LI 	if (cm->cm_cookie_valid &&
221a6bca3d2SAaron LI 	    !timer_expired(cm->cm_cookie_birthdate,
222a6bca3d2SAaron LI 	    COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY, 0)) {
223a6bca3d2SAaron LI 		macs_mac2(macs, buf, len, cm->cm_cookie);
224a6bca3d2SAaron LI 	} else {
225a6bca3d2SAaron LI 		bzero(macs->mac2, COOKIE_MAC_SIZE);
226a6bca3d2SAaron LI 		cm->cm_cookie_valid = false;
227a6bca3d2SAaron LI 	}
2287ef217feSAaron LI 	lockmgr(&cm->cm_lock, LK_RELEASE);
229a6bca3d2SAaron LI }
230a6bca3d2SAaron LI 
231a6bca3d2SAaron LI int
232a6bca3d2SAaron LI cookie_checker_validate_macs(struct cookie_checker *cc, struct cookie_macs *macs,
233a6bca3d2SAaron LI     void *buf, size_t len, bool check_cookie, struct sockaddr *sa, struct vnet *vnet)
234a6bca3d2SAaron LI {
235a6bca3d2SAaron LI 	struct cookie_macs our_macs;
236a6bca3d2SAaron LI 	uint8_t cookie[COOKIE_COOKIE_SIZE];
237a6bca3d2SAaron LI 
238a6bca3d2SAaron LI 	/* Validate incoming MACs */
2397ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_SHARED);
240a6bca3d2SAaron LI 	macs_mac1(&our_macs, buf, len, cc->cc_mac1_key);
2417ef217feSAaron LI 	lockmgr(&cc->cc_key_lock, LK_RELEASE);
242a6bca3d2SAaron LI 
243a6bca3d2SAaron LI 	/* If mac1 is invald, we want to drop the packet */
244a6bca3d2SAaron LI 	if (timingsafe_bcmp(our_macs.mac1, macs->mac1, COOKIE_MAC_SIZE) != 0)
245a6bca3d2SAaron LI 		return EINVAL;
246a6bca3d2SAaron LI 
247a6bca3d2SAaron LI 	if (check_cookie) {
248a6bca3d2SAaron LI 		make_cookie(cc, cookie, sa);
249a6bca3d2SAaron LI 		macs_mac2(&our_macs, buf, len, cookie);
250a6bca3d2SAaron LI 
251a6bca3d2SAaron LI 		/* If the mac2 is invalid, we want to send a cookie response */
252a6bca3d2SAaron LI 		if (timingsafe_bcmp(our_macs.mac2, macs->mac2, COOKIE_MAC_SIZE) != 0)
253a6bca3d2SAaron LI 			return EAGAIN;
254a6bca3d2SAaron LI 
255a6bca3d2SAaron LI 		/* If the mac2 is valid, we may want rate limit the peer.
256a6bca3d2SAaron LI 		 * ratelimit_allow will return either 0 or ECONNREFUSED,
257a6bca3d2SAaron LI 		 * implying there is no ratelimiting, or we should ratelimit
258a6bca3d2SAaron LI 		 * (refuse) respectively. */
259a6bca3d2SAaron LI 		if (sa->sa_family == AF_INET)
260a6bca3d2SAaron LI 			return ratelimit_allow(&ratelimit_v4, sa, vnet);
261a6bca3d2SAaron LI #ifdef INET6
262a6bca3d2SAaron LI 		else if (sa->sa_family == AF_INET6)
263a6bca3d2SAaron LI 			return ratelimit_allow(&ratelimit_v6, sa, vnet);
264a6bca3d2SAaron LI #endif
265a6bca3d2SAaron LI 		else
266a6bca3d2SAaron LI 			return EAFNOSUPPORT;
267a6bca3d2SAaron LI 	}
268a6bca3d2SAaron LI 
269a6bca3d2SAaron LI 	return 0;
270a6bca3d2SAaron LI }
271a6bca3d2SAaron LI 
272a6bca3d2SAaron LI /* Private functions */
273a6bca3d2SAaron LI static void
274a6bca3d2SAaron LI precompute_key(uint8_t *key, const uint8_t input[COOKIE_INPUT_SIZE],
275a6bca3d2SAaron LI     const char *label)
276a6bca3d2SAaron LI {
277a6bca3d2SAaron LI 	struct blake2s_state blake;
278a6bca3d2SAaron LI 	blake2s_init(&blake, COOKIE_KEY_SIZE);
279a6bca3d2SAaron LI 	blake2s_update(&blake, label, strlen(label));
280a6bca3d2SAaron LI 	blake2s_update(&blake, input, COOKIE_INPUT_SIZE);
281a6bca3d2SAaron LI 	blake2s_final(&blake, key);
282a6bca3d2SAaron LI }
283a6bca3d2SAaron LI 
284a6bca3d2SAaron LI static void
285a6bca3d2SAaron LI macs_mac1(struct cookie_macs *macs, const void *buf, size_t len,
286a6bca3d2SAaron LI     const uint8_t key[COOKIE_KEY_SIZE])
287a6bca3d2SAaron LI {
288a6bca3d2SAaron LI 	struct blake2s_state state;
289a6bca3d2SAaron LI 	blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE);
290a6bca3d2SAaron LI 	blake2s_update(&state, buf, len);
291a6bca3d2SAaron LI 	blake2s_final(&state, macs->mac1);
292a6bca3d2SAaron LI }
293a6bca3d2SAaron LI 
294a6bca3d2SAaron LI static void
295a6bca3d2SAaron LI macs_mac2(struct cookie_macs *macs, const void *buf, size_t len,
296a6bca3d2SAaron LI     const uint8_t key[COOKIE_COOKIE_SIZE])
297a6bca3d2SAaron LI {
298a6bca3d2SAaron LI 	struct blake2s_state state;
299a6bca3d2SAaron LI 	blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE);
300a6bca3d2SAaron LI 	blake2s_update(&state, buf, len);
301a6bca3d2SAaron LI 	blake2s_update(&state, macs->mac1, COOKIE_MAC_SIZE);
302a6bca3d2SAaron LI 	blake2s_final(&state, macs->mac2);
303a6bca3d2SAaron LI }
304a6bca3d2SAaron LI 
305a6bca3d2SAaron LI static __inline int
306a6bca3d2SAaron LI timer_expired(sbintime_t timer, uint32_t sec, uint32_t nsec)
307a6bca3d2SAaron LI {
308a6bca3d2SAaron LI 	sbintime_t now = getsbinuptime();
309a6bca3d2SAaron LI 	return (now > (timer + sec * SBT_1S + nstosbt(nsec))) ? ETIMEDOUT : 0;
310a6bca3d2SAaron LI }
311a6bca3d2SAaron LI 
312a6bca3d2SAaron LI static void
313a6bca3d2SAaron LI make_cookie(struct cookie_checker *cc, uint8_t cookie[COOKIE_COOKIE_SIZE],
314a6bca3d2SAaron LI     struct sockaddr *sa)
315a6bca3d2SAaron LI {
316a6bca3d2SAaron LI 	struct blake2s_state state;
317a6bca3d2SAaron LI 
318139109cdSAaron LI 	lockmgr(&cc->cc_secret_mtx, LK_EXCLUSIVE);
319a6bca3d2SAaron LI 	if (timer_expired(cc->cc_secret_birthdate,
320a6bca3d2SAaron LI 	    COOKIE_SECRET_MAX_AGE, 0)) {
321a6bca3d2SAaron LI 		arc4random_buf(cc->cc_secret, COOKIE_SECRET_SIZE);
322a6bca3d2SAaron LI 		cc->cc_secret_birthdate = getsbinuptime();
323a6bca3d2SAaron LI 	}
324a6bca3d2SAaron LI 	blake2s_init_key(&state, COOKIE_COOKIE_SIZE, cc->cc_secret,
325a6bca3d2SAaron LI 	    COOKIE_SECRET_SIZE);
326139109cdSAaron LI 	lockmgr(&cc->cc_secret_mtx, LK_RELEASE);
327a6bca3d2SAaron LI 
328a6bca3d2SAaron LI 	if (sa->sa_family == AF_INET) {
329a6bca3d2SAaron LI 		blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_addr,
330a6bca3d2SAaron LI 				sizeof(struct in_addr));
331a6bca3d2SAaron LI 		blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_port,
332a6bca3d2SAaron LI 				sizeof(in_port_t));
333a6bca3d2SAaron LI 		blake2s_final(&state, cookie);
334a6bca3d2SAaron LI #ifdef INET6
335a6bca3d2SAaron LI 	} else if (sa->sa_family == AF_INET6) {
336a6bca3d2SAaron LI 		blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_addr,
337a6bca3d2SAaron LI 				sizeof(struct in6_addr));
338a6bca3d2SAaron LI 		blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_port,
339a6bca3d2SAaron LI 				sizeof(in_port_t));
340a6bca3d2SAaron LI 		blake2s_final(&state, cookie);
341a6bca3d2SAaron LI #endif
342a6bca3d2SAaron LI 	} else {
343a6bca3d2SAaron LI 		arc4random_buf(cookie, COOKIE_COOKIE_SIZE);
344a6bca3d2SAaron LI 	}
345a6bca3d2SAaron LI }
346a6bca3d2SAaron LI 
347a6bca3d2SAaron LI static void
348a6bca3d2SAaron LI ratelimit_init(struct ratelimit *rl)
349a6bca3d2SAaron LI {
350a6bca3d2SAaron LI 	size_t i;
351139109cdSAaron LI 	lockinit(&rl->rl_mtx, "ratelimit_lock", 0, 0);
352a6bca3d2SAaron LI 	callout_init_mtx(&rl->rl_gc, &rl->rl_mtx, 0);
353a6bca3d2SAaron LI 	arc4random_buf(rl->rl_secret, sizeof(rl->rl_secret));
354a6bca3d2SAaron LI 	for (i = 0; i < RATELIMIT_SIZE; i++)
355a6bca3d2SAaron LI 		LIST_INIT(&rl->rl_table[i]);
356a6bca3d2SAaron LI 	rl->rl_table_num = 0;
357a6bca3d2SAaron LI 	rl->rl_initialized = true;
358a6bca3d2SAaron LI }
359a6bca3d2SAaron LI 
360a6bca3d2SAaron LI static void
361a6bca3d2SAaron LI ratelimit_deinit(struct ratelimit *rl)
362a6bca3d2SAaron LI {
363a6bca3d2SAaron LI 	if (!rl->rl_initialized)
364a6bca3d2SAaron LI 		return;
365139109cdSAaron LI 	lockmgr(&rl->rl_mtx, LK_EXCLUSIVE);
366a6bca3d2SAaron LI 	callout_stop(&rl->rl_gc);
367a6bca3d2SAaron LI 	ratelimit_gc(rl, true);
368139109cdSAaron LI 	lockmgr(&rl->rl_mtx, LK_RELEASE);
369139109cdSAaron LI 	lockuninit(&rl->rl_mtx);
370a6bca3d2SAaron LI 
371a6bca3d2SAaron LI 	rl->rl_initialized = false;
372a6bca3d2SAaron LI }
373a6bca3d2SAaron LI 
374a6bca3d2SAaron LI static void
375a6bca3d2SAaron LI ratelimit_gc_callout(void *_rl)
376a6bca3d2SAaron LI {
377a6bca3d2SAaron LI 	/* callout will lock rl_mtx for us */
378a6bca3d2SAaron LI 	ratelimit_gc(_rl, false);
379a6bca3d2SAaron LI }
380a6bca3d2SAaron LI 
381a6bca3d2SAaron LI static void
382a6bca3d2SAaron LI ratelimit_gc_schedule(struct ratelimit *rl)
383a6bca3d2SAaron LI {
384a6bca3d2SAaron LI 	/* Trigger another GC if needed. There is no point calling GC if there
385a6bca3d2SAaron LI 	 * are no entries in the table. We also want to ensure that GC occurs
386a6bca3d2SAaron LI 	 * on a regular interval, so don't override a currently pending GC.
387a6bca3d2SAaron LI 	 *
388a6bca3d2SAaron LI 	 * In the case of a forced ratelimit_gc, there will be no entries left
389a6bca3d2SAaron LI 	 * so we will will not schedule another GC. */
390a6bca3d2SAaron LI 	if (rl->rl_table_num > 0 && !callout_pending(&rl->rl_gc))
391a6bca3d2SAaron LI 		callout_reset(&rl->rl_gc, ELEMENT_TIMEOUT * hz,
392a6bca3d2SAaron LI 		    ratelimit_gc_callout, rl);
393a6bca3d2SAaron LI }
394a6bca3d2SAaron LI 
395a6bca3d2SAaron LI static void
396a6bca3d2SAaron LI ratelimit_gc(struct ratelimit *rl, bool force)
397a6bca3d2SAaron LI {
398a6bca3d2SAaron LI 	size_t i;
399a6bca3d2SAaron LI 	struct ratelimit_entry *r, *tr;
400a6bca3d2SAaron LI 	sbintime_t expiry;
401a6bca3d2SAaron LI 
402139109cdSAaron LI 	KKASSERT(lockstatus(&rl->rl_mtx, curthread) == LK_EXCLUSIVE);
403a6bca3d2SAaron LI 
404a6bca3d2SAaron LI 	if (rl->rl_table_num == 0)
405a6bca3d2SAaron LI 		return;
406a6bca3d2SAaron LI 
407a6bca3d2SAaron LI 	expiry = getsbinuptime() - ELEMENT_TIMEOUT * SBT_1S;
408a6bca3d2SAaron LI 
409a6bca3d2SAaron LI 	for (i = 0; i < RATELIMIT_SIZE; i++) {
410*1fdfe5a9SAaron LI 		LIST_FOREACH_MUTABLE(r, &rl->rl_table[i], r_entry, tr) {
411a6bca3d2SAaron LI 			if (r->r_last_time < expiry || force) {
412a6bca3d2SAaron LI 				rl->rl_table_num--;
413a6bca3d2SAaron LI 				LIST_REMOVE(r, r_entry);
414be332237SAaron LI 				objcache_put(ratelimit_zone, r);
415a6bca3d2SAaron LI 			}
416a6bca3d2SAaron LI 		}
417a6bca3d2SAaron LI 	}
418a6bca3d2SAaron LI 
419a6bca3d2SAaron LI 	ratelimit_gc_schedule(rl);
420a6bca3d2SAaron LI }
421a6bca3d2SAaron LI 
422a6bca3d2SAaron LI static int
423a6bca3d2SAaron LI ratelimit_allow(struct ratelimit *rl, struct sockaddr *sa, struct vnet *vnet)
424a6bca3d2SAaron LI {
425a6bca3d2SAaron LI 	uint64_t bucket, tokens;
426a6bca3d2SAaron LI 	sbintime_t diff, now;
427a6bca3d2SAaron LI 	struct ratelimit_entry *r;
428a6bca3d2SAaron LI 	int ret = ECONNREFUSED;
429a6bca3d2SAaron LI 	struct ratelimit_key key = { .vnet = vnet };
430a6bca3d2SAaron LI 	size_t len = sizeof(key);
431a6bca3d2SAaron LI 
432a6bca3d2SAaron LI 	if (sa->sa_family == AF_INET) {
433a6bca3d2SAaron LI 		memcpy(key.ip, &satosin(sa)->sin_addr, IPV4_MASK_SIZE);
434a6bca3d2SAaron LI 		len -= IPV6_MASK_SIZE - IPV4_MASK_SIZE;
435a6bca3d2SAaron LI 	}
436a6bca3d2SAaron LI #ifdef INET6
437a6bca3d2SAaron LI 	else if (sa->sa_family == AF_INET6)
438a6bca3d2SAaron LI 		memcpy(key.ip, &satosin6(sa)->sin6_addr, IPV6_MASK_SIZE);
439a6bca3d2SAaron LI #endif
440a6bca3d2SAaron LI 	else
441a6bca3d2SAaron LI 		return ret;
442a6bca3d2SAaron LI 
443a6bca3d2SAaron LI 	bucket = siphash13(rl->rl_secret, &key, len) & RATELIMIT_MASK;
444139109cdSAaron LI 	lockmgr(&rl->rl_mtx, LK_EXCLUSIVE);
445a6bca3d2SAaron LI 
446a6bca3d2SAaron LI 	LIST_FOREACH(r, &rl->rl_table[bucket], r_entry) {
447a6bca3d2SAaron LI 		if (bcmp(&r->r_key, &key, len) != 0)
448a6bca3d2SAaron LI 			continue;
449a6bca3d2SAaron LI 
450a6bca3d2SAaron LI 		/* If we get to here, we've found an entry for the endpoint.
451a6bca3d2SAaron LI 		 * We apply standard token bucket, by calculating the time
452a6bca3d2SAaron LI 		 * lapsed since our last_time, adding that, ensuring that we
453a6bca3d2SAaron LI 		 * cap the tokens at TOKEN_MAX. If the endpoint has no tokens
454a6bca3d2SAaron LI 		 * left (that is tokens <= INITIATION_COST) then we block the
455a6bca3d2SAaron LI 		 * request, otherwise we subtract the INITITIATION_COST and
456a6bca3d2SAaron LI 		 * return OK. */
457a6bca3d2SAaron LI 		now = getsbinuptime();
458a6bca3d2SAaron LI 		diff = now - r->r_last_time;
459a6bca3d2SAaron LI 		r->r_last_time = now;
460a6bca3d2SAaron LI 
461a6bca3d2SAaron LI 		tokens = r->r_tokens + diff;
462a6bca3d2SAaron LI 
463a6bca3d2SAaron LI 		if (tokens > TOKEN_MAX)
464a6bca3d2SAaron LI 			tokens = TOKEN_MAX;
465a6bca3d2SAaron LI 
466a6bca3d2SAaron LI 		if (tokens >= INITIATION_COST) {
467a6bca3d2SAaron LI 			r->r_tokens = tokens - INITIATION_COST;
468a6bca3d2SAaron LI 			goto ok;
469a6bca3d2SAaron LI 		} else {
470a6bca3d2SAaron LI 			r->r_tokens = tokens;
471a6bca3d2SAaron LI 			goto error;
472a6bca3d2SAaron LI 		}
473a6bca3d2SAaron LI 	}
474a6bca3d2SAaron LI 
475a6bca3d2SAaron LI 	/* If we get to here, we didn't have an entry for the endpoint, let's
476a6bca3d2SAaron LI 	 * add one if we have space. */
477a6bca3d2SAaron LI 	if (rl->rl_table_num >= RATELIMIT_SIZE_MAX)
478a6bca3d2SAaron LI 		goto error;
479a6bca3d2SAaron LI 
480a6bca3d2SAaron LI 	/* Goto error if out of memory */
481be332237SAaron LI 	if ((r = objcache_get(ratelimit_zone, M_NOWAIT)) == NULL)
482a6bca3d2SAaron LI 		goto error;
483be332237SAaron LI 	bzero(r, sizeof(*r)); /* objcache_get() doesn't ensure M_ZERO. */
484a6bca3d2SAaron LI 
485a6bca3d2SAaron LI 	rl->rl_table_num++;
486a6bca3d2SAaron LI 
487a6bca3d2SAaron LI 	/* Insert entry into the hashtable and ensure it's initialised */
488a6bca3d2SAaron LI 	LIST_INSERT_HEAD(&rl->rl_table[bucket], r, r_entry);
489a6bca3d2SAaron LI 	r->r_key = key;
490a6bca3d2SAaron LI 	r->r_last_time = getsbinuptime();
491a6bca3d2SAaron LI 	r->r_tokens = TOKEN_MAX - INITIATION_COST;
492a6bca3d2SAaron LI 
493a6bca3d2SAaron LI 	/* If we've added a new entry, let's trigger GC. */
494a6bca3d2SAaron LI 	ratelimit_gc_schedule(rl);
495a6bca3d2SAaron LI ok:
496a6bca3d2SAaron LI 	ret = 0;
497a6bca3d2SAaron LI error:
498139109cdSAaron LI 	lockmgr(&rl->rl_mtx, LK_RELEASE);
499a6bca3d2SAaron LI 	return ret;
500a6bca3d2SAaron LI }
501a6bca3d2SAaron LI 
502a6bca3d2SAaron LI static uint64_t siphash13(const uint8_t key[SIPHASH_KEY_LENGTH], const void *src, size_t len)
503a6bca3d2SAaron LI {
504a6bca3d2SAaron LI 	SIPHASH_CTX ctx;
505a6bca3d2SAaron LI 	return (SipHashX(&ctx, 1, 3, key, src, len));
506a6bca3d2SAaron LI }
507a6bca3d2SAaron LI 
508a6bca3d2SAaron LI #ifdef SELFTESTS
509a6bca3d2SAaron LI #include "selftest/cookie.c"
510a6bca3d2SAaron LI #endif /* SELFTESTS */
511