From a0a962428b430f97b831fb167c9adbebe777beb7 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 3 Aug 2017 23:45:26 +0200 Subject: ratelimiter: remove and use in-kernel functionality --- src/Kbuild | 2 +- src/cookie.c | 20 ++++- src/device.c | 10 +-- src/main.c | 3 +- src/ratelimiter.c | 194 --------------------------------------------- src/ratelimiter.h | 16 ---- src/selftest/ratelimiter.h | 143 --------------------------------- 7 files changed, 22 insertions(+), 366 deletions(-) delete mode 100644 src/ratelimiter.c delete mode 100644 src/ratelimiter.h delete mode 100644 src/selftest/ratelimiter.h diff --git a/src/Kbuild b/src/Kbuild index c5b8718..f5ec49c 100644 --- a/src/Kbuild +++ b/src/Kbuild @@ -2,7 +2,7 @@ ccflags-y := -O3 -fvisibility=hidden ccflags-$(CONFIG_WIREGUARD_DEBUG) += -DDEBUG -g ccflags-y += -Wframe-larger-than=8192 ccflags-y += -D'pr_fmt(fmt)=KBUILD_MODNAME ": " fmt' -wireguard-y := main.o noise.o device.o peer.o timers.o data.o send.o receive.o socket.o config.o hashtables.o routingtable.o ratelimiter.o cookie.o +wireguard-y := main.o noise.o device.o peer.o timers.o data.o send.o receive.o socket.o config.o hashtables.o routingtable.o cookie.o wireguard-y += crypto/curve25519.o crypto/chacha20poly1305.o crypto/blake2s.o ifeq ($(CONFIG_X86_64),y) diff --git a/src/cookie.c b/src/cookie.c index ec58903..323f046 100644 --- a/src/cookie.c +++ b/src/cookie.c @@ -4,7 +4,6 @@ #include "peer.h" #include "device.h" #include "messages.h" -#include "ratelimiter.h" #include "crypto/blake2s.h" #include "crypto/chacha20poly1305.h" @@ -95,6 +94,25 @@ static void make_cookie(u8 cookie[COOKIE_LEN], struct sk_buff *skb, struct cooki up_read(&checker->secret_lock); } +enum { PACKETS_PER_SECOND = 20 }; +static bool ratelimiter_allow(struct sk_buff *skb, struct net *net) +{ + struct inet_peer *peer = NULL; + bool ret; + + if (skb->protocol == htons(ETH_P_IP)) + peer = inet_getpeer_v4(net->ipv4.peers, ip_hdr(skb)->saddr, l3mdev_master_ifindex(skb->dev), true); +#if IS_ENABLED(CONFIG_IPV6) + else if (skb->protocol == htons(ETH_P_IPV6)) + peer = inet_getpeer_v6(net->ipv6.peers, &ipv6_hdr(skb)->saddr, true); +#endif + if (unlikely(!peer)) + return false; + ret = inet_peer_xrlim_allow(peer, PACKETS_PER_SECOND); + inet_putpeer(peer); + return ret; +} + enum cookie_mac_state cookie_validate_packet(struct cookie_checker *checker, struct sk_buff *skb, bool check_cookie) { u8 computed_mac[COOKIE_LEN]; diff --git a/src/device.c b/src/device.c index cb5517e..c079f2b 100644 --- a/src/device.c +++ b/src/device.c @@ -5,7 +5,6 @@ #include "timers.h" #include "device.h" #include "config.h" -#include "ratelimiter.h" #include "peer.h" #include "uapi.h" #include "messages.h" @@ -230,7 +229,6 @@ static void destruct(struct net_device *dev) destroy_workqueue(wg->crypt_wq); #endif routing_table_free(&wg->peer_routing_table); - ratelimiter_uninit(); memzero_explicit(&wg->static_identity, sizeof(struct noise_static_identity)); skb_queue_purge(&wg->incoming_handshakes); socket_uninit(wg); @@ -326,13 +324,9 @@ static int newlink(struct net *src_net, struct net_device *dev, struct nlattr *t padata_start(wg->decrypt_pd); #endif - ret = ratelimiter_init(); - if (ret < 0) - goto error_8; - ret = register_netdevice(dev); if (ret < 0) - goto error_9; + goto error_8; list_add(&wg->device_list, &device_list); @@ -343,8 +337,6 @@ static int newlink(struct net *src_net, struct net_device *dev, struct nlattr *t pr_debug("%s: Interface created\n", dev->name); return ret; -error_9: - ratelimiter_uninit(); error_8: #ifdef CONFIG_WIREGUARD_PARALLEL padata_free(wg->decrypt_pd); diff --git a/src/main.c b/src/main.c index 0697741..a7064ea 100644 --- a/src/main.c +++ b/src/main.c @@ -4,7 +4,6 @@ #include "device.h" #include "noise.h" #include "packets.h" -#include "ratelimiter.h" #include "crypto/chacha20poly1305.h" #include "crypto/blake2s.h" #include "crypto/curve25519.h" @@ -22,7 +21,7 @@ static int __init mod_init(void) blake2s_fpu_init(); curve25519_fpu_init(); #ifdef DEBUG - if (!routing_table_selftest() || !packet_counter_selftest() || !curve25519_selftest() || !chacha20poly1305_selftest() || !blake2s_selftest() || !ratelimiter_selftest()) + if (!routing_table_selftest() || !packet_counter_selftest() || !curve25519_selftest() || !chacha20poly1305_selftest() || !blake2s_selftest()) return -ENOTRECOVERABLE; #endif noise_init(); diff --git a/src/ratelimiter.c b/src/ratelimiter.c deleted file mode 100644 index 9ad451e..0000000 --- a/src/ratelimiter.c +++ /dev/null @@ -1,194 +0,0 @@ -/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ - -#include "ratelimiter.h" -#include -#include -#include -#include - -static struct kmem_cache *entry_cache; -static hsiphash_key_t key; -static spinlock_t table_lock = __SPIN_LOCK_UNLOCKED("ratelimiter_table_lock"); -static atomic64_t refcnt = ATOMIC64_INIT(0); -static atomic_t total_entries = ATOMIC_INIT(0); -static unsigned int max_entries, table_size; -static void gc_entries(struct work_struct *); -static DECLARE_DEFERRABLE_WORK(gc_work, gc_entries); -static struct hlist_head *table_v4; -#if IS_ENABLED(CONFIG_IPV6) -static struct hlist_head *table_v6; -#endif - -struct ratelimiter_entry { - u64 last_time_ns, tokens; - __be64 ip; - void *net; - spinlock_t lock; - struct hlist_node hash; - struct rcu_head rcu; -}; - -enum { - PACKETS_PER_SECOND = 20, - PACKETS_BURSTABLE = 5, - PACKET_COST = NSEC_PER_SEC / PACKETS_PER_SECOND, - TOKEN_MAX = PACKET_COST * PACKETS_BURSTABLE -}; - -static void entry_free(struct rcu_head *rcu) -{ - kmem_cache_free(entry_cache, container_of(rcu, struct ratelimiter_entry, rcu)); - atomic_dec(&total_entries); -} - -static void entry_uninit(struct ratelimiter_entry *entry) -{ - hlist_del_rcu(&entry->hash); - call_rcu(&entry->rcu, entry_free); -} - -/* Calling this function with a NULL work uninits all entries. */ -static void gc_entries(struct work_struct *work) -{ - unsigned int i; - struct ratelimiter_entry *entry; - struct hlist_node *temp; - const u64 now = ktime_get_ns(); - - for (i = 0; i < table_size; ++i) { - spin_lock(&table_lock); - hlist_for_each_entry_safe (entry, temp, &table_v4[i], hash) { - if (unlikely(!work) || now - entry->last_time_ns > NSEC_PER_SEC) - entry_uninit(entry); - } -#if IS_ENABLED(CONFIG_IPV6) - hlist_for_each_entry_safe (entry, temp, &table_v6[i], hash) { - if (unlikely(!work) || now - entry->last_time_ns > NSEC_PER_SEC) - entry_uninit(entry); - } -#endif - spin_unlock(&table_lock); - if (likely(work)) - cond_resched(); - } - if (likely(work)) - queue_delayed_work(system_power_efficient_wq, &gc_work, HZ); -} - -bool ratelimiter_allow(struct sk_buff *skb, struct net *net) -{ - struct ratelimiter_entry *entry; - struct hlist_head *bucket; - struct { __be64 ip; u32 net; } data = { .net = (unsigned long)net & 0xffffffff }; - - if (skb->protocol == htons(ETH_P_IP)) { - data.ip = (__force __be64)ip_hdr(skb)->saddr; - bucket = &table_v4[hsiphash(&data, sizeof(u32) * 3, &key) & (table_size - 1)]; - } -#if IS_ENABLED(CONFIG_IPV6) - else if (skb->protocol == htons(ETH_P_IPV6)) { - memcpy(&data.ip, &ipv6_hdr(skb)->saddr, sizeof(__be64)); /* Only 64 bits */ - bucket = &table_v6[hsiphash(&data, sizeof(u32) * 3, &key) & (table_size - 1)]; - } -#endif - else - return false; - rcu_read_lock(); - hlist_for_each_entry_rcu (entry, bucket, hash) { - if (entry->net == net && entry->ip == data.ip) { - u64 now, tokens; - bool ret; - /* Inspired by nft_limit.c, but this is actually a slightly different - * algorithm. Namely, we incorporate the burst as part of the maximum - * tokens, rather than as part of the rate. */ - spin_lock(&entry->lock); - now = ktime_get_ns(); - tokens = min_t(u64, TOKEN_MAX, entry->tokens + now - entry->last_time_ns); - entry->last_time_ns = now; - ret = tokens >= PACKET_COST; - entry->tokens = ret ? tokens - PACKET_COST : tokens; - spin_unlock(&entry->lock); - rcu_read_unlock(); - return ret; - } - } - rcu_read_unlock(); - - if (atomic_inc_return(&total_entries) > max_entries) - goto err_oom; - - entry = kmem_cache_alloc(entry_cache, GFP_KERNEL); - if (!entry) - goto err_oom; - - entry->net = net; - entry->ip = data.ip; - INIT_HLIST_NODE(&entry->hash); - spin_lock_init(&entry->lock); - entry->last_time_ns = ktime_get_ns(); - entry->tokens = TOKEN_MAX - PACKET_COST; - spin_lock(&table_lock); - hlist_add_head_rcu(&entry->hash, bucket); - spin_unlock(&table_lock); - return true; - -err_oom: - atomic_dec(&total_entries); - return false; -} - -int ratelimiter_init(void) -{ - if (atomic64_inc_return(&refcnt) != 1) - return 0; - - entry_cache = KMEM_CACHE(ratelimiter_entry, 0); - if (!entry_cache) - goto err; - - /* xt_hashlimit.c uses a slightly different algorithm for ratelimiting, - * but what it shares in common is that it uses a massive hashtable. So, - * we borrow their wisdom about good table sizes on different systems - * dependent on RAM. This calculation here comes from there. */ - table_size = (totalram_pages > (1 << 30) / PAGE_SIZE) ? 8192 : max_t(unsigned long, 16, roundup_pow_of_two((totalram_pages << PAGE_SHIFT) / (1 << 14) / sizeof(struct hlist_head))); - max_entries = table_size * 8; - - table_v4 = kvzalloc(table_size * sizeof(struct hlist_head), GFP_KERNEL); - if (!table_v4) - goto err_kmemcache; - -#if IS_ENABLED(CONFIG_IPV6) - table_v6 = kvzalloc(table_size * sizeof(struct hlist_head), GFP_KERNEL); - if (!table_v6) { - kvfree(table_v4); - goto err_kmemcache; - } -#endif - - queue_delayed_work(system_power_efficient_wq, &gc_work, HZ); - get_random_bytes(&key, sizeof(key)); - return 0; - -err_kmemcache: - kmem_cache_destroy(entry_cache); -err: - atomic64_dec(&refcnt); - return -ENOMEM; -} - -void ratelimiter_uninit(void) -{ - if (atomic64_dec_return(&refcnt)) - return; - - cancel_delayed_work_sync(&gc_work); - gc_entries(NULL); - synchronize_rcu(); - kvfree(table_v4); -#if IS_ENABLED(CONFIG_IPV6) - kvfree(table_v6); -#endif - kmem_cache_destroy(entry_cache); -} - -#include "selftest/ratelimiter.h" diff --git a/src/ratelimiter.h b/src/ratelimiter.h deleted file mode 100644 index cb0cdbf..0000000 --- a/src/ratelimiter.h +++ /dev/null @@ -1,16 +0,0 @@ -/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ - -#ifndef RATELIMITER_H -#define RATELIMITER_H - -#include - -int ratelimiter_init(void); -void ratelimiter_uninit(void); -bool ratelimiter_allow(struct sk_buff *skb, struct net *net); - -#ifdef DEBUG -bool ratelimiter_selftest(void); -#endif - -#endif diff --git a/src/selftest/ratelimiter.h b/src/selftest/ratelimiter.h deleted file mode 100644 index bf6b9b8..0000000 --- a/src/selftest/ratelimiter.h +++ /dev/null @@ -1,143 +0,0 @@ -/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ - -#ifdef DEBUG - -#include - -static const struct { bool result; unsigned int msec_to_sleep_before; } expected_results[] __initconst = { - [0 ... PACKETS_BURSTABLE - 1] = { true, 0 }, - [PACKETS_BURSTABLE] = { false, 0 }, - [PACKETS_BURSTABLE + 1] = { true, MSEC_PER_SEC / PACKETS_PER_SECOND }, - [PACKETS_BURSTABLE + 2] = { false, 0 }, - [PACKETS_BURSTABLE + 3] = { true, (MSEC_PER_SEC / PACKETS_PER_SECOND) * 2 }, - [PACKETS_BURSTABLE + 4] = { true, 0 }, - [PACKETS_BURSTABLE + 5] = { false, 0 } -}; - -static __init unsigned int maximum_jiffies_at_index(int index) -{ - unsigned int total_msecs = 2 * MSEC_PER_SEC / PACKETS_PER_SECOND / 3; - int i; - - for (i = 0; i <= index; ++i) - total_msecs += expected_results[i].msec_to_sleep_before; - return msecs_to_jiffies(total_msecs); -} - -bool __init ratelimiter_selftest(void) -{ - struct sk_buff *skb4; - struct iphdr *hdr4; -#if IS_ENABLED(CONFIG_IPV6) - struct sk_buff *skb6; - struct ipv6hdr *hdr6; -#endif - int i = -1, tries = 0, ret = false; - unsigned long loop_start_time; - - BUILD_BUG_ON(MSEC_PER_SEC % PACKETS_PER_SECOND != 0); - - if (ratelimiter_init()) - goto out; - if (ratelimiter_init()) { - ratelimiter_uninit(); - goto out; - } - if (ratelimiter_init()) { - ratelimiter_uninit(); - ratelimiter_uninit(); - goto out; - } - - skb4 = alloc_skb(sizeof(struct iphdr), GFP_KERNEL); - if (!skb4) - goto err_nofree; - skb4->protocol = htons(ETH_P_IP); - hdr4 = (struct iphdr *)skb_put(skb4, sizeof(struct iphdr)); - hdr4->saddr = htonl(8182); - skb_reset_network_header(skb4); - -#if IS_ENABLED(CONFIG_IPV6) - skb6 = alloc_skb(sizeof(struct ipv6hdr), GFP_KERNEL); - if (!skb6) { - kfree_skb(skb4); - goto err_nofree; - } - skb6->protocol = htons(ETH_P_IPV6); - hdr6 = (struct ipv6hdr *)skb_put(skb6, sizeof(struct ipv6hdr)); - hdr6->saddr.in6_u.u6_addr32[0] = htonl(1212); - hdr6->saddr.in6_u.u6_addr32[1] = htonl(289188); - skb_reset_network_header(skb6); -#endif - -restart: - loop_start_time = jiffies; - for (i = 0; i < ARRAY_SIZE(expected_results); ++i) { -#define ensure_time do {\ - if (time_is_before_jiffies(loop_start_time + maximum_jiffies_at_index(i))) { \ - if (++tries >= 5000) \ - goto err; \ - gc_entries(NULL); \ - rcu_barrier(); \ - msleep(500); \ - goto restart; \ - }} while (0) - - if (expected_results[i].msec_to_sleep_before) - msleep(expected_results[i].msec_to_sleep_before); - - ensure_time; - if (ratelimiter_allow(skb4, &init_net) != expected_results[i].result) - goto err; - hdr4->saddr = htonl(ntohl(hdr4->saddr) + i + 1); - ensure_time; - if (!ratelimiter_allow(skb4, &init_net)) - goto err; - hdr4->saddr = htonl(ntohl(hdr4->saddr) - i - 1); - -#if IS_ENABLED(CONFIG_IPV6) - hdr6->saddr.in6_u.u6_addr32[2] = hdr6->saddr.in6_u.u6_addr32[3] = htonl(i); - ensure_time; - if (ratelimiter_allow(skb6, &init_net) != expected_results[i].result) - goto err; - hdr6->saddr.in6_u.u6_addr32[0] = htonl(ntohl(hdr6->saddr.in6_u.u6_addr32[0]) + i + 1); - ensure_time; - if (!ratelimiter_allow(skb6, &init_net)) - goto err; - hdr6->saddr.in6_u.u6_addr32[0] = htonl(ntohl(hdr6->saddr.in6_u.u6_addr32[0]) - i - 1); - ensure_time; -#endif - } - - gc_entries(NULL); - rcu_barrier(); - - if (atomic_read(&total_entries)) - goto err; - - for (i = 0; i <= max_entries; ++i) { - hdr4->saddr = htonl(i); - if (ratelimiter_allow(skb4, &init_net) != (i != max_entries)) - goto err; - } - - ret = true; - -err: - kfree_skb(skb4); -#if IS_ENABLED(CONFIG_IPV6) - kfree_skb(skb6); -#endif -err_nofree: - ratelimiter_uninit(); - ratelimiter_uninit(); - ratelimiter_uninit(); -out: - if (ret) - pr_info("ratelimiter self-tests: pass\n"); - else - pr_info("ratelimiter self-test %d: fail\n", i); - - return ret; -} -#endif -- cgit v1.2.3-59-g8ed1b