summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatt Dunwoodie <ncon@mail.noconroy.net>2019-08-22 20:02:14 +1000
committerMatt Dunwoodie <ncon@mail.noconroy.net>2019-08-22 21:52:16 +1000
commit0f81dae44d741a3ce2c6d1a59413c3703612b5f0 (patch)
tree14c3959e21414b0ac46b7761fc78bc2adb5eb6ef /src
parentActually make sure wg.4 gets included in build (diff)
downloadwireguard-openbsd-0f81dae44d741a3ce2c6d1a59413c3703612b5f0.tar.xz
wireguard-openbsd-0f81dae44d741a3ce2c6d1a59413c3703612b5f0.zip
Add bloombucket.h for ratelimiting.
In my perpetual quest for allocationless datastructures, this bloombucket attempts to rate limit an arbitrary number of peers during initiation. It works on a mix of a bloom filter and a token bucket, and has configurable parameters for size and number of hashes. The hashes are kept independent by using unique siphash keys. The idea is that a unique input, in this case the peer ip will be hashed into multiple buckets, and each of those buckets incremented. When evaluating if a packet should be rate limited, it sees if at least one of those buckets is not at the threshold. I don't have any good mathematical notes behind this, but will need to sit down and do some tests to get some sane defaults for the values.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile5
-rw-r--r--src/bloombucket.h127
-rwxr-xr-xsrc/clean_patch.sh1
-rw-r--r--src/if_wg.c99
-rw-r--r--src/wireguard.c2
-rw-r--r--src/wireguard.h5
6 files changed, 212 insertions, 27 deletions
diff --git a/src/Makefile b/src/Makefile
index 5d37d9c35c2..3533a6f3e12 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -50,6 +50,9 @@ ifconfig: patch_ifconfig
/usr/src/sys/sys/mpq.h: mpq.h
cp mpq.h /usr/src/sys/sys/mpq.h
+/usr/src/sys/sys/bloombucket.h: bloombucket.h
+ cp bloombucket.h /usr/src/sys/sys/bloombucket.h
+
/usr/src/sys/sys/hashmap.h: hashmap.h
cp hashmap.h /usr/src/sys/sys/hashmap.h
@@ -92,7 +95,7 @@ ifconfig: patch_ifconfig
patch -uN /usr/src/distrib/sets/lists/comp/mi < patches/mi.patch || touch /usr/src/distrib/sets/lists/comp/mi
.PHONY:
-patch_kernel: /usr/src/sys/net/wireguard.c /usr/src/sys/net/wireguard.h /usr/src/sys/crypto/blake2s.c /usr/src/sys/crypto/blake2s.h /usr/src/sys/crypto/curve25519.c /usr/src/sys/crypto/curve25519.h /usr/src/sys/crypto/chacha_private.h /usr/src/sys/crypto/chachapoly.h /usr/src/sys/crypto/chachapoly.c /usr/src/sys/conf/files /usr/src/sys/conf/GENERIC /usr/src/sys/netinet/in_pcb.h /usr/src/sys/netinet/udp_usrreq.c /usr/src/sys/net/if_wg.c /usr/src/sys/net/if_wg.h /usr/src/sys/sys/mpq.h /usr/src/sys/sys/hashmap.h
+patch_kernel: /usr/src/sys/net/wireguard.c /usr/src/sys/net/wireguard.h /usr/src/sys/crypto/blake2s.c /usr/src/sys/crypto/blake2s.h /usr/src/sys/crypto/curve25519.c /usr/src/sys/crypto/curve25519.h /usr/src/sys/crypto/chacha_private.h /usr/src/sys/crypto/chachapoly.h /usr/src/sys/crypto/chachapoly.c /usr/src/sys/conf/files /usr/src/sys/conf/GENERIC /usr/src/sys/netinet/in_pcb.h /usr/src/sys/netinet/udp_usrreq.c /usr/src/sys/net/if_wg.c /usr/src/sys/net/if_wg.h /usr/src/sys/sys/mpq.h /usr/src/sys/sys/bloombucket.h /usr/src/sys/sys/hashmap.h
.PHONY:
patch_userspace: /usr/src/usr.bin/kdump/mkioctls /usr/src/usr.bin/kdump/Makefile /usr/src/distrib/sets/lists/comp/mi
diff --git a/src/bloombucket.h b/src/bloombucket.h
new file mode 100644
index 00000000000..7bcaf863cce
--- /dev/null
+++ b/src/bloombucket.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2019 Matt Dunwoodie <ncon@noconroy.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __BLOOMBUCKET_H__
+#define __BLOOMBUCKET_H__
+
+#include <sys/types.h>
+#include <sys/timeout.h>
+#include <crypto/siphash.h>
+
+struct bloom_bucket {
+ int type;
+ size_t size, nkeys;
+ uint8_t *bucket, interval, thresh;
+ SIPHASH_KEY *keys;
+ struct timeout tick;
+};
+
+void bb_init(struct bloom_bucket *, size_t, size_t, uint8_t, uint8_t, int, int);
+void bb_destroy(struct bloom_bucket *);
+void bb_tick(void *);
+int bb_recv(struct bloom_bucket *, uint8_t *, size_t);
+uint8_t bb_load(struct bloom_bucket *);
+
+void
+bb_init(struct bloom_bucket *bb, size_t size, size_t keys, uint8_t interval,
+ uint8_t thresh, int type, int flags)
+{
+ size_t i;
+
+ for (bb->size = 1; bb->size < size; bb->size <<= 1);
+
+ bb->type = type;
+ bb->nkeys = keys;
+ bb->thresh = thresh;
+ bb->interval = interval;
+ bb->keys = mallocarray(bb->nkeys, sizeof(*bb->keys), bb->type, flags);
+ bb->bucket = mallocarray(bb->size, 1, bb->type, flags | M_ZERO);
+
+ for (i = 0; i < bb->nkeys; i++)
+ arc4random_buf(&bb->keys[i], sizeof(*bb->keys));
+
+ timeout_set(&bb->tick, bb_tick, bb);
+}
+
+void
+bb_destroy(struct bloom_bucket *bb)
+{
+ free(bb->keys, bb->type, bb->nkeys);
+ free(bb->bucket, bb->type, bb->size);
+}
+
+void
+bb_tick(void *_bb)
+{
+ struct bloom_bucket *bb = _bb;
+ int act;
+ size_t i;
+
+ for (i = 0, act = 0; i < bb->size; i++) {
+ /* Check to protect underflow, as well as flag
+ * if action has been taken, therefore should
+ * schedule again. */
+ if (bb->bucket[i] > 0) {
+ bb->bucket[i]--;
+ act = 1;
+ }
+ }
+
+ if (act && !timeout_pending(&bb->tick))
+ timeout_add_sec(&bb->tick, bb->interval);
+}
+
+int
+bb_recv(struct bloom_bucket *bb, uint8_t *buf, size_t n)
+{
+ size_t i;
+ uint64_t hash;
+ uint8_t *elem, min = 0xff;
+
+ for (i = 0; i < bb->nkeys; i++) {
+ hash = SipHash24(&bb->keys[i], buf, n);
+ elem = &bb->bucket[hash & (bb->size - 1)];
+ if (*elem < min)
+ min = *elem;
+ if (*elem < bb->thresh)
+ (*elem)++;
+ }
+
+ if (!timeout_pending(&bb->tick))
+ timeout_add_sec(&bb->tick, bb->interval);
+
+ return min == bb->thresh;
+}
+
+uint8_t
+bb_load(struct bloom_bucket *bb)
+{
+ size_t i;
+ uint8_t log;
+ uint64_t sum;
+ for (i = 0, sum = 0; i < bb->size; i++)
+ sum += bb->bucket[i];
+
+ sum *= (0xffffffffffffffff / (bb->size * bb->thresh));
+
+ log = 0;
+ while (sum >>= 1)
+ log++;
+
+ return log;
+}
+
+#endif /* __BLOOMBUCKET_H__ */
diff --git a/src/clean_patch.sh b/src/clean_patch.sh
index 154a918efab..d2757a38ede 100755
--- a/src/clean_patch.sh
+++ b/src/clean_patch.sh
@@ -28,6 +28,7 @@ cp $DIR/wg.4 /usr/src/share/man/man4/
cp $DIR/mpq.h /usr/src/sys/sys/
cp $DIR/hashmap.h /usr/src/sys/sys/
+cp $DIR/bloombucket.h /usr/src/sys/sys/
cp $DIR/if_wg.* $DIR/wireguard.* /usr/src/sys/net/
cp $DIR/blake2s.* $DIR/curve25519.* /usr/src/sys/crypto/
diff --git a/src/if_wg.c b/src/if_wg.c
index 03f9a70a2de..81052b76ce1 100644
--- a/src/if_wg.c
+++ b/src/if_wg.c
@@ -30,6 +30,7 @@
#include <sys/protosw.h>
#include <sys/hashmap.h>
#include <sys/mpq.h>
+#include <sys/bloombucket.h>
#include <net/if.h>
#include <net/if_var.h>
@@ -162,6 +163,7 @@ SIPHASH_KEY wg_siphashkey;
struct mpq wg_queue_tx;
struct mpq wg_queue_rx;
struct mpq wg_queue_rx_slow;
+struct bloom_bucket wg_bb;
int
wg_route_register(struct wg_softc *sc, struct wg_peer *p, struct wg_cidr *cidr)
@@ -454,21 +456,82 @@ wg_peer_cleanup(struct wg_peer *p)
wg_timer_stop(&p->p_timers);
}
-void
-wg_cookie_from_sa(struct wg_cookie *c, struct wg_cookie_maker *cm, struct sockaddr *sa)
+int
+wg_ratelimit(struct mbuf *m)
{
+ enum wg_error e;
+ struct wg_cookie c;
+ struct wg_msg_response *resp;
+ struct wg_msg_initiation *init;
+ struct sockaddr *sa = m_phext_sa(m);
+ struct wg_softc *sc = m->m_pkthdr.ph_cookie;
+
+ uint8_t *mac, *token, token_len;
+ uint32_t sender;
+
+ /* Get token from source IP address */
switch (sa->sa_family) {
case AF_INET:
- wg_cookie_from_ip(c, cm, (uint8_t *)&satosin(sa)->sin_addr, 4);
+ token = (uint8_t *)&satosin(sa)->sin_addr;
+ token_len = 4;
break;
#ifdef INET6
case AF_INET6:
- wg_cookie_from_ip(c, cm, (uint8_t *)&satosin6(sa)->sin6_addr, 16);
+ /* Use upper 8 octets, so we filter based on a /64 subnet */
+ token = (uint8_t *)&satosin6(sa)->sin6_addr;
+ token_len = 8;
break;
#endif
default:
panic("invalid af");
}
+
+ /* If we don't want to rate limit, return OK */
+ if (!bb_recv(&wg_bb, token, token_len))
+ return 0;
+
+ /*
+ * From here on, the peer has been potentially sending many
+ * packets. We need to verify if they are just spoofing IP src
+ * addresses, or they received a false positive from wg_bb.
+ */
+
+ /* Calculate cookie and mac from packet */
+ wg_cookie_from_token(&c, &sc->sc_cm, token, token_len);
+
+ switch (wg_pkt_type(mtod(m, uint8_t *), m->m_pkthdr.len)) {
+ case WG_PKT_INITIATION:
+ init = mtod(m, struct wg_msg_initiation *);
+ e = wg_handshake_initiation_valid_mac2(&c, init);
+ sender = init->sender;
+ mac = init->mac1;
+ break;
+ case WG_PKT_RESPONSE:
+ resp = mtod(m, struct wg_msg_response *);
+ e = wg_handshake_response_valid_mac2(&c, resp);
+ sender = resp->sender;
+ mac = resp->mac1;
+ break;
+ default:
+ panic("only ratelimit initiation and response");
+ }
+
+ /* If mac is invalid, and wg_bb is under high load, it is likely a
+ * bruteforce attack with a spoofed source address. We can use the
+ * cookie to validate the source address. TODO calcluate a good
+ * default value for high load, rather than just 10. */
+
+ if (bb_load(&wg_bb) > 10 && e == WG_MAC) {
+ wg_transmit_cookie(sc, sa, &c, sender, mac);
+ return -1;
+ }
+
+ /* If we get to here, we either have a valid packet, or we are under
+ * an attack from a coordinated bruteforce attack, where the attacker
+ * has control of all the source addresses. For the time being, we
+ * will just accept this and try to process the packet. */
+
+ return 0;
}
struct mbuf *
@@ -494,6 +557,7 @@ wg_input(void *_sc, struct mbuf *m, struct sockaddr *sa, int hlen)
memcpy(m_phext_sa(m), sa, sa->sa_len);
switch (wg_pkt_type(mtod(m, uint8_t *), m->m_pkthdr.len)) {
+ /* Fallthrough to mpq_enqueue slow */
case WG_PKT_INITIATION:
case WG_PKT_RESPONSE:
case WG_PKT_COOKIE:
@@ -684,20 +748,13 @@ wg_receive_slow(struct mbuf *m)
struct wg_softc *sc = m->m_pkthdr.ph_cookie;
struct sockaddr *sa = m_phext_sa(m);
struct wg_handshake hs;
- struct wg_cookie c;
struct wg_peer *p;
switch (wg_pkt_type(mtod(m, uint8_t *), m->m_pkthdr.len)) {
case WG_PKT_INITIATION:
init = mtod(m, struct wg_msg_initiation *);
-
- if (mpq_load(&wg_queue_rx_slow) > 20) {
- wg_cookie_from_sa(&c, &sc->sc_cm, sa);
- if (wg_handshake_initiation_valid_mac2(&c, init) == WG_MAC) {
- wg_transmit_cookie(sc, sa, &c, init->sender, init->mac1);
- goto free;
- }
- }
+ if (wg_ratelimit(m))
+ goto free;
if ((e = wg_handshake_recv_initiation(&hs, &sc->sc_kp, init)) != WG_OK) {
DPRINTF("wg_receive_slow: bad initiation %d\n", e);
@@ -722,14 +779,8 @@ wg_receive_slow(struct mbuf *m)
break;
case WG_PKT_RESPONSE:
resp = mtod(m, struct wg_msg_response *);
-
- if (mpq_load(&wg_queue_rx_slow) > 20) {
- wg_cookie_from_sa(&c, &sc->sc_cm, sa);
- if (wg_handshake_response_valid_mac2(&c, resp) == WG_MAC) {
- wg_transmit_cookie(sc, sa, &c, resp->sender, resp->mac1);
- goto free;
- }
- }
+ if (wg_ratelimit(m))
+ goto free;
if ((p = wg_id_lookup(sc, resp->receiver)) == NULL) {
DPRINTF("wg_receive_slow: unknown response id %x\n", resp->receiver);
@@ -958,9 +1009,11 @@ wg_output_deliver(struct mbuf *m)
peernam.m_data = (caddr_t) &p->p_ip;
peernam.m_len = p->p_ip.sa.sa_len;
+ //printf("output: %p, %x\n", &p->p_ip, p->p_ip.ip_in.sin_addr.s_addr);
+
NET_RLOCK();
if (p->p_ip.sa.sa_family == AF_INET)
- p->p_sc->sc_so4->so_proto->pr_usrreq( p->p_sc->sc_so4,
+ p->p_sc->sc_so4->so_proto->pr_usrreq(p->p_sc->sc_so4,
PRU_SEND, m, &peernam, NULL, NULL);
#ifdef INET6
else if (p->p_ip.sa.sa_family == AF_INET6)
@@ -983,6 +1036,8 @@ wgattach(int nwg)
{
if_clone_attach(&wg_cloner);
arc4random_buf(&wg_siphashkey, sizeof(wg_siphashkey));
+ /* entries: 1024, keys: 3, rate: 5sec, threshold: 5 */
+ bb_init(&wg_bb, 1024, 3, 5, 5, M_DEVBUF, M_WAITOK);
SLIST_INIT(&wg_devs);
}
diff --git a/src/wireguard.c b/src/wireguard.c
index ac8a86acdb4..38a678a2984 100644
--- a/src/wireguard.c
+++ b/src/wireguard.c
@@ -442,7 +442,7 @@ leave:
}
void
-wg_cookie_from_ip(struct wg_cookie *c, struct wg_cookie_maker *cm, uint8_t *ip, uint8_t ip_len)
+wg_cookie_from_token(struct wg_cookie *c, struct wg_cookie_maker *cm, uint8_t *ip, uint8_t ip_len)
{
if (wg_timespec_timedout(&cm->time, WG_COOKIE_VALID_TIME)) {
getnanotime(&cm->time);
diff --git a/src/wireguard.h b/src/wireguard.h
index 44872b49c37..d53f2ee9fa4 100644
--- a/src/wireguard.h
+++ b/src/wireguard.h
@@ -217,8 +217,7 @@ void wg_session_clean(struct wg_session *);
enum wg_error wg_handshake_make_initiation(struct wg_handshake *, uint32_t, struct wg_msg_initiation *);
enum wg_error wg_handshake_make_response(struct wg_handshake *, uint32_t, struct wg_msg_response *);
-enum wg_error wg_handshake_make_cookie(struct wg_keypair *kp, struct wg_cookie *, uint32_t, uint8_t mac[WG_MAC_SIZE], struct wg_msg_cookie *);
-enum wg_error wg_handshake_make_cookie(struct wg_keypair *, struct wg_cookie *, uint32_t sender, uint8_t mac[WG_MAC_SIZE], struct wg_msg_cookie *m);
+enum wg_error wg_handshake_make_cookie(struct wg_keypair *, struct wg_cookie *, uint32_t, uint8_t [WG_MAC_SIZE], struct wg_msg_cookie *);
enum wg_error wg_handshake_recv_initiation(struct wg_handshake *, struct wg_keypair *, struct wg_msg_initiation *);
enum wg_error wg_handshake_recv_response(struct wg_handshake *, struct wg_msg_response *);
@@ -235,7 +234,7 @@ enum wg_error wg_session_from_handshake(struct wg_session *, struct wg_handshake
void wg_keypair_from_bytes(struct wg_keypair *, const uint8_t [WG_KEY_SIZE]);
void wg_keypair_generate(struct wg_keypair *);
enum wg_pkt_type wg_pkt_type(uint8_t *, size_t);
-void wg_cookie_from_ip(struct wg_cookie *, struct wg_cookie_maker *, uint8_t *, uint8_t);
+void wg_cookie_from_token(struct wg_cookie *, struct wg_cookie_maker *, uint8_t *, uint8_t);
/* Timer functions */
void wg_timer_setup(struct wg_timers *, void *, void (*)(void *),