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> 14a6bca3d2SAaron LI #include <sys/mutex.h> 15a6bca3d2SAaron LI #include <sys/socket.h> 16a6bca3d2SAaron LI #include <crypto/siphash/siphash.h> 17a6bca3d2SAaron LI #include <netinet/in.h> 18a6bca3d2SAaron LI #include <vm/uma.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]; 53a6bca3d2SAaron LI struct mtx 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 81a6bca3d2SAaron LI static uma_zone_t ratelimit_zone; 82a6bca3d2SAaron LI 83a6bca3d2SAaron LI /* Public Functions */ 84a6bca3d2SAaron LI int 85a6bca3d2SAaron LI cookie_init(void) 86a6bca3d2SAaron LI { 87a6bca3d2SAaron LI if ((ratelimit_zone = uma_zcreate("wg ratelimit", 88a6bca3d2SAaron LI sizeof(struct ratelimit_entry), NULL, NULL, NULL, NULL, 0, 0)) == NULL) 89a6bca3d2SAaron LI return ENOMEM; 90a6bca3d2SAaron LI 91a6bca3d2SAaron LI ratelimit_init(&ratelimit_v4); 92a6bca3d2SAaron LI #ifdef INET6 93a6bca3d2SAaron LI ratelimit_init(&ratelimit_v6); 94a6bca3d2SAaron LI #endif 95a6bca3d2SAaron LI return (0); 96a6bca3d2SAaron LI } 97a6bca3d2SAaron LI 98a6bca3d2SAaron LI void 99a6bca3d2SAaron LI cookie_deinit(void) 100a6bca3d2SAaron LI { 101a6bca3d2SAaron LI ratelimit_deinit(&ratelimit_v4); 102a6bca3d2SAaron LI #ifdef INET6 103a6bca3d2SAaron LI ratelimit_deinit(&ratelimit_v6); 104a6bca3d2SAaron LI #endif 105a6bca3d2SAaron LI if (ratelimit_zone != NULL) 106a6bca3d2SAaron LI uma_zdestroy(ratelimit_zone); 107a6bca3d2SAaron LI } 108a6bca3d2SAaron LI 109a6bca3d2SAaron LI void 110a6bca3d2SAaron LI cookie_checker_init(struct cookie_checker *cc) 111a6bca3d2SAaron LI { 112a6bca3d2SAaron LI bzero(cc, sizeof(*cc)); 113a6bca3d2SAaron LI 114*7ef217feSAaron LI lockinit(&cc->cc_key_lock, "cookie_checker_key", 0, 0); 115a6bca3d2SAaron LI mtx_init(&cc->cc_secret_mtx, "cookie_checker_secret", NULL, MTX_DEF); 116a6bca3d2SAaron LI } 117a6bca3d2SAaron LI 118a6bca3d2SAaron LI void 119a6bca3d2SAaron LI cookie_checker_free(struct cookie_checker *cc) 120a6bca3d2SAaron LI { 121*7ef217feSAaron LI lockuninit(&cc->cc_key_lock); 122a6bca3d2SAaron LI mtx_destroy(&cc->cc_secret_mtx); 123a6bca3d2SAaron LI explicit_bzero(cc, sizeof(*cc)); 124a6bca3d2SAaron LI } 125a6bca3d2SAaron LI 126a6bca3d2SAaron LI void 127a6bca3d2SAaron LI cookie_checker_update(struct cookie_checker *cc, 128a6bca3d2SAaron LI const uint8_t key[COOKIE_INPUT_SIZE]) 129a6bca3d2SAaron LI { 130*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_EXCLUSIVE); 131a6bca3d2SAaron LI if (key) { 132a6bca3d2SAaron LI precompute_key(cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL); 133a6bca3d2SAaron LI precompute_key(cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL); 134a6bca3d2SAaron LI } else { 135a6bca3d2SAaron LI bzero(cc->cc_mac1_key, sizeof(cc->cc_mac1_key)); 136a6bca3d2SAaron LI bzero(cc->cc_cookie_key, sizeof(cc->cc_cookie_key)); 137a6bca3d2SAaron LI } 138*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_RELEASE); 139a6bca3d2SAaron LI } 140a6bca3d2SAaron LI 141a6bca3d2SAaron LI void 142a6bca3d2SAaron LI cookie_checker_create_payload(struct cookie_checker *cc, 143a6bca3d2SAaron LI struct cookie_macs *macs, uint8_t nonce[COOKIE_NONCE_SIZE], 144a6bca3d2SAaron LI uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], struct sockaddr *sa) 145a6bca3d2SAaron LI { 146a6bca3d2SAaron LI uint8_t cookie[COOKIE_COOKIE_SIZE]; 147a6bca3d2SAaron LI 148a6bca3d2SAaron LI make_cookie(cc, cookie, sa); 149a6bca3d2SAaron LI arc4random_buf(nonce, COOKIE_NONCE_SIZE); 150a6bca3d2SAaron LI 151*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_SHARED); 152a6bca3d2SAaron LI xchacha20poly1305_encrypt(ecookie, cookie, COOKIE_COOKIE_SIZE, 153a6bca3d2SAaron LI macs->mac1, COOKIE_MAC_SIZE, nonce, cc->cc_cookie_key); 154*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_RELEASE); 155a6bca3d2SAaron LI 156a6bca3d2SAaron LI explicit_bzero(cookie, sizeof(cookie)); 157a6bca3d2SAaron LI } 158a6bca3d2SAaron LI 159a6bca3d2SAaron LI void 160a6bca3d2SAaron LI cookie_maker_init(struct cookie_maker *cm, const uint8_t key[COOKIE_INPUT_SIZE]) 161a6bca3d2SAaron LI { 162a6bca3d2SAaron LI bzero(cm, sizeof(*cm)); 163a6bca3d2SAaron LI precompute_key(cm->cm_mac1_key, key, COOKIE_MAC1_KEY_LABEL); 164a6bca3d2SAaron LI precompute_key(cm->cm_cookie_key, key, COOKIE_COOKIE_KEY_LABEL); 165*7ef217feSAaron LI lockinit(&cm->cm_lock, "cookie_maker", 0, 0); 166a6bca3d2SAaron LI } 167a6bca3d2SAaron LI 168a6bca3d2SAaron LI void 169a6bca3d2SAaron LI cookie_maker_free(struct cookie_maker *cm) 170a6bca3d2SAaron LI { 171*7ef217feSAaron LI lockuninit(&cm->cm_lock); 172a6bca3d2SAaron LI explicit_bzero(cm, sizeof(*cm)); 173a6bca3d2SAaron LI } 174a6bca3d2SAaron LI 175a6bca3d2SAaron LI int 176a6bca3d2SAaron LI cookie_maker_consume_payload(struct cookie_maker *cm, 177a6bca3d2SAaron LI uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE]) 178a6bca3d2SAaron LI { 179a6bca3d2SAaron LI uint8_t cookie[COOKIE_COOKIE_SIZE]; 180a6bca3d2SAaron LI int ret; 181a6bca3d2SAaron LI 182*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_SHARED); 183a6bca3d2SAaron LI if (!cm->cm_mac1_sent) { 184a6bca3d2SAaron LI ret = ETIMEDOUT; 185a6bca3d2SAaron LI goto error; 186a6bca3d2SAaron LI } 187a6bca3d2SAaron LI 188a6bca3d2SAaron LI if (!xchacha20poly1305_decrypt(cookie, ecookie, COOKIE_ENCRYPTED_SIZE, 189a6bca3d2SAaron LI cm->cm_mac1_last, COOKIE_MAC_SIZE, nonce, cm->cm_cookie_key)) { 190a6bca3d2SAaron LI ret = EINVAL; 191a6bca3d2SAaron LI goto error; 192a6bca3d2SAaron LI } 193*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_RELEASE); 194a6bca3d2SAaron LI 195*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_EXCLUSIVE); 196a6bca3d2SAaron LI memcpy(cm->cm_cookie, cookie, COOKIE_COOKIE_SIZE); 197a6bca3d2SAaron LI cm->cm_cookie_birthdate = getsbinuptime(); 198a6bca3d2SAaron LI cm->cm_cookie_valid = true; 199a6bca3d2SAaron LI cm->cm_mac1_sent = false; 200*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_RELEASE); 201a6bca3d2SAaron LI 202a6bca3d2SAaron LI return 0; 203a6bca3d2SAaron LI error: 204*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_RELEASE); 205a6bca3d2SAaron LI return ret; 206a6bca3d2SAaron LI } 207a6bca3d2SAaron LI 208a6bca3d2SAaron LI void 209a6bca3d2SAaron LI cookie_maker_mac(struct cookie_maker *cm, struct cookie_macs *macs, void *buf, 210a6bca3d2SAaron LI size_t len) 211a6bca3d2SAaron LI { 212*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_EXCLUSIVE); 213a6bca3d2SAaron LI macs_mac1(macs, buf, len, cm->cm_mac1_key); 214a6bca3d2SAaron LI memcpy(cm->cm_mac1_last, macs->mac1, COOKIE_MAC_SIZE); 215a6bca3d2SAaron LI cm->cm_mac1_sent = true; 216a6bca3d2SAaron LI 217a6bca3d2SAaron LI if (cm->cm_cookie_valid && 218a6bca3d2SAaron LI !timer_expired(cm->cm_cookie_birthdate, 219a6bca3d2SAaron LI COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY, 0)) { 220a6bca3d2SAaron LI macs_mac2(macs, buf, len, cm->cm_cookie); 221a6bca3d2SAaron LI } else { 222a6bca3d2SAaron LI bzero(macs->mac2, COOKIE_MAC_SIZE); 223a6bca3d2SAaron LI cm->cm_cookie_valid = false; 224a6bca3d2SAaron LI } 225*7ef217feSAaron LI lockmgr(&cm->cm_lock, LK_RELEASE); 226a6bca3d2SAaron LI } 227a6bca3d2SAaron LI 228a6bca3d2SAaron LI int 229a6bca3d2SAaron LI cookie_checker_validate_macs(struct cookie_checker *cc, struct cookie_macs *macs, 230a6bca3d2SAaron LI void *buf, size_t len, bool check_cookie, struct sockaddr *sa, struct vnet *vnet) 231a6bca3d2SAaron LI { 232a6bca3d2SAaron LI struct cookie_macs our_macs; 233a6bca3d2SAaron LI uint8_t cookie[COOKIE_COOKIE_SIZE]; 234a6bca3d2SAaron LI 235a6bca3d2SAaron LI /* Validate incoming MACs */ 236*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_SHARED); 237a6bca3d2SAaron LI macs_mac1(&our_macs, buf, len, cc->cc_mac1_key); 238*7ef217feSAaron LI lockmgr(&cc->cc_key_lock, LK_RELEASE); 239a6bca3d2SAaron LI 240a6bca3d2SAaron LI /* If mac1 is invald, we want to drop the packet */ 241a6bca3d2SAaron LI if (timingsafe_bcmp(our_macs.mac1, macs->mac1, COOKIE_MAC_SIZE) != 0) 242a6bca3d2SAaron LI return EINVAL; 243a6bca3d2SAaron LI 244a6bca3d2SAaron LI if (check_cookie) { 245a6bca3d2SAaron LI make_cookie(cc, cookie, sa); 246a6bca3d2SAaron LI macs_mac2(&our_macs, buf, len, cookie); 247a6bca3d2SAaron LI 248a6bca3d2SAaron LI /* If the mac2 is invalid, we want to send a cookie response */ 249a6bca3d2SAaron LI if (timingsafe_bcmp(our_macs.mac2, macs->mac2, COOKIE_MAC_SIZE) != 0) 250a6bca3d2SAaron LI return EAGAIN; 251a6bca3d2SAaron LI 252a6bca3d2SAaron LI /* If the mac2 is valid, we may want rate limit the peer. 253a6bca3d2SAaron LI * ratelimit_allow will return either 0 or ECONNREFUSED, 254a6bca3d2SAaron LI * implying there is no ratelimiting, or we should ratelimit 255a6bca3d2SAaron LI * (refuse) respectively. */ 256a6bca3d2SAaron LI if (sa->sa_family == AF_INET) 257a6bca3d2SAaron LI return ratelimit_allow(&ratelimit_v4, sa, vnet); 258a6bca3d2SAaron LI #ifdef INET6 259a6bca3d2SAaron LI else if (sa->sa_family == AF_INET6) 260a6bca3d2SAaron LI return ratelimit_allow(&ratelimit_v6, sa, vnet); 261a6bca3d2SAaron LI #endif 262a6bca3d2SAaron LI else 263a6bca3d2SAaron LI return EAFNOSUPPORT; 264a6bca3d2SAaron LI } 265a6bca3d2SAaron LI 266a6bca3d2SAaron LI return 0; 267a6bca3d2SAaron LI } 268a6bca3d2SAaron LI 269a6bca3d2SAaron LI /* Private functions */ 270a6bca3d2SAaron LI static void 271a6bca3d2SAaron LI precompute_key(uint8_t *key, const uint8_t input[COOKIE_INPUT_SIZE], 272a6bca3d2SAaron LI const char *label) 273a6bca3d2SAaron LI { 274a6bca3d2SAaron LI struct blake2s_state blake; 275a6bca3d2SAaron LI blake2s_init(&blake, COOKIE_KEY_SIZE); 276a6bca3d2SAaron LI blake2s_update(&blake, label, strlen(label)); 277a6bca3d2SAaron LI blake2s_update(&blake, input, COOKIE_INPUT_SIZE); 278a6bca3d2SAaron LI blake2s_final(&blake, key); 279a6bca3d2SAaron LI } 280a6bca3d2SAaron LI 281a6bca3d2SAaron LI static void 282a6bca3d2SAaron LI macs_mac1(struct cookie_macs *macs, const void *buf, size_t len, 283a6bca3d2SAaron LI const uint8_t key[COOKIE_KEY_SIZE]) 284a6bca3d2SAaron LI { 285a6bca3d2SAaron LI struct blake2s_state state; 286a6bca3d2SAaron LI blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE); 287a6bca3d2SAaron LI blake2s_update(&state, buf, len); 288a6bca3d2SAaron LI blake2s_final(&state, macs->mac1); 289a6bca3d2SAaron LI } 290a6bca3d2SAaron LI 291a6bca3d2SAaron LI static void 292a6bca3d2SAaron LI macs_mac2(struct cookie_macs *macs, const void *buf, size_t len, 293a6bca3d2SAaron LI const uint8_t key[COOKIE_COOKIE_SIZE]) 294a6bca3d2SAaron LI { 295a6bca3d2SAaron LI struct blake2s_state state; 296a6bca3d2SAaron LI blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE); 297a6bca3d2SAaron LI blake2s_update(&state, buf, len); 298a6bca3d2SAaron LI blake2s_update(&state, macs->mac1, COOKIE_MAC_SIZE); 299a6bca3d2SAaron LI blake2s_final(&state, macs->mac2); 300a6bca3d2SAaron LI } 301a6bca3d2SAaron LI 302a6bca3d2SAaron LI static __inline int 303a6bca3d2SAaron LI timer_expired(sbintime_t timer, uint32_t sec, uint32_t nsec) 304a6bca3d2SAaron LI { 305a6bca3d2SAaron LI sbintime_t now = getsbinuptime(); 306a6bca3d2SAaron LI return (now > (timer + sec * SBT_1S + nstosbt(nsec))) ? ETIMEDOUT : 0; 307a6bca3d2SAaron LI } 308a6bca3d2SAaron LI 309a6bca3d2SAaron LI static void 310a6bca3d2SAaron LI make_cookie(struct cookie_checker *cc, uint8_t cookie[COOKIE_COOKIE_SIZE], 311a6bca3d2SAaron LI struct sockaddr *sa) 312a6bca3d2SAaron LI { 313a6bca3d2SAaron LI struct blake2s_state state; 314a6bca3d2SAaron LI 315a6bca3d2SAaron LI mtx_lock(&cc->cc_secret_mtx); 316a6bca3d2SAaron LI if (timer_expired(cc->cc_secret_birthdate, 317a6bca3d2SAaron LI COOKIE_SECRET_MAX_AGE, 0)) { 318a6bca3d2SAaron LI arc4random_buf(cc->cc_secret, COOKIE_SECRET_SIZE); 319a6bca3d2SAaron LI cc->cc_secret_birthdate = getsbinuptime(); 320a6bca3d2SAaron LI } 321a6bca3d2SAaron LI blake2s_init_key(&state, COOKIE_COOKIE_SIZE, cc->cc_secret, 322a6bca3d2SAaron LI COOKIE_SECRET_SIZE); 323a6bca3d2SAaron LI mtx_unlock(&cc->cc_secret_mtx); 324a6bca3d2SAaron LI 325a6bca3d2SAaron LI if (sa->sa_family == AF_INET) { 326a6bca3d2SAaron LI blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_addr, 327a6bca3d2SAaron LI sizeof(struct in_addr)); 328a6bca3d2SAaron LI blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_port, 329a6bca3d2SAaron LI sizeof(in_port_t)); 330a6bca3d2SAaron LI blake2s_final(&state, cookie); 331a6bca3d2SAaron LI #ifdef INET6 332a6bca3d2SAaron LI } else if (sa->sa_family == AF_INET6) { 333a6bca3d2SAaron LI blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_addr, 334a6bca3d2SAaron LI sizeof(struct in6_addr)); 335a6bca3d2SAaron LI blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_port, 336a6bca3d2SAaron LI sizeof(in_port_t)); 337a6bca3d2SAaron LI blake2s_final(&state, cookie); 338a6bca3d2SAaron LI #endif 339a6bca3d2SAaron LI } else { 340a6bca3d2SAaron LI arc4random_buf(cookie, COOKIE_COOKIE_SIZE); 341a6bca3d2SAaron LI } 342a6bca3d2SAaron LI } 343a6bca3d2SAaron LI 344a6bca3d2SAaron LI static void 345a6bca3d2SAaron LI ratelimit_init(struct ratelimit *rl) 346a6bca3d2SAaron LI { 347a6bca3d2SAaron LI size_t i; 348a6bca3d2SAaron LI mtx_init(&rl->rl_mtx, "ratelimit_lock", NULL, MTX_DEF); 349a6bca3d2SAaron LI callout_init_mtx(&rl->rl_gc, &rl->rl_mtx, 0); 350a6bca3d2SAaron LI arc4random_buf(rl->rl_secret, sizeof(rl->rl_secret)); 351a6bca3d2SAaron LI for (i = 0; i < RATELIMIT_SIZE; i++) 352a6bca3d2SAaron LI LIST_INIT(&rl->rl_table[i]); 353a6bca3d2SAaron LI rl->rl_table_num = 0; 354a6bca3d2SAaron LI rl->rl_initialized = true; 355a6bca3d2SAaron LI } 356a6bca3d2SAaron LI 357a6bca3d2SAaron LI static void 358a6bca3d2SAaron LI ratelimit_deinit(struct ratelimit *rl) 359a6bca3d2SAaron LI { 360a6bca3d2SAaron LI if (!rl->rl_initialized) 361a6bca3d2SAaron LI return; 362a6bca3d2SAaron LI mtx_lock(&rl->rl_mtx); 363a6bca3d2SAaron LI callout_stop(&rl->rl_gc); 364a6bca3d2SAaron LI ratelimit_gc(rl, true); 365a6bca3d2SAaron LI mtx_unlock(&rl->rl_mtx); 366a6bca3d2SAaron LI mtx_destroy(&rl->rl_mtx); 367a6bca3d2SAaron LI 368a6bca3d2SAaron LI rl->rl_initialized = false; 369a6bca3d2SAaron LI } 370a6bca3d2SAaron LI 371a6bca3d2SAaron LI static void 372a6bca3d2SAaron LI ratelimit_gc_callout(void *_rl) 373a6bca3d2SAaron LI { 374a6bca3d2SAaron LI /* callout will lock rl_mtx for us */ 375a6bca3d2SAaron LI ratelimit_gc(_rl, false); 376a6bca3d2SAaron LI } 377a6bca3d2SAaron LI 378a6bca3d2SAaron LI static void 379a6bca3d2SAaron LI ratelimit_gc_schedule(struct ratelimit *rl) 380a6bca3d2SAaron LI { 381a6bca3d2SAaron LI /* Trigger another GC if needed. There is no point calling GC if there 382a6bca3d2SAaron LI * are no entries in the table. We also want to ensure that GC occurs 383a6bca3d2SAaron LI * on a regular interval, so don't override a currently pending GC. 384a6bca3d2SAaron LI * 385a6bca3d2SAaron LI * In the case of a forced ratelimit_gc, there will be no entries left 386a6bca3d2SAaron LI * so we will will not schedule another GC. */ 387a6bca3d2SAaron LI if (rl->rl_table_num > 0 && !callout_pending(&rl->rl_gc)) 388a6bca3d2SAaron LI callout_reset(&rl->rl_gc, ELEMENT_TIMEOUT * hz, 389a6bca3d2SAaron LI ratelimit_gc_callout, rl); 390a6bca3d2SAaron LI } 391a6bca3d2SAaron LI 392a6bca3d2SAaron LI static void 393a6bca3d2SAaron LI ratelimit_gc(struct ratelimit *rl, bool force) 394a6bca3d2SAaron LI { 395a6bca3d2SAaron LI size_t i; 396a6bca3d2SAaron LI struct ratelimit_entry *r, *tr; 397a6bca3d2SAaron LI sbintime_t expiry; 398a6bca3d2SAaron LI 399a6bca3d2SAaron LI mtx_assert(&rl->rl_mtx, MA_OWNED); 400a6bca3d2SAaron LI 401a6bca3d2SAaron LI if (rl->rl_table_num == 0) 402a6bca3d2SAaron LI return; 403a6bca3d2SAaron LI 404a6bca3d2SAaron LI expiry = getsbinuptime() - ELEMENT_TIMEOUT * SBT_1S; 405a6bca3d2SAaron LI 406a6bca3d2SAaron LI for (i = 0; i < RATELIMIT_SIZE; i++) { 407a6bca3d2SAaron LI LIST_FOREACH_SAFE(r, &rl->rl_table[i], r_entry, tr) { 408a6bca3d2SAaron LI if (r->r_last_time < expiry || force) { 409a6bca3d2SAaron LI rl->rl_table_num--; 410a6bca3d2SAaron LI LIST_REMOVE(r, r_entry); 411a6bca3d2SAaron LI uma_zfree(ratelimit_zone, r); 412a6bca3d2SAaron LI } 413a6bca3d2SAaron LI } 414a6bca3d2SAaron LI } 415a6bca3d2SAaron LI 416a6bca3d2SAaron LI ratelimit_gc_schedule(rl); 417a6bca3d2SAaron LI } 418a6bca3d2SAaron LI 419a6bca3d2SAaron LI static int 420a6bca3d2SAaron LI ratelimit_allow(struct ratelimit *rl, struct sockaddr *sa, struct vnet *vnet) 421a6bca3d2SAaron LI { 422a6bca3d2SAaron LI uint64_t bucket, tokens; 423a6bca3d2SAaron LI sbintime_t diff, now; 424a6bca3d2SAaron LI struct ratelimit_entry *r; 425a6bca3d2SAaron LI int ret = ECONNREFUSED; 426a6bca3d2SAaron LI struct ratelimit_key key = { .vnet = vnet }; 427a6bca3d2SAaron LI size_t len = sizeof(key); 428a6bca3d2SAaron LI 429a6bca3d2SAaron LI if (sa->sa_family == AF_INET) { 430a6bca3d2SAaron LI memcpy(key.ip, &satosin(sa)->sin_addr, IPV4_MASK_SIZE); 431a6bca3d2SAaron LI len -= IPV6_MASK_SIZE - IPV4_MASK_SIZE; 432a6bca3d2SAaron LI } 433a6bca3d2SAaron LI #ifdef INET6 434a6bca3d2SAaron LI else if (sa->sa_family == AF_INET6) 435a6bca3d2SAaron LI memcpy(key.ip, &satosin6(sa)->sin6_addr, IPV6_MASK_SIZE); 436a6bca3d2SAaron LI #endif 437a6bca3d2SAaron LI else 438a6bca3d2SAaron LI return ret; 439a6bca3d2SAaron LI 440a6bca3d2SAaron LI bucket = siphash13(rl->rl_secret, &key, len) & RATELIMIT_MASK; 441a6bca3d2SAaron LI mtx_lock(&rl->rl_mtx); 442a6bca3d2SAaron LI 443a6bca3d2SAaron LI LIST_FOREACH(r, &rl->rl_table[bucket], r_entry) { 444a6bca3d2SAaron LI if (bcmp(&r->r_key, &key, len) != 0) 445a6bca3d2SAaron LI continue; 446a6bca3d2SAaron LI 447a6bca3d2SAaron LI /* If we get to here, we've found an entry for the endpoint. 448a6bca3d2SAaron LI * We apply standard token bucket, by calculating the time 449a6bca3d2SAaron LI * lapsed since our last_time, adding that, ensuring that we 450a6bca3d2SAaron LI * cap the tokens at TOKEN_MAX. If the endpoint has no tokens 451a6bca3d2SAaron LI * left (that is tokens <= INITIATION_COST) then we block the 452a6bca3d2SAaron LI * request, otherwise we subtract the INITITIATION_COST and 453a6bca3d2SAaron LI * return OK. */ 454a6bca3d2SAaron LI now = getsbinuptime(); 455a6bca3d2SAaron LI diff = now - r->r_last_time; 456a6bca3d2SAaron LI r->r_last_time = now; 457a6bca3d2SAaron LI 458a6bca3d2SAaron LI tokens = r->r_tokens + diff; 459a6bca3d2SAaron LI 460a6bca3d2SAaron LI if (tokens > TOKEN_MAX) 461a6bca3d2SAaron LI tokens = TOKEN_MAX; 462a6bca3d2SAaron LI 463a6bca3d2SAaron LI if (tokens >= INITIATION_COST) { 464a6bca3d2SAaron LI r->r_tokens = tokens - INITIATION_COST; 465a6bca3d2SAaron LI goto ok; 466a6bca3d2SAaron LI } else { 467a6bca3d2SAaron LI r->r_tokens = tokens; 468a6bca3d2SAaron LI goto error; 469a6bca3d2SAaron LI } 470a6bca3d2SAaron LI } 471a6bca3d2SAaron LI 472a6bca3d2SAaron LI /* If we get to here, we didn't have an entry for the endpoint, let's 473a6bca3d2SAaron LI * add one if we have space. */ 474a6bca3d2SAaron LI if (rl->rl_table_num >= RATELIMIT_SIZE_MAX) 475a6bca3d2SAaron LI goto error; 476a6bca3d2SAaron LI 477a6bca3d2SAaron LI /* Goto error if out of memory */ 478a6bca3d2SAaron LI if ((r = uma_zalloc(ratelimit_zone, M_NOWAIT | M_ZERO)) == NULL) 479a6bca3d2SAaron LI goto error; 480a6bca3d2SAaron LI 481a6bca3d2SAaron LI rl->rl_table_num++; 482a6bca3d2SAaron LI 483a6bca3d2SAaron LI /* Insert entry into the hashtable and ensure it's initialised */ 484a6bca3d2SAaron LI LIST_INSERT_HEAD(&rl->rl_table[bucket], r, r_entry); 485a6bca3d2SAaron LI r->r_key = key; 486a6bca3d2SAaron LI r->r_last_time = getsbinuptime(); 487a6bca3d2SAaron LI r->r_tokens = TOKEN_MAX - INITIATION_COST; 488a6bca3d2SAaron LI 489a6bca3d2SAaron LI /* If we've added a new entry, let's trigger GC. */ 490a6bca3d2SAaron LI ratelimit_gc_schedule(rl); 491a6bca3d2SAaron LI ok: 492a6bca3d2SAaron LI ret = 0; 493a6bca3d2SAaron LI error: 494a6bca3d2SAaron LI mtx_unlock(&rl->rl_mtx); 495a6bca3d2SAaron LI return ret; 496a6bca3d2SAaron LI } 497a6bca3d2SAaron LI 498a6bca3d2SAaron LI static uint64_t siphash13(const uint8_t key[SIPHASH_KEY_LENGTH], const void *src, size_t len) 499a6bca3d2SAaron LI { 500a6bca3d2SAaron LI SIPHASH_CTX ctx; 501a6bca3d2SAaron LI return (SipHashX(&ctx, 1, 3, key, src, len)); 502a6bca3d2SAaron LI } 503a6bca3d2SAaron LI 504a6bca3d2SAaron LI #ifdef SELFTESTS 505a6bca3d2SAaron LI #include "selftest/cookie.c" 506a6bca3d2SAaron LI #endif /* SELFTESTS */ 507