Commit | Line | Data |
---|---|---|
926b50f9 HW |
1 | /* |
2 | * ip_conntrack_pptp.c - Version 3.0 | |
3 | * | |
4 | * Connection tracking support for PPTP (Point to Point Tunneling Protocol). | |
5 | * PPTP is a a protocol for creating virtual private networks. | |
6 | * It is a specification defined by Microsoft and some vendors | |
7 | * working with Microsoft. PPTP is built on top of a modified | |
8 | * version of the Internet Generic Routing Encapsulation Protocol. | |
9 | * GRE is defined in RFC 1701 and RFC 1702. Documentation of | |
10 | * PPTP can be found in RFC 2637 | |
11 | * | |
12 | * (C) 2000-2005 by Harald Welte <laforge@gnumonks.org> | |
13 | * | |
14 | * Development of this code funded by Astaro AG (http://www.astaro.com/) | |
15 | * | |
16 | * Limitations: | |
17 | * - We blindly assume that control connections are always | |
18 | * established in PNS->PAC direction. This is a violation | |
19 | * of RFFC2673 | |
20 | * - We can only support one single call within each session | |
21 | * | |
22 | * TODO: | |
23 | * - testing of incoming PPTP calls | |
24 | * | |
25 | * Changes: | |
26 | * 2002-02-05 - Version 1.3 | |
27 | * - Call ip_conntrack_unexpect_related() from | |
28 | * pptp_destroy_siblings() to destroy expectations in case | |
29 | * CALL_DISCONNECT_NOTIFY or tcp fin packet was seen | |
30 | * (Philip Craig <philipc@snapgear.com>) | |
31 | * - Add Version information at module loadtime | |
32 | * 2002-02-10 - Version 1.6 | |
33 | * - move to C99 style initializers | |
34 | * - remove second expectation if first arrives | |
35 | * 2004-10-22 - Version 2.0 | |
36 | * - merge Mandrake's 2.6.x port with recent 2.6.x API changes | |
37 | * - fix lots of linear skb assumptions from Mandrake's port | |
38 | * 2005-06-10 - Version 2.1 | |
39 | * - use ip_conntrack_expect_free() instead of kfree() on the | |
40 | * expect's (which are from the slab for quite some time) | |
41 | * 2005-06-10 - Version 3.0 | |
42 | * - port helper to post-2.6.11 API changes, | |
43 | * funded by Oxcoda NetBox Blue (http://www.netboxblue.com/) | |
44 | * 2005-07-30 - Version 3.1 | |
45 | * - port helper to 2.6.13 API changes | |
46 | * | |
47 | */ | |
48 | ||
926b50f9 HW |
49 | #include <linux/module.h> |
50 | #include <linux/netfilter.h> | |
51 | #include <linux/ip.h> | |
52 | #include <net/checksum.h> | |
53 | #include <net/tcp.h> | |
54 | ||
55 | #include <linux/netfilter_ipv4/ip_conntrack.h> | |
56 | #include <linux/netfilter_ipv4/ip_conntrack_core.h> | |
57 | #include <linux/netfilter_ipv4/ip_conntrack_helper.h> | |
58 | #include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h> | |
59 | #include <linux/netfilter_ipv4/ip_conntrack_pptp.h> | |
60 | ||
61 | #define IP_CT_PPTP_VERSION "3.1" | |
62 | ||
63 | MODULE_LICENSE("GPL"); | |
64 | MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>"); | |
65 | MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP"); | |
66 | ||
67 | static DEFINE_SPINLOCK(ip_pptp_lock); | |
68 | ||
69 | int | |
70 | (*ip_nat_pptp_hook_outbound)(struct sk_buff **pskb, | |
71 | struct ip_conntrack *ct, | |
72 | enum ip_conntrack_info ctinfo, | |
73 | struct PptpControlHeader *ctlh, | |
74 | union pptp_ctrl_union *pptpReq); | |
75 | ||
76 | int | |
77 | (*ip_nat_pptp_hook_inbound)(struct sk_buff **pskb, | |
78 | struct ip_conntrack *ct, | |
79 | enum ip_conntrack_info ctinfo, | |
80 | struct PptpControlHeader *ctlh, | |
81 | union pptp_ctrl_union *pptpReq); | |
82 | ||
83 | int | |
84 | (*ip_nat_pptp_hook_exp_gre)(struct ip_conntrack_expect *expect_orig, | |
85 | struct ip_conntrack_expect *expect_reply); | |
86 | ||
87 | void | |
88 | (*ip_nat_pptp_hook_expectfn)(struct ip_conntrack *ct, | |
89 | struct ip_conntrack_expect *exp); | |
90 | ||
91 | #if 0 | |
92 | /* PptpControlMessageType names */ | |
93 | const char *pptp_msg_name[] = { | |
94 | "UNKNOWN_MESSAGE", | |
95 | "START_SESSION_REQUEST", | |
96 | "START_SESSION_REPLY", | |
97 | "STOP_SESSION_REQUEST", | |
98 | "STOP_SESSION_REPLY", | |
99 | "ECHO_REQUEST", | |
100 | "ECHO_REPLY", | |
101 | "OUT_CALL_REQUEST", | |
102 | "OUT_CALL_REPLY", | |
103 | "IN_CALL_REQUEST", | |
104 | "IN_CALL_REPLY", | |
105 | "IN_CALL_CONNECT", | |
106 | "CALL_CLEAR_REQUEST", | |
107 | "CALL_DISCONNECT_NOTIFY", | |
108 | "WAN_ERROR_NOTIFY", | |
109 | "SET_LINK_INFO" | |
110 | }; | |
111 | EXPORT_SYMBOL(pptp_msg_name); | |
112 | #define DEBUGP(format, args...) printk(KERN_DEBUG "%s:%s: " format, __FILE__, __FUNCTION__, ## args) | |
113 | #else | |
114 | #define DEBUGP(format, args...) | |
115 | #endif | |
116 | ||
117 | #define SECS *HZ | |
118 | #define MINS * 60 SECS | |
119 | #define HOURS * 60 MINS | |
120 | ||
121 | #define PPTP_GRE_TIMEOUT (10 MINS) | |
122 | #define PPTP_GRE_STREAM_TIMEOUT (5 HOURS) | |
123 | ||
124 | static void pptp_expectfn(struct ip_conntrack *ct, | |
125 | struct ip_conntrack_expect *exp) | |
126 | { | |
127 | DEBUGP("increasing timeouts\n"); | |
128 | ||
129 | /* increase timeout of GRE data channel conntrack entry */ | |
130 | ct->proto.gre.timeout = PPTP_GRE_TIMEOUT; | |
131 | ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT; | |
132 | ||
133 | /* Can you see how rusty this code is, compared with the pre-2.6.11 | |
134 | * one? That's what happened to my shiny newnat of 2002 ;( -HW */ | |
135 | ||
136 | if (!ip_nat_pptp_hook_expectfn) { | |
137 | struct ip_conntrack_tuple inv_t; | |
138 | struct ip_conntrack_expect *exp_other; | |
139 | ||
140 | /* obviously this tuple inversion only works until you do NAT */ | |
141 | invert_tuplepr(&inv_t, &exp->tuple); | |
142 | DEBUGP("trying to unexpect other dir: "); | |
143 | DUMP_TUPLE(&inv_t); | |
144 | ||
145 | exp_other = ip_conntrack_expect_find(&inv_t); | |
146 | if (exp_other) { | |
147 | /* delete other expectation. */ | |
148 | DEBUGP("found\n"); | |
149 | ip_conntrack_unexpect_related(exp_other); | |
150 | ip_conntrack_expect_put(exp_other); | |
151 | } else { | |
152 | DEBUGP("not found\n"); | |
153 | } | |
154 | } else { | |
155 | /* we need more than simple inversion */ | |
156 | ip_nat_pptp_hook_expectfn(ct, exp); | |
157 | } | |
158 | } | |
159 | ||
160 | static int destroy_sibling_or_exp(const struct ip_conntrack_tuple *t) | |
161 | { | |
162 | struct ip_conntrack_tuple_hash *h; | |
163 | struct ip_conntrack_expect *exp; | |
164 | ||
165 | DEBUGP("trying to timeout ct or exp for tuple "); | |
166 | DUMP_TUPLE(t); | |
167 | ||
168 | h = ip_conntrack_find_get(t, NULL); | |
169 | if (h) { | |
170 | struct ip_conntrack *sibling = tuplehash_to_ctrack(h); | |
171 | DEBUGP("setting timeout of conntrack %p to 0\n", sibling); | |
172 | sibling->proto.gre.timeout = 0; | |
173 | sibling->proto.gre.stream_timeout = 0; | |
926b50f9 HW |
174 | if (del_timer(&sibling->timeout)) |
175 | sibling->timeout.function((unsigned long)sibling); | |
176 | ip_conntrack_put(sibling); | |
177 | return 1; | |
178 | } else { | |
179 | exp = ip_conntrack_expect_find(t); | |
180 | if (exp) { | |
181 | DEBUGP("unexpect_related of expect %p\n", exp); | |
182 | ip_conntrack_unexpect_related(exp); | |
183 | ip_conntrack_expect_put(exp); | |
184 | return 1; | |
185 | } | |
186 | } | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | ||
192 | /* timeout GRE data connections */ | |
193 | static void pptp_destroy_siblings(struct ip_conntrack *ct) | |
194 | { | |
195 | struct ip_conntrack_tuple t; | |
196 | ||
197 | /* Since ct->sibling_list has literally rusted away in 2.6.11, | |
198 | * we now need another way to find out about our sibling | |
199 | * contrack and expects... -HW */ | |
200 | ||
201 | /* try original (pns->pac) tuple */ | |
202 | memcpy(&t, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(t)); | |
203 | t.dst.protonum = IPPROTO_GRE; | |
204 | t.src.u.gre.key = htons(ct->help.ct_pptp_info.pns_call_id); | |
205 | t.dst.u.gre.key = htons(ct->help.ct_pptp_info.pac_call_id); | |
206 | ||
207 | if (!destroy_sibling_or_exp(&t)) | |
208 | DEBUGP("failed to timeout original pns->pac ct/exp\n"); | |
209 | ||
210 | /* try reply (pac->pns) tuple */ | |
211 | memcpy(&t, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, sizeof(t)); | |
212 | t.dst.protonum = IPPROTO_GRE; | |
213 | t.src.u.gre.key = htons(ct->help.ct_pptp_info.pac_call_id); | |
214 | t.dst.u.gre.key = htons(ct->help.ct_pptp_info.pns_call_id); | |
215 | ||
216 | if (!destroy_sibling_or_exp(&t)) | |
217 | DEBUGP("failed to timeout reply pac->pns ct/exp\n"); | |
218 | } | |
219 | ||
220 | /* expect GRE connections (PNS->PAC and PAC->PNS direction) */ | |
221 | static inline int | |
222 | exp_gre(struct ip_conntrack *master, | |
223 | u_int32_t seq, | |
67497205 AD |
224 | __be16 callid, |
225 | __be16 peer_callid) | |
926b50f9 HW |
226 | { |
227 | struct ip_conntrack_tuple inv_tuple; | |
228 | struct ip_conntrack_tuple exp_tuples[] = { | |
229 | /* tuple in original direction, PNS->PAC */ | |
230 | { .src = { .ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip, | |
231 | .u = { .gre = { .key = peer_callid } } | |
232 | }, | |
233 | .dst = { .ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip, | |
234 | .u = { .gre = { .key = callid } }, | |
235 | .protonum = IPPROTO_GRE | |
236 | }, | |
237 | }, | |
238 | /* tuple in reply direction, PAC->PNS */ | |
239 | { .src = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip, | |
240 | .u = { .gre = { .key = callid } } | |
241 | }, | |
242 | .dst = { .ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip, | |
243 | .u = { .gre = { .key = peer_callid } }, | |
244 | .protonum = IPPROTO_GRE | |
245 | }, | |
246 | } | |
247 | }; | |
248 | struct ip_conntrack_expect *exp_orig, *exp_reply; | |
249 | int ret = 1; | |
250 | ||
251 | exp_orig = ip_conntrack_expect_alloc(master); | |
252 | if (exp_orig == NULL) | |
253 | goto out; | |
254 | ||
255 | exp_reply = ip_conntrack_expect_alloc(master); | |
256 | if (exp_reply == NULL) | |
257 | goto out_put_orig; | |
258 | ||
259 | memcpy(&exp_orig->tuple, &exp_tuples[0], sizeof(exp_orig->tuple)); | |
260 | ||
261 | exp_orig->mask.src.ip = 0xffffffff; | |
262 | exp_orig->mask.src.u.all = 0; | |
263 | exp_orig->mask.dst.u.all = 0; | |
67497205 | 264 | exp_orig->mask.dst.u.gre.key = htons(0xffff); |
926b50f9 HW |
265 | exp_orig->mask.dst.ip = 0xffffffff; |
266 | exp_orig->mask.dst.protonum = 0xff; | |
267 | ||
268 | exp_orig->master = master; | |
269 | exp_orig->expectfn = pptp_expectfn; | |
270 | exp_orig->flags = 0; | |
271 | ||
926b50f9 HW |
272 | /* both expectations are identical apart from tuple */ |
273 | memcpy(exp_reply, exp_orig, sizeof(*exp_reply)); | |
274 | memcpy(&exp_reply->tuple, &exp_tuples[1], sizeof(exp_reply->tuple)); | |
275 | ||
926b50f9 HW |
276 | if (ip_nat_pptp_hook_exp_gre) |
277 | ret = ip_nat_pptp_hook_exp_gre(exp_orig, exp_reply); | |
278 | else { | |
279 | ||
280 | DEBUGP("calling expect_related PNS->PAC"); | |
281 | DUMP_TUPLE(&exp_orig->tuple); | |
282 | ||
283 | if (ip_conntrack_expect_related(exp_orig) != 0) { | |
284 | DEBUGP("cannot expect_related()\n"); | |
285 | goto out_put_both; | |
286 | } | |
287 | ||
288 | DEBUGP("calling expect_related PAC->PNS"); | |
289 | DUMP_TUPLE(&exp_reply->tuple); | |
290 | ||
291 | if (ip_conntrack_expect_related(exp_reply) != 0) { | |
292 | DEBUGP("cannot expect_related()\n"); | |
293 | goto out_unexpect_orig; | |
294 | } | |
295 | ||
296 | /* Add GRE keymap entries */ | |
297 | if (ip_ct_gre_keymap_add(master, &exp_reply->tuple, 0) != 0) { | |
298 | DEBUGP("cannot keymap_add() exp\n"); | |
299 | goto out_unexpect_both; | |
300 | } | |
301 | ||
302 | invert_tuplepr(&inv_tuple, &exp_reply->tuple); | |
303 | if (ip_ct_gre_keymap_add(master, &inv_tuple, 1) != 0) { | |
304 | ip_ct_gre_keymap_destroy(master); | |
305 | DEBUGP("cannot keymap_add() exp_inv\n"); | |
306 | goto out_unexpect_both; | |
307 | } | |
308 | ret = 0; | |
309 | } | |
310 | ||
311 | out_put_both: | |
312 | ip_conntrack_expect_put(exp_reply); | |
313 | out_put_orig: | |
314 | ip_conntrack_expect_put(exp_orig); | |
315 | out: | |
316 | return ret; | |
317 | ||
318 | out_unexpect_both: | |
319 | ip_conntrack_unexpect_related(exp_reply); | |
320 | out_unexpect_orig: | |
321 | ip_conntrack_unexpect_related(exp_orig); | |
322 | goto out_put_both; | |
323 | } | |
324 | ||
325 | static inline int | |
326 | pptp_inbound_pkt(struct sk_buff **pskb, | |
327 | struct tcphdr *tcph, | |
328 | unsigned int nexthdr_off, | |
329 | unsigned int datalen, | |
330 | struct ip_conntrack *ct, | |
331 | enum ip_conntrack_info ctinfo) | |
332 | { | |
333 | struct PptpControlHeader _ctlh, *ctlh; | |
334 | unsigned int reqlen; | |
335 | union pptp_ctrl_union _pptpReq, *pptpReq; | |
336 | struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; | |
67497205 AD |
337 | u_int16_t msg; |
338 | __be16 *cid, *pcid; | |
926b50f9 HW |
339 | u_int32_t seq; |
340 | ||
341 | ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh); | |
342 | if (!ctlh) { | |
343 | DEBUGP("error during skb_header_pointer\n"); | |
344 | return NF_ACCEPT; | |
345 | } | |
346 | nexthdr_off += sizeof(_ctlh); | |
347 | datalen -= sizeof(_ctlh); | |
348 | ||
349 | reqlen = datalen; | |
350 | if (reqlen > sizeof(*pptpReq)) | |
351 | reqlen = sizeof(*pptpReq); | |
352 | pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq); | |
353 | if (!pptpReq) { | |
354 | DEBUGP("error during skb_header_pointer\n"); | |
355 | return NF_ACCEPT; | |
356 | } | |
357 | ||
358 | msg = ntohs(ctlh->messageType); | |
359 | DEBUGP("inbound control message %s\n", pptp_msg_name[msg]); | |
360 | ||
361 | switch (msg) { | |
362 | case PPTP_START_SESSION_REPLY: | |
363 | if (reqlen < sizeof(_pptpReq.srep)) { | |
364 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
365 | break; | |
366 | } | |
367 | ||
368 | /* server confirms new control session */ | |
369 | if (info->sstate < PPTP_SESSION_REQUESTED) { | |
370 | DEBUGP("%s without START_SESS_REQUEST\n", | |
371 | pptp_msg_name[msg]); | |
372 | break; | |
373 | } | |
374 | if (pptpReq->srep.resultCode == PPTP_START_OK) | |
375 | info->sstate = PPTP_SESSION_CONFIRMED; | |
376 | else | |
377 | info->sstate = PPTP_SESSION_ERROR; | |
378 | break; | |
379 | ||
380 | case PPTP_STOP_SESSION_REPLY: | |
381 | if (reqlen < sizeof(_pptpReq.strep)) { | |
382 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
383 | break; | |
384 | } | |
385 | ||
386 | /* server confirms end of control session */ | |
387 | if (info->sstate > PPTP_SESSION_STOPREQ) { | |
388 | DEBUGP("%s without STOP_SESS_REQUEST\n", | |
389 | pptp_msg_name[msg]); | |
390 | break; | |
391 | } | |
392 | if (pptpReq->strep.resultCode == PPTP_STOP_OK) | |
393 | info->sstate = PPTP_SESSION_NONE; | |
394 | else | |
395 | info->sstate = PPTP_SESSION_ERROR; | |
396 | break; | |
397 | ||
398 | case PPTP_OUT_CALL_REPLY: | |
399 | if (reqlen < sizeof(_pptpReq.ocack)) { | |
400 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
401 | break; | |
402 | } | |
403 | ||
404 | /* server accepted call, we now expect GRE frames */ | |
405 | if (info->sstate != PPTP_SESSION_CONFIRMED) { | |
406 | DEBUGP("%s but no session\n", pptp_msg_name[msg]); | |
407 | break; | |
408 | } | |
409 | if (info->cstate != PPTP_CALL_OUT_REQ && | |
410 | info->cstate != PPTP_CALL_OUT_CONF) { | |
411 | DEBUGP("%s without OUTCALL_REQ\n", pptp_msg_name[msg]); | |
412 | break; | |
413 | } | |
414 | if (pptpReq->ocack.resultCode != PPTP_OUTCALL_CONNECT) { | |
415 | info->cstate = PPTP_CALL_NONE; | |
416 | break; | |
417 | } | |
418 | ||
419 | cid = &pptpReq->ocack.callID; | |
420 | pcid = &pptpReq->ocack.peersCallID; | |
421 | ||
422 | info->pac_call_id = ntohs(*cid); | |
423 | ||
424 | if (htons(info->pns_call_id) != *pcid) { | |
425 | DEBUGP("%s for unknown callid %u\n", | |
426 | pptp_msg_name[msg], ntohs(*pcid)); | |
427 | break; | |
428 | } | |
429 | ||
430 | DEBUGP("%s, CID=%X, PCID=%X\n", pptp_msg_name[msg], | |
431 | ntohs(*cid), ntohs(*pcid)); | |
432 | ||
433 | info->cstate = PPTP_CALL_OUT_CONF; | |
434 | ||
435 | seq = ntohl(tcph->seq) + sizeof(struct pptp_pkt_hdr) | |
436 | + sizeof(struct PptpControlHeader) | |
437 | + ((void *)pcid - (void *)pptpReq); | |
438 | ||
439 | if (exp_gre(ct, seq, *cid, *pcid) != 0) | |
440 | printk("ip_conntrack_pptp: error during exp_gre\n"); | |
441 | break; | |
442 | ||
443 | case PPTP_IN_CALL_REQUEST: | |
444 | if (reqlen < sizeof(_pptpReq.icack)) { | |
445 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
446 | break; | |
447 | } | |
448 | ||
449 | /* server tells us about incoming call request */ | |
450 | if (info->sstate != PPTP_SESSION_CONFIRMED) { | |
451 | DEBUGP("%s but no session\n", pptp_msg_name[msg]); | |
452 | break; | |
453 | } | |
454 | pcid = &pptpReq->icack.peersCallID; | |
455 | DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(*pcid)); | |
456 | info->cstate = PPTP_CALL_IN_REQ; | |
457 | info->pac_call_id = ntohs(*pcid); | |
458 | break; | |
459 | ||
460 | case PPTP_IN_CALL_CONNECT: | |
461 | if (reqlen < sizeof(_pptpReq.iccon)) { | |
462 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
463 | break; | |
464 | } | |
465 | ||
466 | /* server tells us about incoming call established */ | |
467 | if (info->sstate != PPTP_SESSION_CONFIRMED) { | |
468 | DEBUGP("%s but no session\n", pptp_msg_name[msg]); | |
469 | break; | |
470 | } | |
7114b0bb AD |
471 | if (info->cstate != PPTP_CALL_IN_REP |
472 | && info->cstate != PPTP_CALL_IN_CONF) { | |
926b50f9 HW |
473 | DEBUGP("%s but never sent IN_CALL_REPLY\n", |
474 | pptp_msg_name[msg]); | |
475 | break; | |
476 | } | |
477 | ||
478 | pcid = &pptpReq->iccon.peersCallID; | |
479 | cid = &info->pac_call_id; | |
480 | ||
481 | if (info->pns_call_id != ntohs(*pcid)) { | |
482 | DEBUGP("%s for unknown CallID %u\n", | |
0ae5d253 | 483 | pptp_msg_name[msg], ntohs(*pcid)); |
926b50f9 HW |
484 | break; |
485 | } | |
486 | ||
487 | DEBUGP("%s, PCID=%X\n", pptp_msg_name[msg], ntohs(*pcid)); | |
488 | info->cstate = PPTP_CALL_IN_CONF; | |
489 | ||
490 | /* we expect a GRE connection from PAC to PNS */ | |
491 | seq = ntohl(tcph->seq) + sizeof(struct pptp_pkt_hdr) | |
492 | + sizeof(struct PptpControlHeader) | |
493 | + ((void *)pcid - (void *)pptpReq); | |
494 | ||
495 | if (exp_gre(ct, seq, *cid, *pcid) != 0) | |
496 | printk("ip_conntrack_pptp: error during exp_gre\n"); | |
497 | ||
498 | break; | |
499 | ||
500 | case PPTP_CALL_DISCONNECT_NOTIFY: | |
501 | if (reqlen < sizeof(_pptpReq.disc)) { | |
502 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
503 | break; | |
504 | } | |
505 | ||
506 | /* server confirms disconnect */ | |
507 | cid = &pptpReq->disc.callID; | |
508 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*cid)); | |
509 | info->cstate = PPTP_CALL_NONE; | |
510 | ||
511 | /* untrack this call id, unexpect GRE packets */ | |
512 | pptp_destroy_siblings(ct); | |
513 | break; | |
514 | ||
515 | case PPTP_WAN_ERROR_NOTIFY: | |
516 | break; | |
517 | ||
518 | case PPTP_ECHO_REQUEST: | |
519 | case PPTP_ECHO_REPLY: | |
520 | /* I don't have to explain these ;) */ | |
521 | break; | |
522 | default: | |
523 | DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX) | |
524 | ? pptp_msg_name[msg]:pptp_msg_name[0], msg); | |
525 | break; | |
526 | } | |
527 | ||
528 | ||
529 | if (ip_nat_pptp_hook_inbound) | |
530 | return ip_nat_pptp_hook_inbound(pskb, ct, ctinfo, ctlh, | |
531 | pptpReq); | |
532 | ||
533 | return NF_ACCEPT; | |
534 | ||
535 | } | |
536 | ||
537 | static inline int | |
538 | pptp_outbound_pkt(struct sk_buff **pskb, | |
539 | struct tcphdr *tcph, | |
540 | unsigned int nexthdr_off, | |
541 | unsigned int datalen, | |
542 | struct ip_conntrack *ct, | |
543 | enum ip_conntrack_info ctinfo) | |
544 | { | |
545 | struct PptpControlHeader _ctlh, *ctlh; | |
546 | unsigned int reqlen; | |
547 | union pptp_ctrl_union _pptpReq, *pptpReq; | |
548 | struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; | |
67497205 AD |
549 | u_int16_t msg; |
550 | __be16 *cid, *pcid; | |
926b50f9 HW |
551 | |
552 | ctlh = skb_header_pointer(*pskb, nexthdr_off, sizeof(_ctlh), &_ctlh); | |
553 | if (!ctlh) | |
554 | return NF_ACCEPT; | |
555 | nexthdr_off += sizeof(_ctlh); | |
556 | datalen -= sizeof(_ctlh); | |
557 | ||
558 | reqlen = datalen; | |
559 | if (reqlen > sizeof(*pptpReq)) | |
560 | reqlen = sizeof(*pptpReq); | |
561 | pptpReq = skb_header_pointer(*pskb, nexthdr_off, reqlen, &_pptpReq); | |
562 | if (!pptpReq) | |
563 | return NF_ACCEPT; | |
564 | ||
565 | msg = ntohs(ctlh->messageType); | |
566 | DEBUGP("outbound control message %s\n", pptp_msg_name[msg]); | |
567 | ||
568 | switch (msg) { | |
569 | case PPTP_START_SESSION_REQUEST: | |
570 | /* client requests for new control session */ | |
571 | if (info->sstate != PPTP_SESSION_NONE) { | |
572 | DEBUGP("%s but we already have one", | |
573 | pptp_msg_name[msg]); | |
574 | } | |
575 | info->sstate = PPTP_SESSION_REQUESTED; | |
576 | break; | |
577 | case PPTP_STOP_SESSION_REQUEST: | |
578 | /* client requests end of control session */ | |
579 | info->sstate = PPTP_SESSION_STOPREQ; | |
580 | break; | |
581 | ||
582 | case PPTP_OUT_CALL_REQUEST: | |
583 | if (reqlen < sizeof(_pptpReq.ocreq)) { | |
584 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
585 | /* FIXME: break; */ | |
586 | } | |
587 | ||
588 | /* client initiating connection to server */ | |
589 | if (info->sstate != PPTP_SESSION_CONFIRMED) { | |
590 | DEBUGP("%s but no session\n", | |
591 | pptp_msg_name[msg]); | |
592 | break; | |
593 | } | |
594 | info->cstate = PPTP_CALL_OUT_REQ; | |
595 | /* track PNS call id */ | |
596 | cid = &pptpReq->ocreq.callID; | |
597 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*cid)); | |
598 | info->pns_call_id = ntohs(*cid); | |
599 | break; | |
600 | case PPTP_IN_CALL_REPLY: | |
601 | if (reqlen < sizeof(_pptpReq.icack)) { | |
602 | DEBUGP("%s: short packet\n", pptp_msg_name[msg]); | |
603 | break; | |
604 | } | |
605 | ||
606 | /* client answers incoming call */ | |
607 | if (info->cstate != PPTP_CALL_IN_REQ | |
608 | && info->cstate != PPTP_CALL_IN_REP) { | |
609 | DEBUGP("%s without incall_req\n", | |
610 | pptp_msg_name[msg]); | |
611 | break; | |
612 | } | |
613 | if (pptpReq->icack.resultCode != PPTP_INCALL_ACCEPT) { | |
614 | info->cstate = PPTP_CALL_NONE; | |
615 | break; | |
616 | } | |
617 | pcid = &pptpReq->icack.peersCallID; | |
618 | if (info->pac_call_id != ntohs(*pcid)) { | |
619 | DEBUGP("%s for unknown call %u\n", | |
620 | pptp_msg_name[msg], ntohs(*pcid)); | |
621 | break; | |
622 | } | |
623 | DEBUGP("%s, CID=%X\n", pptp_msg_name[msg], ntohs(*pcid)); | |
624 | /* part two of the three-way handshake */ | |
625 | info->cstate = PPTP_CALL_IN_REP; | |
626 | info->pns_call_id = ntohs(pptpReq->icack.callID); | |
627 | break; | |
628 | ||
629 | case PPTP_CALL_CLEAR_REQUEST: | |
630 | /* client requests hangup of call */ | |
631 | if (info->sstate != PPTP_SESSION_CONFIRMED) { | |
632 | DEBUGP("CLEAR_CALL but no session\n"); | |
633 | break; | |
634 | } | |
635 | /* FUTURE: iterate over all calls and check if | |
636 | * call ID is valid. We don't do this without newnat, | |
637 | * because we only know about last call */ | |
638 | info->cstate = PPTP_CALL_CLEAR_REQ; | |
639 | break; | |
640 | case PPTP_SET_LINK_INFO: | |
641 | break; | |
642 | case PPTP_ECHO_REQUEST: | |
643 | case PPTP_ECHO_REPLY: | |
644 | /* I don't have to explain these ;) */ | |
645 | break; | |
646 | default: | |
647 | DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)? | |
648 | pptp_msg_name[msg]:pptp_msg_name[0], msg); | |
649 | /* unknown: no need to create GRE masq table entry */ | |
650 | break; | |
651 | } | |
652 | ||
653 | if (ip_nat_pptp_hook_outbound) | |
654 | return ip_nat_pptp_hook_outbound(pskb, ct, ctinfo, ctlh, | |
655 | pptpReq); | |
656 | ||
657 | return NF_ACCEPT; | |
658 | } | |
659 | ||
660 | ||
661 | /* track caller id inside control connection, call expect_related */ | |
662 | static int | |
663 | conntrack_pptp_help(struct sk_buff **pskb, | |
664 | struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) | |
665 | ||
666 | { | |
667 | struct pptp_pkt_hdr _pptph, *pptph; | |
668 | struct tcphdr _tcph, *tcph; | |
669 | u_int32_t tcplen = (*pskb)->len - (*pskb)->nh.iph->ihl * 4; | |
670 | u_int32_t datalen; | |
671 | int dir = CTINFO2DIR(ctinfo); | |
672 | struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info; | |
673 | unsigned int nexthdr_off; | |
674 | ||
675 | int oldsstate, oldcstate; | |
676 | int ret; | |
677 | ||
678 | /* don't do any tracking before tcp handshake complete */ | |
679 | if (ctinfo != IP_CT_ESTABLISHED | |
680 | && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) { | |
681 | DEBUGP("ctinfo = %u, skipping\n", ctinfo); | |
682 | return NF_ACCEPT; | |
683 | } | |
684 | ||
685 | nexthdr_off = (*pskb)->nh.iph->ihl*4; | |
686 | tcph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_tcph), &_tcph); | |
687 | BUG_ON(!tcph); | |
688 | nexthdr_off += tcph->doff * 4; | |
689 | datalen = tcplen - tcph->doff * 4; | |
690 | ||
691 | if (tcph->fin || tcph->rst) { | |
692 | DEBUGP("RST/FIN received, timeouting GRE\n"); | |
693 | /* can't do this after real newnat */ | |
694 | info->cstate = PPTP_CALL_NONE; | |
695 | ||
696 | /* untrack this call id, unexpect GRE packets */ | |
697 | pptp_destroy_siblings(ct); | |
698 | } | |
699 | ||
700 | pptph = skb_header_pointer(*pskb, nexthdr_off, sizeof(_pptph), &_pptph); | |
701 | if (!pptph) { | |
702 | DEBUGP("no full PPTP header, can't track\n"); | |
703 | return NF_ACCEPT; | |
704 | } | |
705 | nexthdr_off += sizeof(_pptph); | |
706 | datalen -= sizeof(_pptph); | |
707 | ||
708 | /* if it's not a control message we can't do anything with it */ | |
709 | if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL || | |
710 | ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) { | |
711 | DEBUGP("not a control packet\n"); | |
712 | return NF_ACCEPT; | |
713 | } | |
714 | ||
715 | oldsstate = info->sstate; | |
716 | oldcstate = info->cstate; | |
717 | ||
718 | spin_lock_bh(&ip_pptp_lock); | |
719 | ||
720 | /* FIXME: We just blindly assume that the control connection is always | |
721 | * established from PNS->PAC. However, RFC makes no guarantee */ | |
722 | if (dir == IP_CT_DIR_ORIGINAL) | |
723 | /* client -> server (PNS -> PAC) */ | |
724 | ret = pptp_outbound_pkt(pskb, tcph, nexthdr_off, datalen, ct, | |
725 | ctinfo); | |
726 | else | |
727 | /* server -> client (PAC -> PNS) */ | |
728 | ret = pptp_inbound_pkt(pskb, tcph, nexthdr_off, datalen, ct, | |
729 | ctinfo); | |
730 | DEBUGP("sstate: %d->%d, cstate: %d->%d\n", | |
731 | oldsstate, info->sstate, oldcstate, info->cstate); | |
732 | spin_unlock_bh(&ip_pptp_lock); | |
733 | ||
734 | return ret; | |
735 | } | |
736 | ||
737 | /* control protocol helper */ | |
738 | static struct ip_conntrack_helper pptp = { | |
739 | .list = { NULL, NULL }, | |
740 | .name = "pptp", | |
741 | .me = THIS_MODULE, | |
742 | .max_expected = 2, | |
743 | .timeout = 5 * 60, | |
744 | .tuple = { .src = { .ip = 0, | |
745 | .u = { .tcp = { .port = | |
746 | __constant_htons(PPTP_CONTROL_PORT) } } | |
747 | }, | |
748 | .dst = { .ip = 0, | |
749 | .u = { .all = 0 }, | |
750 | .protonum = IPPROTO_TCP | |
751 | } | |
752 | }, | |
753 | .mask = { .src = { .ip = 0, | |
67497205 | 754 | .u = { .tcp = { .port = __constant_htons(0xffff) } } |
926b50f9 HW |
755 | }, |
756 | .dst = { .ip = 0, | |
757 | .u = { .all = 0 }, | |
758 | .protonum = 0xff | |
759 | } | |
760 | }, | |
761 | .help = conntrack_pptp_help | |
762 | }; | |
763 | ||
a7768097 | 764 | extern void ip_ct_proto_gre_fini(void); |
926b50f9 HW |
765 | extern int __init ip_ct_proto_gre_init(void); |
766 | ||
767 | /* ip_conntrack_pptp initialization */ | |
65b4b4e8 | 768 | static int __init ip_conntrack_helper_pptp_init(void) |
926b50f9 HW |
769 | { |
770 | int retcode; | |
771 | ||
772 | retcode = ip_ct_proto_gre_init(); | |
773 | if (retcode < 0) | |
774 | return retcode; | |
775 | ||
776 | DEBUGP(" registering helper\n"); | |
777 | if ((retcode = ip_conntrack_helper_register(&pptp))) { | |
778 | printk(KERN_ERR "Unable to register conntrack application " | |
779 | "helper for pptp: %d\n", retcode); | |
780 | ip_ct_proto_gre_fini(); | |
781 | return retcode; | |
782 | } | |
783 | ||
784 | printk("ip_conntrack_pptp version %s loaded\n", IP_CT_PPTP_VERSION); | |
785 | return 0; | |
786 | } | |
787 | ||
65b4b4e8 | 788 | static void __exit ip_conntrack_helper_pptp_fini(void) |
926b50f9 HW |
789 | { |
790 | ip_conntrack_helper_unregister(&pptp); | |
791 | ip_ct_proto_gre_fini(); | |
792 | printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION); | |
793 | } | |
794 | ||
65b4b4e8 AM |
795 | module_init(ip_conntrack_helper_pptp_init); |
796 | module_exit(ip_conntrack_helper_pptp_fini); | |
926b50f9 HW |
797 | |
798 | EXPORT_SYMBOL(ip_nat_pptp_hook_outbound); | |
799 | EXPORT_SYMBOL(ip_nat_pptp_hook_inbound); | |
800 | EXPORT_SYMBOL(ip_nat_pptp_hook_exp_gre); | |
801 | EXPORT_SYMBOL(ip_nat_pptp_hook_expectfn); |