summaryrefslogtreecommitdiffhomepage
path: root/src/ratelimiter.c
blob: 12282fdc2fadf93560694ec67a81f9f5bbe5a345 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */

#include "ratelimiter.h"
#include "peer.h"
#include "device.h"

#include <linux/module.h>
#include <linux/netfilter/x_tables.h>
#include <net/ip.h>

static struct xt_match *v4_match __read_mostly;
#if IS_ENABLED(CONFIG_IPV6)
static struct xt_match *v6_match __read_mostly;
#endif

enum {
	RATELIMITER_PACKETS_PER_SECOND = 30,
	RATELIMITER_PACKETS_BURSTABLE = 5
};

static inline void cfg_init(struct hashlimit_cfg1 *cfg, int family)
{
	memset(cfg, 0, sizeof(struct hashlimit_cfg1));
	if (family == NFPROTO_IPV4)
		cfg->srcmask = 32;
	else if (family == NFPROTO_IPV6)
		cfg->srcmask = 96;
	cfg->mode = XT_HASHLIMIT_HASH_SIP; /* source IP only -- we could also do source port by ORing this with XT_HASHLIMIT_HASH_SPT */
	cfg->avg = XT_HASHLIMIT_SCALE / RATELIMITER_PACKETS_PER_SECOND; /* 30 per second per IP */
	cfg->burst = RATELIMITER_PACKETS_BURSTABLE; /* Allow bursts of 5 at a time */
	cfg->gc_interval = 1000; /* same as expiration date */
	cfg->expire = 1000; /* Units of avg (seconds = 1) times 1000 */
	/* cfg->size and cfg->max are computed based on the memory size of left to zero */
}

int ratelimiter_init(struct ratelimiter *ratelimiter, struct wireguard_device *wg)
{
	struct net_device *dev = netdev_pub(wg);
	struct xt_mtchk_param chk = { .net = wg->creating_net };
	int ret;

	memset(ratelimiter, 0, sizeof(struct ratelimiter));

	cfg_init(&ratelimiter->v4_info.cfg, NFPROTO_IPV4);
	memcpy(ratelimiter->v4_info.name, dev->name, IFNAMSIZ);
	chk.matchinfo = &ratelimiter->v4_info;
	chk.match = v4_match;
	chk.family = NFPROTO_IPV4;
	ret = v4_match->checkentry(&chk);
	if (ret < 0)
		return ret;

#if IS_ENABLED(CONFIG_IPV6)
	cfg_init(&ratelimiter->v6_info.cfg, NFPROTO_IPV6);
	memcpy(ratelimiter->v6_info.name, dev->name, IFNAMSIZ);
	chk.matchinfo = &ratelimiter->v6_info;
	chk.match = v6_match;
	chk.family = NFPROTO_IPV6;
	ret = v6_match->checkentry(&chk);
	if (ret < 0) {
		struct xt_mtdtor_param dtor_v4 = {
			.net = wg->creating_net,
			.match = v4_match,
			.matchinfo = &ratelimiter->v4_info,
			.family = NFPROTO_IPV4
		};
		v4_match->destroy(&dtor_v4);
		return ret;
	}
#endif

	ratelimiter->net = wg->creating_net;
	return 0;
}

void ratelimiter_uninit(struct ratelimiter *ratelimiter)
{
	struct xt_mtdtor_param dtor = { .net = ratelimiter->net };

	dtor.match = v4_match;
	dtor.matchinfo = &ratelimiter->v4_info;
	dtor.family = NFPROTO_IPV4;
	v4_match->destroy(&dtor);

#if IS_ENABLED(CONFIG_IPV6)
	dtor.match = v6_match;
	dtor.matchinfo = &ratelimiter->v6_info;
	dtor.family = NFPROTO_IPV6;
	v6_match->destroy(&dtor);
#endif
}

bool ratelimiter_allow(struct ratelimiter *ratelimiter, struct sk_buff *skb)
{
	struct xt_action_param action = { { NULL } };
	if (unlikely(skb->len < sizeof(struct iphdr)))
		return false;
	if (ip_hdr(skb)->version == 4) {
		action.match = v4_match;
		action.matchinfo = &ratelimiter->v4_info;
		action.thoff = ip_hdrlen(skb);
	}
#if IS_ENABLED(CONFIG_IPV6)
	else if (ip_hdr(skb)->version == 6) {
		action.match = v6_match;
		action.matchinfo = &ratelimiter->v6_info;
	}
#endif
	else
		return false;
	return action.match->match(skb, &action);
}

int ratelimiter_module_init(void)
{
	v4_match = xt_request_find_match(NFPROTO_IPV4, "hashlimit", 1);
	if (IS_ERR(v4_match)) {
		pr_err("The xt_hashlimit module for IPv4 is required\n");
		return PTR_ERR(v4_match);
	}
#if IS_ENABLED(CONFIG_IPV6)
	v6_match = xt_request_find_match(NFPROTO_IPV6, "hashlimit", 1);
	if (IS_ERR(v6_match)) {
		pr_err("The xt_hashlimit module for IPv6 is required\n");
		module_put(v4_match->me);
		return PTR_ERR(v6_match);
	}
#endif
	return 0;
}

void ratelimiter_module_deinit(void)
{
	module_put(v4_match->me);
#if IS_ENABLED(CONFIG_IPV6)
	module_put(v6_match->me);
#endif
}