1/* Shared library add-on to iptables to add limit support.
2 *
3 * Jérôme de Vivie   <devivie@info.enserb.u-bordeaux.fr>
4 * Hervé Eychenne    <rv@wallfire.org>
5 */
6#define _BSD_SOURCE 1
7#define _ISOC99_SOURCE 1
8#include <math.h>
9#include <stdio.h>
10#include <string.h>
11#include <stdlib.h>
12#include <xtables.h>
13#include <linux/netfilter/x_tables.h>
14#include <linux/netfilter/xt_limit.h>
15
16#define XT_LIMIT_AVG	"3/hour"
17#define XT_LIMIT_BURST	5
18
19enum {
20	O_LIMIT = 0,
21	O_BURST,
22};
23
24static void limit_help(void)
25{
26	printf(
27"limit match options:\n"
28"--limit avg			max average match rate: default "XT_LIMIT_AVG"\n"
29"                                [Packets per second unless followed by \n"
30"                                /sec /minute /hour /day postfixes]\n"
31"--limit-burst number		number to match in a burst, default %u\n",
32XT_LIMIT_BURST);
33}
34
35static const struct xt_option_entry limit_opts[] = {
36	{.name = "limit", .id = O_LIMIT, .type = XTTYPE_STRING},
37	{.name = "limit-burst", .id = O_BURST, .type = XTTYPE_UINT32,
38	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_rateinfo, burst),
39	 .min = 0, .max = 10000},
40	XTOPT_TABLEEND,
41};
42
43static
44int parse_rate(const char *rate, uint32_t *val)
45{
46	const char *delim;
47	uint32_t r;
48	uint32_t mult = 1;  /* Seconds by default. */
49
50	delim = strchr(rate, '/');
51	if (delim) {
52		if (strlen(delim+1) == 0)
53			return 0;
54
55		if (strncasecmp(delim+1, "second", strlen(delim+1)) == 0)
56			mult = 1;
57		else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0)
58			mult = 60;
59		else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0)
60			mult = 60*60;
61		else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0)
62			mult = 24*60*60;
63		else
64			return 0;
65	}
66	r = atoi(rate);
67	if (!r)
68		return 0;
69
70	*val = XT_LIMIT_SCALE * mult / r;
71	if (*val == 0)
72		/*
73		 * The rate maps to infinity. (1/day is the minimum they can
74		 * specify, so we are ok at that end).
75		 */
76		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
77	return 1;
78}
79
80static void limit_init(struct xt_entry_match *m)
81{
82	struct xt_rateinfo *r = (struct xt_rateinfo *)m->data;
83
84	parse_rate(XT_LIMIT_AVG, &r->avg);
85	r->burst = XT_LIMIT_BURST;
86
87}
88
89/* FIXME: handle overflow:
90	if (r->avg*r->burst/r->burst != r->avg)
91		xtables_error(PARAMETER_PROBLEM,
92			   "Sorry: burst too large for that avg rate.\n");
93*/
94
95static void limit_parse(struct xt_option_call *cb)
96{
97	struct xt_rateinfo *r = cb->data;
98
99	xtables_option_parse(cb);
100	switch (cb->entry->id) {
101	case O_LIMIT:
102		if (!parse_rate(cb->arg, &r->avg))
103			xtables_error(PARAMETER_PROBLEM,
104				   "bad rate \"%s\"'", cb->arg);
105		break;
106	}
107	if (cb->invert)
108		xtables_error(PARAMETER_PROBLEM,
109			   "limit does not support invert");
110}
111
112static const struct rates
113{
114	const char *name;
115	uint32_t mult;
116} rates[] = { { "day", XT_LIMIT_SCALE*24*60*60 },
117	      { "hour", XT_LIMIT_SCALE*60*60 },
118	      { "min", XT_LIMIT_SCALE*60 },
119	      { "sec", XT_LIMIT_SCALE } };
120
121static void print_rate(uint32_t period)
122{
123	unsigned int i;
124
125	if (period == 0) {
126		printf(" %f", INFINITY);
127		return;
128	}
129
130	for (i = 1; i < ARRAY_SIZE(rates); ++i)
131		if (period > rates[i].mult
132            || rates[i].mult/period < rates[i].mult%period)
133			break;
134
135	printf(" %u/%s", rates[i-1].mult / period, rates[i-1].name);
136}
137
138static void
139limit_print(const void *ip, const struct xt_entry_match *match, int numeric)
140{
141	const struct xt_rateinfo *r = (const void *)match->data;
142	printf(" limit: avg"); print_rate(r->avg);
143	printf(" burst %u", r->burst);
144}
145
146static void limit_save(const void *ip, const struct xt_entry_match *match)
147{
148	const struct xt_rateinfo *r = (const void *)match->data;
149
150	printf(" --limit"); print_rate(r->avg);
151	if (r->burst != XT_LIMIT_BURST)
152		printf(" --limit-burst %u", r->burst);
153}
154
155static struct xtables_match limit_match = {
156	.family		= NFPROTO_UNSPEC,
157	.name		= "limit",
158	.version	= XTABLES_VERSION,
159	.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
160	.userspacesize	= offsetof(struct xt_rateinfo, prev),
161	.help		= limit_help,
162	.init		= limit_init,
163	.x6_parse	= limit_parse,
164	.print		= limit_print,
165	.save		= limit_save,
166	.x6_options	= limit_opts,
167};
168
169void _init(void)
170{
171	xtables_register_match(&limit_match);
172}
173