Pull update-default-configs into release branch
[linux-2.6] / net / ipv4 / netfilter / ip_conntrack_proto_gre.c
1 /*
2  * ip_conntrack_proto_gre.c - Version 3.0 
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-2005 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 static DEFINE_RWLOCK(ip_ct_gre_lock);
36 #define ASSERT_READ_LOCK(x)
37 #define ASSERT_WRITE_LOCK(x)
38
39 #include <linux/netfilter_ipv4/listhelp.h>
40 #include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
41 #include <linux/netfilter_ipv4/ip_conntrack_helper.h>
42 #include <linux/netfilter_ipv4/ip_conntrack_core.h>
43
44 #include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
45 #include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
46
47 MODULE_LICENSE("GPL");
48 MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
49 MODULE_DESCRIPTION("netfilter connection tracking protocol helper for GRE");
50
51 /* shamelessly stolen from ip_conntrack_proto_udp.c */
52 #define GRE_TIMEOUT             (30*HZ)
53 #define GRE_STREAM_TIMEOUT      (180*HZ)
54
55 #if 0
56 #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, __FUNCTION__, ## args)
57 #define DUMP_TUPLE_GRE(x) printk("%u.%u.%u.%u:0x%x -> %u.%u.%u.%u:0x%x\n", \
58                         NIPQUAD((x)->src.ip), ntohs((x)->src.u.gre.key), \
59                         NIPQUAD((x)->dst.ip), ntohs((x)->dst.u.gre.key))
60 #else
61 #define DEBUGP(x, args...)
62 #define DUMP_TUPLE_GRE(x)
63 #endif
64                                 
65 /* GRE KEYMAP HANDLING FUNCTIONS */
66 static LIST_HEAD(gre_keymap_list);
67
68 static inline int gre_key_cmpfn(const struct ip_ct_gre_keymap *km,
69                                 const struct ip_conntrack_tuple *t)
70 {
71         return ((km->tuple.src.ip == t->src.ip) &&
72                 (km->tuple.dst.ip == t->dst.ip) &&
73                 (km->tuple.dst.protonum == t->dst.protonum) &&
74                 (km->tuple.dst.u.all == t->dst.u.all));
75 }
76
77 /* look up the source key for a given tuple */
78 static u_int32_t gre_keymap_lookup(struct ip_conntrack_tuple *t)
79 {
80         struct ip_ct_gre_keymap *km;
81         u_int32_t key = 0;
82
83         read_lock_bh(&ip_ct_gre_lock);
84         km = LIST_FIND(&gre_keymap_list, gre_key_cmpfn,
85                         struct ip_ct_gre_keymap *, t);
86         if (km)
87                 key = km->tuple.src.u.gre.key;
88         read_unlock_bh(&ip_ct_gre_lock);
89         
90         DEBUGP("lookup src key 0x%x up key for ", key);
91         DUMP_TUPLE_GRE(t);
92
93         return key;
94 }
95
96 /* add a single keymap entry, associate with specified master ct */
97 int
98 ip_ct_gre_keymap_add(struct ip_conntrack *ct,
99                      struct ip_conntrack_tuple *t, int reply)
100 {
101         struct ip_ct_gre_keymap **exist_km, *km, *old;
102
103         if (!ct->helper || strcmp(ct->helper->name, "pptp")) {
104                 DEBUGP("refusing to add GRE keymap to non-pptp session\n");
105                 return -1;
106         }
107
108         if (!reply) 
109                 exist_km = &ct->help.ct_pptp_info.keymap_orig;
110         else
111                 exist_km = &ct->help.ct_pptp_info.keymap_reply;
112
113         if (*exist_km) {
114                 /* check whether it's a retransmission */
115                 old = LIST_FIND(&gre_keymap_list, gre_key_cmpfn,
116                                 struct ip_ct_gre_keymap *, t);
117                 if (old == *exist_km) {
118                         DEBUGP("retransmission\n");
119                         return 0;
120                 }
121
122                 DEBUGP("trying to override keymap_%s for ct %p\n", 
123                         reply? "reply":"orig", ct);
124                 return -EEXIST;
125         }
126
127         km = kmalloc(sizeof(*km), GFP_ATOMIC);
128         if (!km)
129                 return -ENOMEM;
130
131         memcpy(&km->tuple, t, sizeof(*t));
132         *exist_km = km;
133
134         DEBUGP("adding new entry %p: ", km);
135         DUMP_TUPLE_GRE(&km->tuple);
136
137         write_lock_bh(&ip_ct_gre_lock);
138         list_append(&gre_keymap_list, km);
139         write_unlock_bh(&ip_ct_gre_lock);
140
141         return 0;
142 }
143
144 /* destroy the keymap entries associated with specified master ct */
145 void ip_ct_gre_keymap_destroy(struct ip_conntrack *ct)
146 {
147         DEBUGP("entering for ct %p\n", ct);
148
149         if (!ct->helper || strcmp(ct->helper->name, "pptp")) {
150                 DEBUGP("refusing to destroy GRE keymap to non-pptp session\n");
151                 return;
152         }
153
154         write_lock_bh(&ip_ct_gre_lock);
155         if (ct->help.ct_pptp_info.keymap_orig) {
156                 DEBUGP("removing %p from list\n", 
157                         ct->help.ct_pptp_info.keymap_orig);
158                 list_del(&ct->help.ct_pptp_info.keymap_orig->list);
159                 kfree(ct->help.ct_pptp_info.keymap_orig);
160                 ct->help.ct_pptp_info.keymap_orig = NULL;
161         }
162         if (ct->help.ct_pptp_info.keymap_reply) {
163                 DEBUGP("removing %p from list\n",
164                         ct->help.ct_pptp_info.keymap_reply);
165                 list_del(&ct->help.ct_pptp_info.keymap_reply->list);
166                 kfree(ct->help.ct_pptp_info.keymap_reply);
167                 ct->help.ct_pptp_info.keymap_reply = NULL;
168         }
169         write_unlock_bh(&ip_ct_gre_lock);
170 }
171
172
173 /* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */
174
175 /* invert gre part of tuple */
176 static int gre_invert_tuple(struct ip_conntrack_tuple *tuple,
177                             const struct ip_conntrack_tuple *orig)
178 {
179         tuple->dst.u.gre.key = orig->src.u.gre.key;
180         tuple->src.u.gre.key = orig->dst.u.gre.key;
181
182         return 1;
183 }
184
185 /* gre hdr info to tuple */
186 static int gre_pkt_to_tuple(const struct sk_buff *skb,
187                            unsigned int dataoff,
188                            struct ip_conntrack_tuple *tuple)
189 {
190         struct gre_hdr_pptp _pgrehdr, *pgrehdr;
191         u_int32_t srckey;
192         struct gre_hdr _grehdr, *grehdr;
193
194         /* first only delinearize old RFC1701 GRE header */
195         grehdr = skb_header_pointer(skb, dataoff, sizeof(_grehdr), &_grehdr);
196         if (!grehdr || grehdr->version != GRE_VERSION_PPTP) {
197                 /* try to behave like "ip_conntrack_proto_generic" */
198                 tuple->src.u.all = 0;
199                 tuple->dst.u.all = 0;
200                 return 1;
201         }
202
203         /* PPTP header is variable length, only need up to the call_id field */
204         pgrehdr = skb_header_pointer(skb, dataoff, 8, &_pgrehdr);
205         if (!pgrehdr)
206                 return 1;
207
208         if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) {
209                 DEBUGP("GRE_VERSION_PPTP but unknown proto\n");
210                 return 0;
211         }
212
213         tuple->dst.u.gre.key = pgrehdr->call_id;
214         srckey = gre_keymap_lookup(tuple);
215         tuple->src.u.gre.key = srckey;
216
217         return 1;
218 }
219
220 /* print gre part of tuple */
221 static int gre_print_tuple(struct seq_file *s,
222                            const struct ip_conntrack_tuple *tuple)
223 {
224         return seq_printf(s, "srckey=0x%x dstkey=0x%x ", 
225                           ntohs(tuple->src.u.gre.key),
226                           ntohs(tuple->dst.u.gre.key));
227 }
228
229 /* print private data for conntrack */
230 static int gre_print_conntrack(struct seq_file *s,
231                                const struct ip_conntrack *ct)
232 {
233         return seq_printf(s, "timeout=%u, stream_timeout=%u ",
234                           (ct->proto.gre.timeout / HZ),
235                           (ct->proto.gre.stream_timeout / HZ));
236 }
237
238 /* Returns verdict for packet, and may modify conntrack */
239 static int gre_packet(struct ip_conntrack *ct,
240                       const struct sk_buff *skb,
241                       enum ip_conntrack_info conntrackinfo)
242 {
243         /* If we've seen traffic both ways, this is a GRE connection.
244          * Extend timeout. */
245         if (ct->status & IPS_SEEN_REPLY) {
246                 ip_ct_refresh_acct(ct, conntrackinfo, skb,
247                                    ct->proto.gre.stream_timeout);
248                 /* Also, more likely to be important, and not a probe. */
249                 set_bit(IPS_ASSURED_BIT, &ct->status);
250                 ip_conntrack_event_cache(IPCT_STATUS, skb);
251         } else
252                 ip_ct_refresh_acct(ct, conntrackinfo, skb,
253                                    ct->proto.gre.timeout);
254         
255         return NF_ACCEPT;
256 }
257
258 /* Called when a new connection for this protocol found. */
259 static int gre_new(struct ip_conntrack *ct,
260                    const struct sk_buff *skb)
261
262         DEBUGP(": ");
263         DUMP_TUPLE_GRE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
264
265         /* initialize to sane value.  Ideally a conntrack helper
266          * (e.g. in case of pptp) is increasing them */
267         ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT;
268         ct->proto.gre.timeout = GRE_TIMEOUT;
269
270         return 1;
271 }
272
273 /* Called when a conntrack entry has already been removed from the hashes
274  * and is about to be deleted from memory */
275 static void gre_destroy(struct ip_conntrack *ct)
276 {
277         struct ip_conntrack *master = ct->master;
278         DEBUGP(" entering\n");
279
280         if (!master)
281                 DEBUGP("no master !?!\n");
282         else
283                 ip_ct_gre_keymap_destroy(master);
284 }
285
286 /* protocol helper struct */
287 static struct ip_conntrack_protocol gre = { 
288         .proto           = IPPROTO_GRE,
289         .name            = "gre", 
290         .pkt_to_tuple    = gre_pkt_to_tuple,
291         .invert_tuple    = gre_invert_tuple,
292         .print_tuple     = gre_print_tuple,
293         .print_conntrack = gre_print_conntrack,
294         .packet          = gre_packet,
295         .new             = gre_new,
296         .destroy         = gre_destroy,
297         .me              = THIS_MODULE,
298 #if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || \
299     defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
300         .tuple_to_nfattr = ip_ct_port_tuple_to_nfattr,
301         .nfattr_to_tuple = ip_ct_port_nfattr_to_tuple,
302 #endif
303 };
304
305 /* ip_conntrack_proto_gre initialization */
306 int __init ip_ct_proto_gre_init(void)
307 {
308         return ip_conntrack_protocol_register(&gre);
309 }
310
311 void __exit ip_ct_proto_gre_fini(void)
312 {
313         struct list_head *pos, *n;
314
315         /* delete all keymap entries */
316         write_lock_bh(&ip_ct_gre_lock);
317         list_for_each_safe(pos, n, &gre_keymap_list) {
318                 DEBUGP("deleting keymap %p at module unload time\n", pos);
319                 list_del(pos);
320                 kfree(pos);
321         }
322         write_unlock_bh(&ip_ct_gre_lock);
323
324         ip_conntrack_protocol_unregister(&gre); 
325 }
326
327 EXPORT_SYMBOL(ip_ct_gre_keymap_add);
328 EXPORT_SYMBOL(ip_ct_gre_keymap_destroy);