1/*
2 * ip_conntrack_proto_gre.c - Version 1.11
3 *
4 * Connection tracking protocol helper module for GRE.
5 *
6 * GRE is a generic encapsulation protocol, which is generally not very
7 * suited for NAT, as it has no protocol-specific part as port numbers.
8 *
9 * It has an optional key field, which may help us distinguishing two
10 * connections between the same two hosts.
11 *
12 * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784
13 *
14 * PPTP is built on top of a modified version of GRE, and has a mandatory
15 * field called "CallID", which serves us for the same purpose as the key
16 * field in plain GRE.
17 *
18 * Documentation about PPTP can be found in RFC 2637
19 *
20 * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
21 *
22 * Development of this code funded by Astaro AG (http://www.astaro.com/)
23 *
24 */
25
26#include <linux/config.h>
27#include <linux/module.h>
28#include <linux/types.h>
29#include <linux/timer.h>
30#include <linux/netfilter.h>
31#include <linux/ip.h>
32#include <linux/in.h>
33#include <linux/list.h>
34
35#include <linux/netfilter_ipv4/lockhelp.h>
36
37DECLARE_RWLOCK(ip_ct_gre_lock);
38#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_ct_gre_lock)
39#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_ct_gre_lock)
40
41#include <linux/netfilter_ipv4/listhelp.h>
42#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
43#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
44#include <linux/netfilter_ipv4/ip_conntrack_core.h>
45
46#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
47#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
48
49MODULE_LICENSE("GPL");
50MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
51MODULE_DESCRIPTION("netfilter connection tracking protocol helper for GRE");
52
53/* shamelessly stolen from ip_conntrack_proto_udp.c */
54#define GRE_TIMEOUT		(30*HZ)
55#define GRE_STREAM_TIMEOUT	(180*HZ)
56
57#define DEBUGP(x, args...)
58#define DUMP_TUPLE_GRE(x)
59
60/* GRE KEYMAP HANDLING FUNCTIONS */
61static LIST_HEAD(gre_keymap_list);
62
63static inline int gre_key_cmpfn(const struct ip_ct_gre_keymap *km,
64				const struct ip_conntrack_tuple *t)
65{
66	return ((km->tuple.src.ip == t->src.ip) &&
67		(km->tuple.dst.ip == t->dst.ip) &&
68	        (km->tuple.dst.protonum == t->dst.protonum) &&
69	        (km->tuple.dst.u.all == t->dst.u.all));
70}
71
72/* look up the source key for a given tuple */
73static u_int32_t gre_keymap_lookup(struct ip_conntrack_tuple *t)
74{
75	struct ip_ct_gre_keymap *km;
76	u_int32_t key;
77
78	READ_LOCK(&ip_ct_gre_lock);
79	km = LIST_FIND(&gre_keymap_list, gre_key_cmpfn,
80			struct ip_ct_gre_keymap *, t);
81	if (!km) {
82		READ_UNLOCK(&ip_ct_gre_lock);
83		return 0;
84	}
85
86	key = km->tuple.src.u.gre.key;
87	READ_UNLOCK(&ip_ct_gre_lock);
88
89	return key;
90}
91
92/* add a single keymap entry, associate with specified expect */
93int ip_ct_gre_keymap_add(struct ip_conntrack_expect *exp,
94			 struct ip_conntrack_tuple *t, int reply)
95{
96	struct ip_ct_gre_keymap *km;
97
98	km = kmalloc(sizeof(*km), GFP_ATOMIC);
99	if (!km)
100		return -1;
101
102	/* initializing list head should be sufficient */
103	memset(km, 0, sizeof(*km));
104
105	memcpy(&km->tuple, t, sizeof(*t));
106	km->master = exp;
107
108	if (!reply)
109		exp->proto.gre.keymap_orig = km;
110	else
111		exp->proto.gre.keymap_reply = km;
112
113	DEBUGP("adding new entry %p: ", km);
114	DUMP_TUPLE_GRE(&km->tuple);
115
116	WRITE_LOCK(&ip_ct_gre_lock);
117	list_append(&gre_keymap_list, km);
118	WRITE_UNLOCK(&ip_ct_gre_lock);
119
120	return 0;
121}
122
123/* change the tuple of a keymap entry (used by nat helper) */
124void ip_ct_gre_keymap_change(struct ip_ct_gre_keymap *km,
125			     struct ip_conntrack_tuple *t)
126{
127	DEBUGP("changing entry %p to: ", km);
128	DUMP_TUPLE_GRE(t);
129
130	WRITE_LOCK(&ip_ct_gre_lock);
131	memcpy(&km->tuple, t, sizeof(km->tuple));
132	WRITE_UNLOCK(&ip_ct_gre_lock);
133}
134
135
136/* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */
137
138/* invert gre part of tuple */
139static int gre_invert_tuple(struct ip_conntrack_tuple *tuple,
140			    const struct ip_conntrack_tuple *orig)
141{
142	tuple->dst.u.gre.protocol = orig->dst.u.gre.protocol;
143	tuple->dst.u.gre.version = orig->dst.u.gre.version;
144
145	tuple->dst.u.gre.key = orig->src.u.gre.key;
146	tuple->src.u.gre.key = orig->dst.u.gre.key;
147
148	return 1;
149}
150
151/* gre hdr info to tuple */
152static int gre_pkt_to_tuple(const void *datah, size_t datalen,
153			    struct ip_conntrack_tuple *tuple)
154{
155	struct gre_hdr *grehdr = (struct gre_hdr *) datah;
156	struct gre_hdr_pptp *pgrehdr = (struct gre_hdr_pptp *) datah;
157	u_int32_t srckey;
158
159	/* core guarantees 8 protocol bytes, no need for size check */
160
161	tuple->dst.u.gre.version = grehdr->version;
162	tuple->dst.u.gre.protocol = grehdr->protocol;
163
164	switch (grehdr->version) {
165		case GRE_VERSION_1701:
166			if (!grehdr->key) {
167				DEBUGP("Can't track GRE without key\n");
168				return 0;
169			}
170			tuple->dst.u.gre.key = *(gre_key(grehdr));
171			break;
172
173		case GRE_VERSION_PPTP:
174			if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) {
175				DEBUGP("GRE_VERSION_PPTP but unknown proto\n");
176				return 0;
177			}
178			tuple->dst.u.gre.key = htonl(ntohs(pgrehdr->call_id));
179			break;
180
181		default:
182			printk(KERN_WARNING "unknown GRE version %hu\n",
183				tuple->dst.u.gre.version);
184			return 0;
185	}
186
187	srckey = gre_keymap_lookup(tuple);
188
189	tuple->src.u.gre.key = srckey;
190
191	return 1;
192}
193
194/* print gre part of tuple */
195static unsigned int gre_print_tuple(char *buffer,
196				    const struct ip_conntrack_tuple *tuple)
197{
198	return sprintf(buffer, "version=%d protocol=0x%04x srckey=0x%x dstkey=0x%x ",
199			tuple->dst.u.gre.version,
200			ntohs(tuple->dst.u.gre.protocol),
201			ntohl(tuple->src.u.gre.key),
202			ntohl(tuple->dst.u.gre.key));
203}
204
205/* print private data for conntrack */
206static unsigned int gre_print_conntrack(char *buffer,
207					const struct ip_conntrack *ct)
208{
209	return sprintf(buffer, "timeout=%u, stream_timeout=%u ",
210		       (ct->proto.gre.timeout / HZ),
211		       (ct->proto.gre.stream_timeout / HZ));
212}
213
214/* Returns verdict for packet, and may modify conntrack */
215static int gre_packet(struct ip_conntrack *ct,
216		      struct iphdr *iph, size_t len,
217		      enum ip_conntrack_info conntrackinfo)
218{
219	/* If we've seen traffic both ways, this is a GRE connection.
220	 * Extend timeout. */
221	if (ct->status & IPS_SEEN_REPLY) {
222		ip_ct_refresh(ct, ct->proto.gre.stream_timeout);
223		/* Also, more likely to be important, and not a probe. */
224		set_bit(IPS_ASSURED_BIT, &ct->status);
225	} else
226		ip_ct_refresh(ct, ct->proto.gre.timeout);
227
228	return NF_ACCEPT;
229}
230
231/* Called when a new connection for this protocol found. */
232static int gre_new(struct ip_conntrack *ct,
233		   struct iphdr *iph, size_t len)
234{
235	DEBUGP(": ");
236	DUMP_TUPLE_GRE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
237
238	/* initialize to sane value.  Ideally a conntrack helper
239	 * (e.g. in case of pptp) is increasing them */
240	ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT;
241	ct->proto.gre.timeout = GRE_TIMEOUT;
242
243	return 1;
244}
245
246/* Called when a conntrack entry has already been removed from the hashes
247 * and is about to be deleted from memory */
248static void gre_destroy(struct ip_conntrack *ct)
249{
250	struct ip_conntrack_expect *master = ct->master;
251
252	DEBUGP(" entering\n");
253
254	if (!master) {
255		DEBUGP("no master exp for ct %p\n", ct);
256		return;
257	}
258
259	WRITE_LOCK(&ip_ct_gre_lock);
260	if (master->proto.gre.keymap_orig) {
261		DEBUGP("removing %p from list\n", master->proto.gre.keymap_orig);
262		list_del(&master->proto.gre.keymap_orig->list);
263		kfree(master->proto.gre.keymap_orig);
264	}
265	if (master->proto.gre.keymap_reply) {
266		DEBUGP("removing %p from list\n", master->proto.gre.keymap_reply);
267		list_del(&master->proto.gre.keymap_reply->list);
268		kfree(master->proto.gre.keymap_reply);
269	}
270	WRITE_UNLOCK(&ip_ct_gre_lock);
271}
272
273/* protocol helper struct */
274static struct ip_conntrack_protocol gre = { { NULL, NULL }, IPPROTO_GRE,
275					    "gre",
276					    gre_pkt_to_tuple,
277					    gre_invert_tuple,
278					    gre_print_tuple,
279					    gre_print_conntrack,
280					    gre_packet,
281					    gre_new,
282					    gre_destroy,
283					    NULL,
284					    THIS_MODULE };
285
286/* ip_conntrack_proto_gre initialization */
287static int __init init(void)
288{
289	int retcode;
290
291	if ((retcode = ip_conntrack_protocol_register(&gre))) {
292                printk(KERN_ERR "Unable to register conntrack protocol "
293			        "helper for gre: %d\n",	retcode);
294		return -EIO;
295	}
296
297	return 0;
298}
299
300static void __exit fini(void)
301{
302	struct list_head *pos, *n;
303
304	/* delete all keymap entries */
305	WRITE_LOCK(&ip_ct_gre_lock);
306	list_for_each_safe(pos, n, &gre_keymap_list) {
307		DEBUGP("deleting keymap %p\n", pos);
308		list_del(pos);
309		kfree(pos);
310	}
311	WRITE_UNLOCK(&ip_ct_gre_lock);
312
313	ip_conntrack_protocol_unregister(&gre);
314}
315
316EXPORT_SYMBOL(ip_ct_gre_keymap_add);
317EXPORT_SYMBOL(ip_ct_gre_keymap_change);
318
319module_init(init);
320module_exit(fini);
321